chore: remove unused UI components, dummy data, and theme provider

This commit is contained in:
2026-02-22 00:16:58 +07:00
parent 58d74cb8c8
commit 384ac12109
13 changed files with 948 additions and 229 deletions
+68
View File
@@ -0,0 +1,68 @@
import { fetchWithAuth } from "@/lib/auth"
import type { FormSummary, FormDetail, CreateFormPayload, UpdateFormPayload } from "@/lib/types"
const API_BASE = "http://localhost:8080"
export async function getForms(): Promise<FormSummary[]> {
const res = await fetchWithAuth(`${API_BASE}/api/forms`)
if (!res.ok) {
throw new Error("Failed to fetch forms")
}
return res.json()
}
export async function getFormById(id: string): Promise<FormDetail> {
const res = await fetchWithAuth(`${API_BASE}/api/form/${id}`)
if (!res.ok) {
if (res.status === 404) {
throw new Response("Form not found", { status: 404 })
}
throw new Error("Failed to fetch form")
}
return res.json()
}
export async function createForm(payload: CreateFormPayload): Promise<FormDetail> {
const res = await fetchWithAuth(`${API_BASE}/api/form`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
})
if (!res.ok) {
const data = await res.json().catch(() => null)
throw new Error(data?.message ?? "Failed to create form")
}
return res.json()
}
export async function updateForm(id: string, payload: UpdateFormPayload): Promise<FormDetail> {
const res = await fetchWithAuth(`${API_BASE}/api/form/${id}`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
})
if (!res.ok) {
const data = await res.json().catch(() => null)
throw new Error(data?.message ?? "Failed to update form")
}
return res.json()
}
export async function deleteForm(id: string): Promise<void> {
const res = await fetchWithAuth(`${API_BASE}/api/form/${id}`, {
method: "DELETE",
})
if (!res.ok) {
const data = await res.json().catch(() => null)
throw new Error(data?.message ?? "Failed to delete form")
}
}
+206
View File
@@ -0,0 +1,206 @@
export interface User {
id: string
email: string
name?: string
}
export interface JWTPayload {
sub: string
email: string
name?: string
exp: number
iat: number
}
export function decodeJWT(token: string): JWTPayload | null {
try {
const base64Url = token.split(".")[1]
const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/")
const jsonPayload = decodeURIComponent(
atob(base64)
.split("")
.map((c) => "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2))
.join("")
)
return JSON.parse(jsonPayload)
} catch {
return null
}
}
export function isTokenExpired(token: string): boolean {
const payload = decodeJWT(token)
if (!payload) return true
return Date.now() >= payload.exp * 1000
}
function getStorage(): Storage {
if (typeof window === "undefined") return localStorage
return localStorage.getItem("remember_me") === "true"
? localStorage
: sessionStorage
}
function getToken(key: string): string | null {
return localStorage.getItem(key) ?? sessionStorage.getItem(key)
}
export function setTokens(accessToken: string, refreshToken: string, remember?: boolean) {
if (typeof window === "undefined") return
if (remember !== undefined) {
localStorage.setItem("remember_me", String(remember))
}
const storage = remember !== undefined
? (remember ? localStorage : sessionStorage)
: getStorage()
localStorage.removeItem("access_token")
localStorage.removeItem("refresh_token")
sessionStorage.removeItem("access_token")
sessionStorage.removeItem("refresh_token")
storage.setItem("access_token", accessToken)
storage.setItem("refresh_token", refreshToken)
}
function clearTokens() {
localStorage.removeItem("access_token")
localStorage.removeItem("refresh_token")
localStorage.removeItem("remember_me")
sessionStorage.removeItem("access_token")
sessionStorage.removeItem("refresh_token")
}
export async function refreshAccessToken(): Promise<string | null> {
if (typeof window === "undefined") return null
const refreshToken = getToken("refresh_token")
if (!refreshToken) {
clearTokens()
return null
}
try {
const res = await fetch("http://localhost:8080/api/auth/refresh", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ refresh_token: refreshToken }),
})
if (!res.ok) {
clearTokens()
return null
}
const data = await res.json()
setTokens(data.access_token, data.refresh_token)
return data.access_token
} catch {
clearTokens()
return null
}
}
export function getUser(): User | null {
if (typeof window === "undefined") return null
const token = getToken("access_token")
if (!token || isTokenExpired(token)) return null
const payload = decodeJWT(token)
if (!payload) return null
return {
id: payload.sub,
email: payload.email,
name: payload.name,
}
}
export async function getUserAsync(): Promise<User | null> {
if (typeof window === "undefined") return null
let token = getToken("access_token")
if (!token || isTokenExpired(token)) {
token = await refreshAccessToken()
if (!token) return null
}
const payload = decodeJWT(token)
if (!payload) return null
return {
id: payload.sub,
email: payload.email,
name: payload.name,
}
}
export function getAccessToken(): string | null {
if (typeof window === "undefined") return null
const token = getToken("access_token")
if (!token || isTokenExpired(token)) return null
return token
}
export async function getAccessTokenAsync(): Promise<string | null> {
if (typeof window === "undefined") return null
let token = getToken("access_token")
if (token && !isTokenExpired(token)) return token
token = await refreshAccessToken()
return token
}
export async function logout() {
if (typeof window === "undefined") return
const token = getToken("access_token")
const refreshToken = getToken("refresh_token")
try {
await fetch("http://localhost:8080/api/auth/logout", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: token ? `Bearer ${token}` : "",
},
body: JSON.stringify({ refresh_token: refreshToken }),
})
} catch {
}
clearTokens()
}
export async function fetchWithAuth(url: string, options: RequestInit = {}) {
let token = await getAccessTokenAsync()
const res = await fetch(url, {
...options,
headers: {
...options.headers,
Authorization: token ? `Bearer ${token}` : "",
},
})
if (res.status === 401) {
token = await refreshAccessToken()
if (token) {
return fetch(url, {
...options,
headers: {
...options.headers,
Authorization: `Bearer ${token}`,
},
})
}
}
return res
}
-172
View File
@@ -1,172 +0,0 @@
export type QuestionType =
| "short_text"
| "long_text"
| "multiple_choice"
| "checkbox"
| "dropdown"
| "date"
| "rating"
export interface Question {
id: string
type: QuestionType
title: string
required: boolean
options?: string[]
}
export interface Form {
id: string
title: string
description: string
createdAt: string
updatedAt: string
responseCount: number
questions: Question[]
}
export const dummyForms: Form[] = [
{
id: "1",
title: "RISTEK Datathon 2025 Registration",
description:
"Register for RISTEK's flagship data science competition — the biggest student-led Datathon in Indonesia. Open for undergraduate students from all universities.",
createdAt: "2026-01-10",
updatedAt: "2026-02-05",
responseCount: 420,
questions: [
{
id: "q1",
type: "short_text",
title: "Full Name",
required: true,
},
{
id: "q2",
type: "short_text",
title: "University / Institution",
required: true,
},
{
id: "q3",
type: "dropdown",
title: "Which track are you registering for?",
required: true,
options: [
"Data Analytics",
"Machine Learning",
"Data Engineering",
"MLOps",
],
},
{
id: "q4",
type: "multiple_choice",
title: "How did you hear about RISTEK Datathon 2025?",
required: false,
options: [
"Instagram / Social Media",
"Friend / Colleague",
"Fasilkom UI Website",
"Email Newsletter",
],
},
{
id: "q5",
type: "checkbox",
title:
"Which tools/technologies are you comfortable with? (Select all that apply)",
required: false,
options: [
"Python",
"SQL",
"TensorFlow / PyTorch",
"Tableau / Power BI",
"Spark / Hadoop",
],
},
{
id: "q6",
type: "long_text",
title:
"Briefly describe your motivation for joining RISTEK Datathon 2025.",
required: true,
},
],
},
{
id: "2",
title: "RISTEK Sisters in Tech 2025 — Mentee Application",
description:
"Apply to be a mentee in SISTECH 2025, the first student-powered women-only tech mentorship program in Indonesia. Open for female Indonesian citizens aged 1725.",
createdAt: "2026-01-20",
updatedAt: "2026-02-15",
responseCount: 67,
questions: [
{
id: "q1",
type: "short_text",
title: "Full Name",
required: true,
},
{
id: "q2",
type: "short_text",
title: "Email Address",
required: true,
},
{
id: "q3",
type: "dropdown",
title: "Select your preferred career path",
required: true,
options: [
"Product Management",
"UI/UX Design",
"Software Engineering",
"Data Analytics",
"Digital Marketing",
],
},
{
id: "q4",
type: "rating",
title:
"How would you rate your current knowledge in your chosen career path?",
required: true,
},
{
id: "q5",
type: "checkbox",
title: "What do you hope to gain from SISTECH? (Select all that apply)",
required: false,
options: [
"Mentorship from industry professionals",
"Networking opportunities",
"Portfolio & project guidance",
"Career direction & clarity",
"Community support",
],
},
{
id: "q6",
type: "long_text",
title:
"Tell us about yourself and why you want to join RISTEK Sisters in Tech 2025.",
required: true,
},
{
id: "q7",
type: "multiple_choice",
title: "What is your current education level?",
required: true,
options: [
"High School / Vocational",
"Undergraduate (Year 12)",
"Undergraduate (Year 34)",
"Fresh Graduate",
],
},
],
},
]
+68
View File
@@ -0,0 +1,68 @@
export type QuestionType =
| "short_text"
| "long_text"
| "multiple_choice"
| "checkbox"
| "dropdown"
| "date"
| "rating"
export interface QuestionOption {
id: number
label: string
position: number
}
export interface Question {
id: string
type: QuestionType
title: string
required: boolean
position: number
options: QuestionOption[]
}
export interface FormSummary {
id: string
title: string
description: string
response_count: number
created_at: string
updated_at: string
}
export interface FormDetail {
id: string
user_id: string
title: string
description: string
response_count: number
questions: Question[]
created_at: string
updated_at: string
}
export interface CreateQuestionOption {
label: string
position: number
}
export interface CreateQuestion {
type: QuestionType
title: string
required: boolean
position: number
options: CreateQuestionOption[]
}
export interface CreateFormPayload {
title: string
description: string
questions: CreateQuestion[]
}
export interface UpdateFormPayload {
title: string
description: string
questions: CreateQuestion[]
}