diff --git a/apps/landing/src/app/(auth)/_components/animated-background.tsx b/apps/landing/src/app/(auth)/_components/animated-background.tsx deleted file mode 100644 index 15fd401..0000000 --- a/apps/landing/src/app/(auth)/_components/animated-background.tsx +++ /dev/null @@ -1,86 +0,0 @@ -'use client'; - -import { motion } from 'framer-motion'; -import { useEffect, useMemo, useState } from 'react'; -import { - SiCplusplus, - SiCss, - SiGo, - SiHtml5, - SiJavascript, - SiPhp, - SiPython, - SiRuby, - SiRust, - SiSwift, - SiTypescript, -} from 'react-icons/si'; - -export function AnimatedBackground() { - const [isClient, setIsClient] = useState(false); - - useEffect(() => { - setIsClient(true); - }, []); - - const iconColorMap = useMemo( - () => [ - { Icon: SiJavascript, color: '#F7DF1E' }, - { Icon: SiTypescript, color: '#3178C6' }, - { Icon: SiPython, color: '#3776AB' }, - { Icon: SiCplusplus, color: '#00599C' }, - { Icon: SiRuby, color: '#CC342D' }, - { Icon: SiSwift, color: '#F05138' }, - { Icon: SiRust, color: '#000000' }, - { Icon: SiGo, color: '#00ADD8' }, - { Icon: SiPhp, color: '#777BB4' }, - { Icon: SiHtml5, color: '#E34F26' }, - { Icon: SiCss, color: '#1572B6' }, - ], - [] - ); - - const getRandom = (min: number, max: number) => - Math.random() * (max - min) + min; - - const floatingIcons = useMemo(() => { - if (!isClient) return []; - - return Array.from({ length: 40 }).map((_, i) => { - const { Icon, color } = iconColorMap[i % iconColorMap.length]; - return ( - - - - ); - }); - }, [isClient, iconColorMap]); - - if (!isClient) return null; - - return ( -
{floatingIcons}
- ); -} diff --git a/apps/landing/src/app/(auth)/_http/fetch-post-verify-turnstile.ts b/apps/landing/src/app/(auth)/_http/fetch-post-verify-turnstile.ts deleted file mode 100644 index aefeb2d..0000000 --- a/apps/landing/src/app/(auth)/_http/fetch-post-verify-turnstile.ts +++ /dev/null @@ -1,41 +0,0 @@ -export async function fetchPostverifyTurnstile( - token: string, - remoteIp?: string -): Promise { - const url = 'https://challenges.cloudflare.com/turnstile/v0/siteverify'; - const params = new URLSearchParams({ - secret: String(process.env.TURNSTILE_SECRET_KEY), - response: token, - }); - if (remoteIp) { - params.append('remoteip', remoteIp); - } - - const res = await fetch(url, { - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - }, - body: params.toString(), - }); - - if (!res.ok) { - console.error('Turnstile verify HTTP error', res.status); - return false; - } - - const data = (await res.json()) as TurnstileVerifyResponse; - if (!data.success) { - console.warn('Turnstile failure', data['error-codes']); - return false; - } - - return true; -} - -interface TurnstileVerifyResponse { - success: boolean; - challenge_ts: string; - hostname: string; - 'error-codes'?: string[]; -} diff --git a/apps/landing/src/app/(auth)/forgot-password/_actions/forgot-password-action.ts b/apps/landing/src/app/(auth)/forgot-password/_actions/forgot-password-action.ts deleted file mode 100644 index 200d4db..0000000 --- a/apps/landing/src/app/(auth)/forgot-password/_actions/forgot-password-action.ts +++ /dev/null @@ -1,33 +0,0 @@ -'use server'; - -import { fetcher } from '@/lib/fetcher'; -import { getRemoteIp } from '@/lib/headers'; -import { fetchPostverifyTurnstile } from '../../_http/fetch-post-verify-turnstile'; -import { - forgotPasswordValidationSchema, - ForgotPasswordValidationType, -} from '../_validation/forgot-password-validation'; - -export async function ForgotPasswordAction( - request: ForgotPasswordValidationType -) { - const validRequest = forgotPasswordValidationSchema.parse(request); - const remoteIp = await getRemoteIp(); - - const isCapchaValid = await fetchPostverifyTurnstile( - validRequest.token, - remoteIp - ); - - if (!isCapchaValid) throw new Error('Failed to verify captcha'); - - const { data, error } = await fetcher.POST('/v1/auth/forgot', { - body: { - email: validRequest.email, - }, - }); - - if (error) throw new Error(error.message); - - return data; -} diff --git a/apps/landing/src/app/(auth)/forgot-password/_components/forgot-password-form.tsx b/apps/landing/src/app/(auth)/forgot-password/_components/forgot-password-form.tsx deleted file mode 100644 index 5ead8e0..0000000 --- a/apps/landing/src/app/(auth)/forgot-password/_components/forgot-password-form.tsx +++ /dev/null @@ -1,98 +0,0 @@ -'use client'; - -import { - Button, - Form, - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, - Input, -} from '@components'; -import { Turnstile, TurnstileInstance } from '@marsidev/react-turnstile'; -import { useRef, useState } from 'react'; -import { LuLoader } from 'react-icons/lu'; -import { useFormForgotPassword } from '../_hooks/use-form-forgot-password'; -import { usePostForgotPassowrd } from '../_hooks/use-post-forgot-password'; -import { ForgotPasswordValidationType } from '../_validation/forgot-password-validation'; - -export function ForgotPasswordForm() { - const ref = useRef(null); - - const [step, setStep] = useState(1); - const [emailValue, setEmailValue] = useState(''); - - const form = useFormForgotPassword(); - const { mutate, isPending, error } = usePostForgotPassowrd(form); - - const handleFirstStep = (values: ForgotPasswordValidationType) => { - setEmailValue(values.email); - setStep(2); - }; - - const handleSecondStep = () => { - mutate({ email: emailValue, token: form.getValues('token') }); - }; - - return ( -
- { - e.preventDefault(); - handleSecondStep(); - } - } - className="w-full space-y-4" - > - {error && ( -
- {(error as Error).message} -
- )} - - {step === 1 && ( - ( - - Email - - - - - - )} - /> - )} - - {step === 2 && ( - form.setValue('token', token)} - options={{ theme: 'light', size: 'flexible', language: 'id' }} - /> - )} - - - - - ); -} diff --git a/apps/landing/src/app/(auth)/forgot-password/_hooks/use-form-forgot-password.ts b/apps/landing/src/app/(auth)/forgot-password/_hooks/use-form-forgot-password.ts deleted file mode 100644 index ebf9172..0000000 --- a/apps/landing/src/app/(auth)/forgot-password/_hooks/use-form-forgot-password.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { zodResolver } from '@hookform/resolvers/zod'; -import { useForm } from 'react-hook-form'; -import { - forgotPasswordValidationSchema, - type ForgotPasswordValidationType, -} from '../_validation/forgot-password-validation'; - -export function useFormForgotPassword() { - return useForm({ - resolver: zodResolver(forgotPasswordValidationSchema), - defaultValues: { - email: '', - token: '', - }, - }); -} diff --git a/apps/landing/src/app/(auth)/forgot-password/_hooks/use-post-forgot-password.ts b/apps/landing/src/app/(auth)/forgot-password/_hooks/use-post-forgot-password.ts deleted file mode 100644 index a06a05f..0000000 --- a/apps/landing/src/app/(auth)/forgot-password/_hooks/use-post-forgot-password.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { useMutation } from '@tanstack/react-query'; -import { toast } from 'sonner'; -import { ForgotPasswordAction } from '../_actions/forgot-password-action'; -import { useFormForgotPassword } from './use-form-forgot-password'; - -export function usePostForgotPassowrd( - form: ReturnType -) { - return useMutation({ - mutationFn: ForgotPasswordAction, - onSuccess: ({ message }) => { - form.reset(); - toast.success(message); - }, - onError: ({ message }) => { - form.reset(); - toast.error(message); - }, - }); -} diff --git a/apps/landing/src/app/(auth)/forgot-password/_http/fetch-post-forgot-password.ts b/apps/landing/src/app/(auth)/forgot-password/_http/fetch-post-forgot-password.ts deleted file mode 100644 index e69de29..0000000 diff --git a/apps/landing/src/app/(auth)/forgot-password/_validation/forgot-password-validation.ts b/apps/landing/src/app/(auth)/forgot-password/_validation/forgot-password-validation.ts deleted file mode 100644 index d99d3a1..0000000 --- a/apps/landing/src/app/(auth)/forgot-password/_validation/forgot-password-validation.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { z } from 'zod'; - -export const forgotPasswordValidationSchema = z.object({ - email: z.string().email({ message: 'Format email tidak valid' }), - token: z.string(), -}); -export type ForgotPasswordValidationType = z.infer< - typeof forgotPasswordValidationSchema ->; diff --git a/apps/landing/src/app/(auth)/forgot-password/page.tsx b/apps/landing/src/app/(auth)/forgot-password/page.tsx deleted file mode 100644 index 36cccf8..0000000 --- a/apps/landing/src/app/(auth)/forgot-password/page.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { LogoSimple } from '@/app/_components/logo'; -import { Metadata } from 'next'; -import { ForgotPasswordForm } from './_components/forgot-password-form'; - -export const metadata: Metadata = { - title: 'IMPHNEN - Signin', -}; - -export default function Page() { - return ( - <> -
- - -

- Reset password akunmu -

- - -
- - ); -} diff --git a/apps/landing/src/app/(auth)/layout.tsx b/apps/landing/src/app/(auth)/layout.tsx deleted file mode 100644 index bfcbcb5..0000000 --- a/apps/landing/src/app/(auth)/layout.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { baiJamjureeFont } from '@/lib/fonts'; -import { Card } from '@components'; -import { cn } from '@utils'; -import { ReactNode } from 'react'; -import { AnimatedBackground } from './_components/animated-background'; - -export default function Layout({ children }: { children: ReactNode }) { - return ( -
- -
- -
-
{children}
-
-
-
-
- ); -} diff --git a/apps/landing/src/app/(auth)/reset-password/_actions/reset-password-actions.ts b/apps/landing/src/app/(auth)/reset-password/_actions/reset-password-actions.ts deleted file mode 100644 index b4a8a1a..0000000 --- a/apps/landing/src/app/(auth)/reset-password/_actions/reset-password-actions.ts +++ /dev/null @@ -1,28 +0,0 @@ -'use server'; - -import { fetcher } from '@/lib/fetcher'; -import { - resetPasswordValidationSchema, - ResetPasswordValidationSchema, -} from '../_validation/reset-password-validation'; - -export async function resetPasswordAction( - request: ResetPasswordValidationSchema -) { - const validRequest = resetPasswordValidationSchema.parse(request); - - if (validRequest.confirm_password !== validRequest.confirm_password) { - throw new Error('Password missmatch'); - } - - const { data, error } = await fetcher.POST('/v1/auth/new-password', { - body: { - password: validRequest.password, - token: validRequest.token, - }, - }); - - if (error) throw new Error(error.message); - - return data; -} diff --git a/apps/landing/src/app/(auth)/reset-password/_components/reset-password-form.tsx b/apps/landing/src/app/(auth)/reset-password/_components/reset-password-form.tsx deleted file mode 100644 index c4289b3..0000000 --- a/apps/landing/src/app/(auth)/reset-password/_components/reset-password-form.tsx +++ /dev/null @@ -1,73 +0,0 @@ -'use client'; - -import { - Button, - Form, - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, - Input, -} from '@components'; -import { LuLoaderCircle } from 'react-icons/lu'; -import { usePostResetPassword } from '../_hooks/use-post-reset-password'; -import { useResetPasswordForm } from '../_hooks/use-reset-password-form'; -import { ResetPasswordValidationSchema } from '../_validation/reset-password-validation'; - -export function ResetPasswordForm() { - const form = useResetPasswordForm(); - const { mutate, error, isPending } = usePostResetPassword(form); - - const onSubmit = (values: ResetPasswordValidationSchema) => { - mutate(values); - }; - - return ( -
- {error && ( -
- {(error as Error).message} -
- )} - - - ( - - New Password - - - - - - )} - /> - - ( - - Confirm New Password - - - - - - )} - /> - - - - - ); -} diff --git a/apps/landing/src/app/(auth)/reset-password/_hooks/use-post-reset-password.ts b/apps/landing/src/app/(auth)/reset-password/_hooks/use-post-reset-password.ts deleted file mode 100644 index 80f89ec..0000000 --- a/apps/landing/src/app/(auth)/reset-password/_hooks/use-post-reset-password.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { useMutation } from '@tanstack/react-query'; -import { useRouter } from 'next/navigation'; -import { toast } from 'sonner'; -import { resetPasswordAction } from '../_actions/reset-password-actions'; -import { useResetPasswordForm } from './use-reset-password-form'; - -export function usePostResetPassword( - form: ReturnType -) { - const router = useRouter(); - - return useMutation({ - mutationFn: resetPasswordAction, - onSuccess: ({ message }) => { - form.reset(); - router.replace('/signin'); - toast.success(message); - }, - onError: ({ message }) => { - form.reset(); - toast.error(message); - }, - }); -} diff --git a/apps/landing/src/app/(auth)/reset-password/_hooks/use-reset-password-form.ts b/apps/landing/src/app/(auth)/reset-password/_hooks/use-reset-password-form.ts deleted file mode 100644 index 4cdbb1c..0000000 --- a/apps/landing/src/app/(auth)/reset-password/_hooks/use-reset-password-form.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { zodResolver } from '@hookform/resolvers/zod'; -import { useSearchParams } from 'next/navigation'; -import { useForm } from 'react-hook-form'; - -import { - resetPasswordValidationSchema, - ResetPasswordValidationSchema, -} from '../_validation/reset-password-validation'; - -export function useResetPasswordForm() { - const searchParams = useSearchParams(); - const tokenFromQuery = searchParams.get('token') ?? ''; - - return useForm({ - resolver: zodResolver(resetPasswordValidationSchema), - defaultValues: { - password: '', - confirm_password: '', - token: tokenFromQuery, - }, - }); -} diff --git a/apps/landing/src/app/(auth)/reset-password/_validation/reset-password-validation.ts b/apps/landing/src/app/(auth)/reset-password/_validation/reset-password-validation.ts deleted file mode 100644 index 1261ca8..0000000 --- a/apps/landing/src/app/(auth)/reset-password/_validation/reset-password-validation.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { z } from 'zod'; - -export const resetPasswordValidationSchema = z - .object({ - password: z - .string({ - required_error: 'Password tidak boleh kosong', - invalid_type_error: 'Password harus berupa string', - }) - .min(8, 'Password harus minimal 8 karakter') - .max(50, 'Password tidak boleh lebih dari 50 karakter') - .regex( - /^(?=.*[A-Z])(?=.*[a-z])(?=.*\d)(?=.*\W).+$/, - 'Password harus mengandung setidaknya satu huruf kapital, satu huruf kecil, satu angka, dan satu karakter spesial' - ), - confirm_password: z - .string({ - required_error: 'Konfirmasi password tidak boleh kosong', - }) - .min(8, 'Konfirmasi password harus minimal 8 karakter') - .max(50, 'Konfirmasi password tidak boleh lebih dari 50 karakter'), - token: z.string(), - }) - .refine((data) => data.password === data.confirm_password, { - message: 'Password dan Konfirmasi Password harus sama', - path: ['confirm_password'], - }); - -export type ResetPasswordValidationSchema = z.infer< - typeof resetPasswordValidationSchema ->; diff --git a/apps/landing/src/app/(auth)/reset-password/page.tsx b/apps/landing/src/app/(auth)/reset-password/page.tsx deleted file mode 100644 index 6378290..0000000 --- a/apps/landing/src/app/(auth)/reset-password/page.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { LogoSimple } from '@/app/_components/logo'; -import { redirect } from 'next/navigation'; -import { use } from 'react'; -import { ResetPasswordForm } from './_components/reset-password-form'; - -export const dynamic = 'force-dynamic'; - -export default function Page({ - searchParams, -}: { - searchParams: Promise<{ token?: string }>; -}) { - const { token } = use(searchParams); - - if (!token) redirect('/signin'); - - return ( - <> -
- - -

- Ubah passwordmu -

- - -
- - ); -} diff --git a/apps/landing/src/app/(auth)/signin/_actions/signin-action.ts b/apps/landing/src/app/(auth)/signin/_actions/signin-action.ts deleted file mode 100644 index 0397189..0000000 --- a/apps/landing/src/app/(auth)/signin/_actions/signin-action.ts +++ /dev/null @@ -1,22 +0,0 @@ -'use server'; - -import { setAccessToken, setRefreshToken } from '@/lib/cookies'; -import { fetchPostSignin } from '../_http/fetch-post-signin'; -import { - type SignInValidationType, - signInValidationSchema, -} from '../_validation/signin-validation'; - -export async function SigninAction(request: SignInValidationType) { - const validRequest = signInValidationSchema.parse(request); - - const { data } = await fetchPostSignin(validRequest); - - const accessToken = data.token.access_token; - const refreshToken = data.token.refresh_token; - - await setAccessToken(accessToken); - await setRefreshToken(refreshToken); - - return data; -} diff --git a/apps/landing/src/app/(auth)/signin/_components/signin-form.tsx b/apps/landing/src/app/(auth)/signin/_components/signin-form.tsx deleted file mode 100644 index 490627b..0000000 --- a/apps/landing/src/app/(auth)/signin/_components/signin-form.tsx +++ /dev/null @@ -1,78 +0,0 @@ -'use client'; - -import { - Button, - Form, - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, - Input, -} from '@components'; -import Link from 'next/link'; -import { LuLoader } from 'react-icons/lu'; -import { useFormSignin } from '../_hooks/use-form-signin'; -import { usePostSignin } from '../_hooks/use-post-signin'; -import { type SignInValidationType } from '../_validation/signin-validation'; - -export function SigninForm() { - const form = useFormSignin(); - const { mutate, isPending, error } = usePostSignin(form); - - const onSubmit = (values: SignInValidationType) => { - mutate(values); - }; - - return ( -
- - {error && ( -
- {(error as Error).message} -
- )} - - ( - - Email - - - - - - )} - /> - - ( - - Password - - - - -
- - Lupa Password? - -
-
- )} - /> - - - - - ); -} diff --git a/apps/landing/src/app/(auth)/signin/_hooks/use-form-signin.ts b/apps/landing/src/app/(auth)/signin/_hooks/use-form-signin.ts deleted file mode 100644 index 76af058..0000000 --- a/apps/landing/src/app/(auth)/signin/_hooks/use-form-signin.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { zodResolver } from '@hookform/resolvers/zod'; -import { useForm } from 'react-hook-form'; -import { - signInValidationSchema, - type SignInValidationType, -} from '../_validation/signin-validation'; - -export function useFormSignin() { - return useForm({ - resolver: zodResolver(signInValidationSchema), - defaultValues: { - email: '', - password: '', - }, - }); -} diff --git a/apps/landing/src/app/(auth)/signin/_hooks/use-post-signin.ts b/apps/landing/src/app/(auth)/signin/_hooks/use-post-signin.ts deleted file mode 100644 index 2d4ee62..0000000 --- a/apps/landing/src/app/(auth)/signin/_hooks/use-post-signin.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { useMutation } from '@tanstack/react-query'; -import { useRouter } from 'next/navigation'; -import { toast } from 'sonner'; -import { SigninAction } from '../_actions/signin-action'; -import { useFormSignin } from './use-form-signin'; - -export function usePostSignin(form: ReturnType) { - const router = useRouter(); - - return useMutation({ - mutationFn: SigninAction, - - onSuccess: () => { - router.push('/'); - }, - onError: (error, variables) => { - form.resetField('password'); - - if (error.message.includes('not active')) { - setTimeout(() => { - router.push(`/verification?ref=${variables.email}`); - }, 750); - } - - toast.error(error.message); - }, - }); -} diff --git a/apps/landing/src/app/(auth)/signin/_http/fetch-post-signin.ts b/apps/landing/src/app/(auth)/signin/_http/fetch-post-signin.ts deleted file mode 100644 index 52fbab2..0000000 --- a/apps/landing/src/app/(auth)/signin/_http/fetch-post-signin.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { fetcher } from '@/lib/fetcher'; -import { SignInValidationType } from '../_validation/signin-validation'; - -export async function fetchPostSignin({ - email, - password, -}: SignInValidationType) { - const { data, error, response } = await fetcher.POST('/v1/auth/login', { - body: { - email, - password, - }, - }); - - if (error) { - throw new Error(error.message); - } - - if (!response.ok) { - throw new Error('Someting went wrong, please try again later'); - } - - return data; -} diff --git a/apps/landing/src/app/(auth)/signin/_validation/signin-validation.ts b/apps/landing/src/app/(auth)/signin/_validation/signin-validation.ts deleted file mode 100644 index 0bb050b..0000000 --- a/apps/landing/src/app/(auth)/signin/_validation/signin-validation.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { z } from 'zod'; - -export const signInValidationSchema = z.object({ - email: z - .string({ - required_error: 'Email tidak boleh kosong', - invalid_type_error: 'Email harus berupa string', - }) - .min(1, 'Email tidak boleh kosong') - .email('Email harus valid'), - password: z - .string({ - required_error: 'Password tidak boleh kosong', - invalid_type_error: 'Password harus berupa string', - }) - .min(1, 'Password tidak boleh kosong'), -}); -export type SignInValidationType = z.infer; diff --git a/apps/landing/src/app/(auth)/signin/page.tsx b/apps/landing/src/app/(auth)/signin/page.tsx deleted file mode 100644 index 2eb58e1..0000000 --- a/apps/landing/src/app/(auth)/signin/page.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { LogoSimple } from '@/app/_components/logo'; -import { Metadata } from 'next'; -import Link from 'next/link'; -import { SigninForm } from './_components/signin-form'; - -export const metadata: Metadata = { - title: 'IMPHNEN - Signin', -}; - -export default function Page() { - return ( - <> -
- - -

- Masuk untuk mengakses akunmu -

-
- - - -
-

- Belum punya akun?{' '} - - Daftar - -

-
- - ); -} diff --git a/apps/landing/src/app/(auth)/signup/_actions/signup-action.ts b/apps/landing/src/app/(auth)/signup/_actions/signup-action.ts deleted file mode 100644 index a36fbc0..0000000 --- a/apps/landing/src/app/(auth)/signup/_actions/signup-action.ts +++ /dev/null @@ -1,25 +0,0 @@ -'use server'; - -import { getRemoteIp } from '@/lib/headers'; -import { z } from 'zod'; -import { fetchPostverifyTurnstile } from '../../_http/fetch-post-verify-turnstile'; -import { fetchPostSignin } from '../_http/fetch-post-signup'; -import { signupValidationSchema } from '../_validation/signup-validation'; - -export async function SignupAction( - request: z.infer -) { - const validRequest = signupValidationSchema.parse(request); - const remoteIp = await getRemoteIp(); - - const isCapchaValid = await fetchPostverifyTurnstile( - validRequest.token, - remoteIp - ); - - if (!isCapchaValid) throw new Error('Failed to verify captcha'); - - const data = await fetchPostSignin(validRequest); - - return data; -} diff --git a/apps/landing/src/app/(auth)/signup/_components/signup-form.tsx b/apps/landing/src/app/(auth)/signup/_components/signup-form.tsx deleted file mode 100644 index 40fe83f..0000000 --- a/apps/landing/src/app/(auth)/signup/_components/signup-form.tsx +++ /dev/null @@ -1,223 +0,0 @@ -'use client'; - -import { - Button, - Form, - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, - Input, -} from '@components'; -import { zodResolver } from '@hookform/resolvers/zod'; -import { Turnstile, TurnstileInstance } from '@marsidev/react-turnstile'; -import { useMutation } from '@tanstack/react-query'; -import { useRouter } from 'next/navigation'; -import { useRef, useState } from 'react'; -import { useForm } from 'react-hook-form'; -import { LuLoader } from 'react-icons/lu'; -import { z } from 'zod'; -import { SignupAction } from '../_actions/signup-action'; -import { - signupValidationSchema, - stepOneSignupValidationSchema, - stepTwoSignupValidationSchema, -} from '../_validation/signup-validation'; - -export function SignupForm() { - const router = useRouter(); - const ref = useRef(null); - - const [step, setStep] = useState(1); - const [stepOneData, setStepOneData] = useState | null>(null); - - const firstForm = useForm>({ - resolver: zodResolver(stepOneSignupValidationSchema), - defaultValues: { - email: '', - phone_number: '', - fullname: '', - password: '', - confirm_password: '', - }, - }); - - const secondForm = useForm>({ - resolver: zodResolver(stepTwoSignupValidationSchema), - defaultValues: { - token: '', - }, - }); - - const { mutate, isPending, error } = useMutation({ - mutationFn: async (data: z.infer) => { - const result = await SignupAction(data); - - return { - ...result, - email: data.email, - }; - }, - onSuccess: ({ email }) => { - router.push(`/verification?ref=${email}`); - }, - onError: () => { - ref.current?.reset(); - secondForm.resetField('token'); - }, - }); - - const handleFirstSubmit = ( - values: z.infer - ) => { - setStepOneData(values); - setStep(2); - }; - - const handleSecondSubmit = ( - values: z.infer - ) => { - if (stepOneData) { - mutate({ ...stepOneData, ...values }); - } - }; - - return ( - <> - {step === 1 && ( -
- - ( - - Full Name - - - - - - )} - /> - - ( - - Email - - - - - - )} - /> - - ( - - Nomor Telepon - - - - - - )} - /> - - ( - - Password - - - - - - )} - /> - - ( - - Confirm Password - - - - - - )} - /> - - - - - )} - - {step === 2 && ( -
- - {error && ( -
- {(error as Error).message} -
- )} - - secondForm.setValue('token', token)} - options={{ - theme: 'light', - size: 'flexible', - language: 'id', - }} - /> - -
- - -
- - - )} - - ); -} diff --git a/apps/landing/src/app/(auth)/signup/_http/fetch-post-signup.ts b/apps/landing/src/app/(auth)/signup/_http/fetch-post-signup.ts deleted file mode 100644 index 16e6c16..0000000 --- a/apps/landing/src/app/(auth)/signup/_http/fetch-post-signup.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { fetcher } from '@/lib/fetcher'; -import { SignupValidationSchema } from '../_validation/signup-validation'; - -export async function fetchPostSignin({ - email, - password, - fullname, - phone_number, -}: SignupValidationSchema) { - const { data, error, response } = await fetcher.POST('/v1/auth/register', { - body: { - email, - password, - fullname, - phone_number, - }, - }); - - if (error) { - throw new Error(error.message); - } - - if (!response.ok) { - throw new Error('Someting went wrong, please try again later'); - } - - return data; -} diff --git a/apps/landing/src/app/(auth)/signup/_validation/signup-validation.ts b/apps/landing/src/app/(auth)/signup/_validation/signup-validation.ts deleted file mode 100644 index dd994a4..0000000 --- a/apps/landing/src/app/(auth)/signup/_validation/signup-validation.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { z } from 'zod'; - -export const stepOneSignupValidationSchema = z - .object({ - email: z - .string({ - required_error: 'Email tidak boleh kosong', - invalid_type_error: 'Email harus berupa string', - }) - .min(1, 'Email tidak boleh kosong') - .email('Email harus valid'), - fullname: z - .string({ - required_error: 'Nama tidak boleh kosong', - invalid_type_error: 'Nama harus berupa string', - }) - .min(1, 'Nama tidak boleh kosong') - .max(50, 'Nama tidak boleh lebih dari 50 karakter'), - password: z - .string({ - required_error: 'Password tidak boleh kosong', - invalid_type_error: 'Password harus berupa string', - }) - .min(8, 'Password harus minimal 8 karakter') - .max(50, 'Password tidak boleh lebih dari 50 karakter') - .regex( - /^(?=.*[A-Z])(?=.*[a-z])(?=.*\d)(?=.*\W).+$/, - 'Password harus mengandung setidaknya satu huruf kapital, satu huruf kecil, satu angka, dan satu karakter spesial' - ), - confirm_password: z - .string({ - required_error: 'Konfirmasi password tidak boleh kosong', - }) - .min(8, 'Konfirmasi password harus minimal 8 karakter') - .max(50, 'Konfirmasi password tidak boleh lebih dari 50 karakter'), - phone_number: z - .string({ - required_error: 'Nomor telepon tidak boleh kosong', - invalid_type_error: 'Nomor telepon harus berupa string', - }) - .min(10, 'Nomor telepon tidak boleh kurang dari 10 karakter') - .max(15, 'Nomor telepon tidak boleh lebih dari 15 karakter') - .regex(/^\d+$/, 'Nomor telepon hanya boleh berisi angka'), - }) - .refine((data) => data.password === data.confirm_password, { - message: 'Password dan Konfirmasi Password harus sama', - path: ['confirm_password'], - }); - -export const stepTwoSignupValidationSchema = z.object({ - token: z.string(), -}); - -export const signupValidationSchema = stepOneSignupValidationSchema.and( - stepTwoSignupValidationSchema -); -export type SignupValidationSchema = z.infer; diff --git a/apps/landing/src/app/(auth)/signup/page.tsx b/apps/landing/src/app/(auth)/signup/page.tsx deleted file mode 100644 index 444d145..0000000 --- a/apps/landing/src/app/(auth)/signup/page.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { LogoSimple } from '@/app/_components/logo'; -import { Metadata } from 'next'; -import Link from 'next/link'; -import { SignupForm } from './_components/signup-form'; - -export const metadata: Metadata = { - title: 'IMPHNEN - Signup', -}; - -export default function Page() { - return ( - <> -
- - -

- Buat akunmu sekarang -

-
- - - -
-

- Sudah punya akun?{' '} - - Masuk - -

-
- - ); -} diff --git a/apps/landing/src/app/(auth)/verification/_actions/resend-otp-action.ts b/apps/landing/src/app/(auth)/verification/_actions/resend-otp-action.ts deleted file mode 100644 index 48f733e..0000000 --- a/apps/landing/src/app/(auth)/verification/_actions/resend-otp-action.ts +++ /dev/null @@ -1,31 +0,0 @@ -'use server'; - -import { fetcher } from '@/lib/fetcher'; -import { getRemoteIp } from '@/lib/headers'; -import { fetchPostverifyTurnstile } from '../../_http/fetch-post-verify-turnstile'; -import { - resendOTPValidationSchema, - type ResendOTPValidationType, -} from '../_validation/resend-otp-validation'; - -export async function resendOTPAction(request: ResendOTPValidationType) { - const validRequest = resendOTPValidationSchema.parse(request); - const remoteIp = await getRemoteIp(); - - const isCapchaValid = await fetchPostverifyTurnstile( - validRequest.token, - remoteIp - ); - - if (!isCapchaValid) throw new Error('Failed to verify captcha'); - - const { data, error } = await fetcher.POST('/v1/auth/send-otp', { - body: { - email: validRequest.email, - }, - }); - - if (error) throw new Error(error.message); - - return data; -} diff --git a/apps/landing/src/app/(auth)/verification/_actions/verify-email-action.ts b/apps/landing/src/app/(auth)/verification/_actions/verify-email-action.ts deleted file mode 100644 index 8e18021..0000000 --- a/apps/landing/src/app/(auth)/verification/_actions/verify-email-action.ts +++ /dev/null @@ -1,15 +0,0 @@ -'use server'; - -import { fetchPostVerifyEmail } from '../_http/fetch-post-verify-email'; -import { - type VerifyEmailValidationType, - verifyEmailValidationSchema, -} from '../_validation/verify-email-validation'; - -export async function verifyEmailAction(request: VerifyEmailValidationType) { - const validRequest = verifyEmailValidationSchema.parse(request); - - const data = await fetchPostVerifyEmail(validRequest); - - return data; -} diff --git a/apps/landing/src/app/(auth)/verification/_components/resend-otp-form.tsx b/apps/landing/src/app/(auth)/verification/_components/resend-otp-form.tsx deleted file mode 100644 index 7731608..0000000 --- a/apps/landing/src/app/(auth)/verification/_components/resend-otp-form.tsx +++ /dev/null @@ -1,74 +0,0 @@ -'use client'; - -import { - Button, - Form, - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, - Input, -} from '@components'; -import { Turnstile, TurnstileInstance } from '@marsidev/react-turnstile'; -import { useRef } from 'react'; -import { useFormResendOTP } from '../_hooks/use-form-resend-otp'; -import { usePostResendOTP } from '../_hooks/use-post-resend-otp'; -import { ResendOTPValidationType } from '../_validation/resend-otp-validation'; - -export function ResendOTPForm() { - const ref = useRef(null); - - const form = useFormResendOTP(); - - const { mutate, isPending, error } = usePostResendOTP(form); - - const onSubmit = (values: ResendOTPValidationType) => { - mutate(values); - }; - - return ( -
- - {error && ( -
- {(error as Error).message} -
- )} - - ( - - Email - - - - - - )} - /> - - form.setValue('token', token)} - options={{ - theme: 'light', - size: 'flexible', - language: 'id', - }} - /> - - - - - ); -} diff --git a/apps/landing/src/app/(auth)/verification/_components/verification-tabs.tsx b/apps/landing/src/app/(auth)/verification/_components/verification-tabs.tsx deleted file mode 100644 index 319505c..0000000 --- a/apps/landing/src/app/(auth)/verification/_components/verification-tabs.tsx +++ /dev/null @@ -1,41 +0,0 @@ -'use client'; - -import { cn } from '@utils'; -import { useState } from 'react'; -import { ResendOTPForm } from './resend-otp-form'; -import { VerifyEmailForm } from './verify-email-form'; - -export function VerificationTabs() { - const [activeTab, setActiveTab] = useState<'form' | 'resend'>('form'); - - return ( - <> -
- - -
- - {activeTab === 'form' ? : } - - ); -} diff --git a/apps/landing/src/app/(auth)/verification/_components/verify-email-form.tsx b/apps/landing/src/app/(auth)/verification/_components/verify-email-form.tsx deleted file mode 100644 index 895bb10..0000000 --- a/apps/landing/src/app/(auth)/verification/_components/verify-email-form.tsx +++ /dev/null @@ -1,77 +0,0 @@ -'use client'; - -import { - Button, - Form, - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, - Input, -} from '@components'; -import { LuLoader } from 'react-icons/lu'; -import { useFormVerifyEmail } from '../_hooks/use-form-verify-email'; -import { usePostVerifyEmail } from '../_hooks/use-post-verify-email'; -import { type VerifyEmailValidationType } from '../_validation/verify-email-validation'; - -export function VerifyEmailForm() { - const form = useFormVerifyEmail(); - const { mutate, isPending, error } = usePostVerifyEmail(form); - - const onSubmit = (values: VerifyEmailValidationType) => { - mutate(values); - }; - - return ( -
- - {error && ( -
- {(error as Error).message} -
- )} - - ( - - Email - - - - - - )} - /> - - ( - - OTP - - - - - - )} - /> - - - - - ); -} diff --git a/apps/landing/src/app/(auth)/verification/_hooks/use-form-resend-otp.ts b/apps/landing/src/app/(auth)/verification/_hooks/use-form-resend-otp.ts deleted file mode 100644 index 8578cda..0000000 --- a/apps/landing/src/app/(auth)/verification/_hooks/use-form-resend-otp.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { zodResolver } from '@hookform/resolvers/zod'; -import { useSearchParams } from 'next/navigation'; -import { useForm } from 'react-hook-form'; -import { - resendOTPValidationSchema, - type ResendOTPValidationType, -} from '../_validation/resend-otp-validation'; - -export function useFormResendOTP() { - const searchParams = useSearchParams(); - const email = searchParams.get('ref'); - - return useForm({ - resolver: zodResolver(resendOTPValidationSchema), - defaultValues: { - email: email ?? '', - token: '', - }, - }); -} diff --git a/apps/landing/src/app/(auth)/verification/_hooks/use-form-verify-email.ts b/apps/landing/src/app/(auth)/verification/_hooks/use-form-verify-email.ts deleted file mode 100644 index 859a6a8..0000000 --- a/apps/landing/src/app/(auth)/verification/_hooks/use-form-verify-email.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { zodResolver } from '@hookform/resolvers/zod'; -import { useSearchParams } from 'next/navigation'; -import { useForm } from 'react-hook-form'; -import { - verifyEmailValidationSchema, - type VerifyEmailValidationType, -} from '../_validation/verify-email-validation'; - -export function useFormVerifyEmail() { - const searchParams = useSearchParams(); - const email = searchParams.get('ref'); - - return useForm({ - resolver: zodResolver(verifyEmailValidationSchema), - defaultValues: { - email: email ?? '', - otp: '', - }, - }); -} diff --git a/apps/landing/src/app/(auth)/verification/_hooks/use-post-resend-otp.ts b/apps/landing/src/app/(auth)/verification/_hooks/use-post-resend-otp.ts deleted file mode 100644 index 401d6eb..0000000 --- a/apps/landing/src/app/(auth)/verification/_hooks/use-post-resend-otp.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { useMutation } from '@tanstack/react-query'; -import { toast } from 'sonner'; -import { resendOTPAction } from '../_actions/resend-otp-action'; -import { useFormResendOTP } from './use-form-resend-otp'; - -export function usePostResendOTP(form: ReturnType) { - return useMutation({ - mutationFn: resendOTPAction, - onSuccess: ({ message }) => { - form.reset(); - toast.success(message); - }, - onError: ({ message }) => { - form.reset(); - toast.error(message); - }, - }); -} diff --git a/apps/landing/src/app/(auth)/verification/_hooks/use-post-verify-email.ts b/apps/landing/src/app/(auth)/verification/_hooks/use-post-verify-email.ts deleted file mode 100644 index e7da35d..0000000 --- a/apps/landing/src/app/(auth)/verification/_hooks/use-post-verify-email.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { useMutation } from '@tanstack/react-query'; -import { useRouter } from 'next/navigation'; -import { verifyEmailAction } from '../_actions/verify-email-action'; -import { useFormVerifyEmail } from './use-form-verify-email'; - -export function usePostVerifyEmail( - form: ReturnType -) { - const router = useRouter(); - - return useMutation({ - mutationFn: verifyEmailAction, - onSuccess: () => { - router.push('/'); - }, - onError: () => { - form.resetField('otp'); - }, - }); -} diff --git a/apps/landing/src/app/(auth)/verification/_http/fetch-post-verify-email.ts b/apps/landing/src/app/(auth)/verification/_http/fetch-post-verify-email.ts deleted file mode 100644 index e0a57c8..0000000 --- a/apps/landing/src/app/(auth)/verification/_http/fetch-post-verify-email.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { fetcher } from '@/lib/fetcher'; -import { VerifyEmailValidationType } from '../_validation/verify-email-validation'; - -export async function fetchPostVerifyEmail({ - email, - otp, -}: VerifyEmailValidationType) { - const formattedOTP = parseInt(otp); - - const { data, error, response } = await fetcher.POST( - '/v1/auth/verify-email', - { - body: { - email, - otp: formattedOTP, - }, - } - ); - - if (error) { - throw new Error(error.message); - } - - if (!response.ok) { - throw new Error('Something went wrong, please try again later'); - } - - console.log(data); - - return data; -} diff --git a/apps/landing/src/app/(auth)/verification/_validation/resend-otp-validation.ts b/apps/landing/src/app/(auth)/verification/_validation/resend-otp-validation.ts deleted file mode 100644 index 2b4bc20..0000000 --- a/apps/landing/src/app/(auth)/verification/_validation/resend-otp-validation.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { z } from 'zod'; - -export const resendOTPValidationSchema = z.object({ - email: z.string().email({ message: 'Format email tidak valid' }), - token: z.string().min(1, { message: 'OTP harus terdiri dari 1 digit' }), -}); -export type ResendOTPValidationType = z.infer; diff --git a/apps/landing/src/app/(auth)/verification/_validation/verify-email-validation.ts b/apps/landing/src/app/(auth)/verification/_validation/verify-email-validation.ts deleted file mode 100644 index 7ace2c8..0000000 --- a/apps/landing/src/app/(auth)/verification/_validation/verify-email-validation.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { z } from 'zod'; - -export const verifyEmailValidationSchema = z.object({ - email: z.string().email({ message: 'Format email tidak valid' }), - otp: z - .string() - .min(6, { message: 'OTP harus terdiri dari 6 digit' }) - .max(6, { message: 'OTP harus terdiri dari 6 digit' }), -}); -export type VerifyEmailValidationType = z.infer< - typeof verifyEmailValidationSchema ->; diff --git a/apps/landing/src/app/(auth)/verification/page.tsx b/apps/landing/src/app/(auth)/verification/page.tsx deleted file mode 100644 index 21658ed..0000000 --- a/apps/landing/src/app/(auth)/verification/page.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { Suspense } from 'react'; -import { LogoSimple } from '../../_components/logo'; -import { VerificationTabs } from './_components/verification-tabs'; - -export default function Page() { - return ( - <> -
- - -

- Verifikasi akun mu sekarang -

-
- - - - - - ); -} diff --git a/apps/landing/src/app/(public)/_components/header.tsx b/apps/landing/src/app/(public)/_components/header.tsx index 63aae9a..e73ecc5 100644 --- a/apps/landing/src/app/(public)/_components/header.tsx +++ b/apps/landing/src/app/(public)/_components/header.tsx @@ -2,16 +2,14 @@ import { LogoSimple } from '@/app/_components/logo'; import NAVIGATIONS from '@/data/navigations.json'; -import { Button } from '@components'; import { cn } from '@utils'; import { AnimatePresence, motion } from 'framer-motion'; import Link from 'next/link'; -import { usePathname, useRouter } from 'next/navigation'; +import { usePathname } from 'next/navigation'; import { useEffect, useState } from 'react'; import { LuMenu, LuX } from 'react-icons/lu'; export function Header() { - const router = useRouter(); const pathname = usePathname(); const [mobileMenuOpen, setMobileMenuOpen] = useState(false); @@ -61,22 +59,6 @@ export function Header() { ))} -
- - -
- - - )} diff --git a/apps/landing/src/app/(public)/hackathon/page.tsx b/apps/landing/src/app/(public)/hackathon/page.tsx index 1b26585..f6c9ac0 100644 --- a/apps/landing/src/app/(public)/hackathon/page.tsx +++ b/apps/landing/src/app/(public)/hackathon/page.tsx @@ -1,78 +1,158 @@ 'use client'; -import hackathons from '@/data/hackathons.json'; import { buttonVariants } from '@components'; import { cn } from '@utils'; -import Image from 'next/image'; -import { HiOutlineCode } from 'react-icons/hi'; +import { useEffect, useState } from 'react'; +import { HiOutlineCode, HiOutlineTrophy } from 'react-icons/hi'; import { motion } from 'framer-motion'; -export default function HackathonsPage() { - const sortedHackathons = [...hackathons]; +interface TeamItem { + id: string; + name: string; + description: string; + city: string; + logo: string | null; + banner: string | null; +} - if (sortedHackathons.length === 0) { +interface WinnerItem { + id: string; + team_id: string; + team_name: string; + rank: number; + prize: string | null; +} + +export default function HackathonsPage() { + const [teams, setTeams] = useState([]); + const [winners, setWinners] = useState([]); + const [loading, setLoading] = useState(true); + + useEffect(() => { + Promise.all([ + fetch('https://api.imphnen.dev/v1/hackathon/teams/browse?per_page=20') + .then((r) => r.json()) + .then((j) => j.data?.data || []) + .catch(() => []), + fetch('https://api.imphnen.dev/v1/hackathon/winners') + .then((r) => r.json()) + .then((j) => j.data || []) + .catch(() => []), + ]).then(([t, w]) => { + setTeams(t); + setWinners(w); + setLoading(false); + }); + }, []); + + const getWinnerRank = (teamId: string) => { + const w = winners.find((x) => x.team_id === teamId); + return w ? w.rank : null; + }; + + if (loading) { return (
-

No hackathon projects available yet.

+
+
+ ); + } + + if (teams.length === 0) { + return ( +
+
+

No hackathon teams yet.

+ + Join Hackathon + +
); } return (
+
+

IMPHNEN Hackathon

+

+ Tim-tim yang berpartisipasi dalam hackathon IMPHNEN +

+
+ + {winners.length > 0 && ( +
+

+ Pemenang +

+
+ {winners.sort((a, b) => a.rank - b.rank).map((w) => ( +
+
{w.rank === 1 ? '🥇' : w.rank === 2 ? '🥈' : '🥉'}
+

{w.team_name}

+

Juara {w.rank}

+ {w.prize &&

{w.prize}

} +
+ ))} +
+
+ )} + +

Semua Tim

- {sortedHackathons.map((hackathon, idx) => ( - - - {hackathon.project_title} - -
-

- {hackathon.project_title} -

-
-
- - {hackathon.team_name} + {teams.map((team, idx) => { + const rank = getWinnerRank(team.id); + return ( + + {team.banner ? ( +
+ {team.name}
-
-

- {hackathon.description} -

- + +
+ )} + - - ))} + {team.description && ( +

{team.description}

+ )} + + Lihat Tim + +
+
+ ); + })}
); diff --git a/apps/landing/src/app/(public)/roadmap/_components/projects-vote.tsx b/apps/landing/src/app/(public)/roadmap/_components/projects-vote.tsx index 8f92a67..35cf010 100644 --- a/apps/landing/src/app/(public)/roadmap/_components/projects-vote.tsx +++ b/apps/landing/src/app/(public)/roadmap/_components/projects-vote.tsx @@ -2,64 +2,63 @@ import { Button, Card, CardContent, CardHeader, CardTitle } from '@components'; import { AnimatePresence, motion } from 'framer-motion'; -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import { BiUpvote } from 'react-icons/bi'; import { FiCheckCircle } from 'react-icons/fi'; import { MdOutlineOpenInNew } from 'react-icons/md'; +interface RoadmapItem { + id: string; + title: string; + description: string; + status: 'upcoming' | 'in_progress' | 'completed'; + votes: number; + created_at: string; +} + export default function ProjectsVote() { - const [upcomingItems, setUpcomingItems] = useState([ - { - title: 'IMPHNEN Project Showcase', - description: 'Showcase projectmu ke member lain dan dapatkan feedback', - votes: 42, - voted: false, - }, - { - title: 'IMPHNEN Meme Generator', - description: 'Bikin meme kocak kapanpun dengan mudah', - votes: 42, - voted: false, - }, - ]); + const [items, setItems] = useState([]); + const [loading, setLoading] = useState(true); + const [votedIds, setVotedIds] = useState>(new Set()); - const inProgressItems = [ - { - title: 'IMPHNEN Twibbon', - description: 'Buat Twibbon kece untuk profile media sosialmu', - }, - { - title: 'IMPHNEN Certificate', - description: 'Cetak sertifikat keren secara instan untuk anggota IMPHNEN', - }, - ]; + useEffect(() => { + fetch('https://api.imphnen.dev/v1/landing/cms/roadmap') + .then((r) => r.json()) + .then((json) => { + setItems(json.data || []); + }) + .catch(() => {}) + .finally(() => setLoading(false)); + }, []); - const completedItems = [ - { - title: 'IMPHNEN List Event', - description: - 'Koleksi daftar event dan kolaborasi seru yang bisa kamu ikuti', - }, - { - title: 'IMPHNEN Testimoni', - description: 'Berikan testimonial gokil buat komunitas IMPHNEN', - }, - { - title: 'IMPHNEN Roadmap by Vote', - description: 'Usulkan ide fitur seru dan ajak anggota lain buat voting', - }, - ]; + const upcomingItems = items.filter((i) => i.status === 'upcoming'); + const inProgressItems = items.filter((i) => i.status === 'in_progress'); + const completedItems = items.filter((i) => i.status === 'completed'); - const handleVote = (index: number) => { - const newItems = [...upcomingItems]; - newItems[index] = { - ...newItems[index], - votes: newItems[index].voted - ? newItems[index].votes - 1 - : newItems[index].votes + 1, - voted: !newItems[index].voted, - }; - setUpcomingItems(newItems); + const handleVote = (id: string) => { + const alreadyVoted = votedIds.has(id); + + // Optimistic update + setItems((prev) => + prev.map((item) => + item.id === id + ? { ...item, votes: item.votes + (alreadyVoted ? -1 : 1) } + : item + ) + ); + + if (alreadyVoted) { + setVotedIds((prev) => { + const next = new Set(prev); + next.delete(id); + return next; + }); + } else { + setVotedIds((prev) => new Set(prev).add(id)); + fetch(`https://api.imphnen.dev/v1/landing/cms/roadmap/vote/${id}`, { + method: 'POST', + }).catch(() => {}); + } }; const containerVariants = { @@ -77,6 +76,14 @@ export default function ProjectsVote() { visible: { opacity: 1, y: 0, transition: { duration: 0.2 } }, }; + if (loading) { + return ( +
+
+
+ ); + } + return (
@@ -95,8 +102,8 @@ export default function ProjectsVote() { animate="visible" className="space-y-5" > - {upcomingItems.map((item, index) => ( - + {upcomingItems.map((item) => ( + @@ -110,24 +117,24 @@ export default function ProjectsVote() {
handleVote(index)} + onClick={() => handleVote(item.id)} className={`flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium transition-all ${ - item.voted + votedIds.has(item.id) ? 'bg-primary-500 text-white hover:bg-primary-600' : 'bg-gray-100 text-gray-700 hover:bg-gray-200' }`} > - {item.voted ? 'Voted' : 'Vote'} + {votedIds.has(item.id) ? 'Voted' : 'Vote'}
@@ -140,6 +147,9 @@ export default function ProjectsVote() { ))} + {upcomingItems.length === 0 && ( +

No upcoming items yet.

+ )}
@@ -160,7 +170,7 @@ export default function ProjectsVote() { className="space-y-5" > {inProgressItems.map((item, index) => ( - + @@ -176,14 +186,15 @@ export default function ProjectsVote() { style={{ width: `${(index + 1) * 33}%` }} >
-

- {index === 0 ? 'Development started' : 'In development'} -

+

In development

))} + {inProgressItems.length === 0 && ( +

Nothing in progress.

+ )} @@ -203,8 +214,8 @@ export default function ProjectsVote() { animate="visible" className="space-y-5" > - {completedItems.map((item, index) => ( - + {completedItems.map((item) => ( + @@ -229,6 +240,9 @@ export default function ProjectsVote() { ))} + {completedItems.length === 0 && ( +

No completed items yet.

+ )}
diff --git a/apps/landing/src/data/events.json b/apps/landing/src/data/events.json deleted file mode 100644 index 9778e48..0000000 --- a/apps/landing/src/data/events.json +++ /dev/null @@ -1,46 +0,0 @@ -[ - { - "name": "IMPHNEN X GDG Medan", - "description": "Join us for the Google Cloud Roadshow: Build with AI in Medan! This is your chance to dive deep into the latest advancements in cloud technology and AI, guided by industry experts.", - "start_date": "2025-04-26T13:00:00+07:00", - "end_date": "2025-04-26T17:00:00+07:00", - "thumbnail": "https://scontent.fsoc1-2.fna.fbcdn.net/v/t39.30808-6/492363939_2132158393873766_2304913620264481070_n.jpg?_nc_cat=105&ccb=1-7&_nc_sid=127cfc&_nc_eui2=AeFe43oCdy2FOIVI6pr_grkO_q5XyVVxt5v-rlfJVXG3m9KgINIp9lo_vzXP4CtAAFJviDNJdDPaPyGnz7B8-gfE&_nc_ohc=bv3GExoL4pUQ7kNvwEpV7xQ&_nc_oc=Adk45dIpzgFxCHgtQKDTo99Sewj0ZMO4LX1cNEI2Dj7VeQDp8jd1RzYnG_UwsKkaTug&_nc_zt=23&_nc_ht=scontent.fsoc1-2.fna&_nc_gid=ZPKg-Y9FLRXcDiSak5WcLA&oh=00_AfL7BFg5g-q6MALMRFUlgaUWK8jHxQVTHdZ_1ezxBthXfw&oe=68354615", - "type": "onsite", - "price": 0, - "location": "Magnificient, Medan", - "detail_link": "https://n8n.gdgmedan.com/form/8466f60d-94b0-42a6-93aa-8738ae4ab5df?fbclid=IwY2xjawKcQoNleHRuA2FlbQIxMABicmlkETFnQ1RvSU5aWVhpWldaOXBjAR7LUWa_npCLb-QSfk1QFTl12tyw_eBhNO8oI8N2LvhwweeE5_41uQsARsEQGw_aem_1IP2vqrQcvjsvLCBy2C6Hg" - }, - { - "name": "IMPHNEN X Connect Citcom", - "description": "Unleash the Future with AI: Decode the potential of artificial intelligence to revolutionize your strategies, unchain innovation, and gain a powerful competitive edge in today's fast-evolving business landscape. Harness the transformative capabilities of AI to outsmart competitors, optimize operations, and drive sustainable growth.", - "start_date": "2025-04-22T13:00:00+07:00", - "end_date": "2025-04-22T17:00:00+07:00", - "thumbnail": "https://scontent.fsoc1-2.fna.fbcdn.net/v/t39.30808-6/490432625_2122941998128739_5372979458124718061_n.jpg?stp=dst-jpg_p526x296_tt6&_nc_cat=105&ccb=1-7&_nc_sid=aa7b47&_nc_eui2=AeGc_W_0Gd3XY9X9ImKz1h1HpYKlinPYX-qlgqWKc9hf6k75HObQiJ8A8m6BZOIe2OrVQjjyXnHq08Rc4JuQWeCQ&_nc_ohc=uoSae0n37k0Q7kNvwHch8E5&_nc_oc=AdngSKtrCdZ4H3hlHh-9cttOnaKyrwiFEcm88kjp0gaflvQ65LUp4OT45-XVGDAoIo8&_nc_zt=23&_nc_ht=scontent.fsoc1-2.fna&_nc_gid=YGkoLGAxNuB0uF3C9hSYGQ&oh=00_AfLRkrzG7DRYa3HRuy9aXfUfG6zLkRBDj9LxqXKOfF9x7Q&oe=683546E0", - "type": "onsite", - "price": 0, - "location": "El Hotel, Bandung", - "detail_link": "/events/1" - }, - { - "name": "JVM Meetup #64", - "description": "Join us for an insightful talk where we’ll explore how AI is reshaping the payment industry. Whether you’re into tech, AI, or just curious about the future of transactions, this is a must-attend event!", - "start_date": "2025-04-30T13:00:00+07:00", - "end_date": "2025-04-30T17:00:00+07:00", - "thumbnail": "https://scontent.fsoc1-1.fna.fbcdn.net/v/t39.30808-6/494158983_2136830150073257_5259003432367969457_n.jpg?_nc_cat=100&ccb=1-7&_nc_sid=833d8c&_nc_eui2=AeGcneO8ihnXyQ_7WV93z7Onz0UPVTr-bf_PRQ9VOv5t_wI-el8xouZ-Z4TKGMMlBPi21d9AA55SP54MHkzO4rx1&_nc_ohc=3Q9NKGIkpT4Q7kNvwHKzwXr&_nc_oc=AdkRJ--zwRGcfX_F2eIgx0qhK-N3OkhwqRwujFhgSRWy5mH_SdIyBmwW8PHHXp71ZUM&_nc_zt=23&_nc_ht=scontent.fsoc1-1.fna&_nc_gid=Xi30SjzSBAU1XN9rRxp0VA&oh=00_AfKvjd9LTMfOwfPLD43U8lKYEr4KABecj336J8hAY8ekMg&oe=68353105", - "type": "onsite", - "price": 0, - "location": "El Hotel, Bandung", - "detail_link": "https://lu.ma/422jkgz0?utm_campaign=jvmmeetup&utm_medium=social%2C%20event%2C%20meetup&utm_source=google%2C%20facebook%2C%20linkedin%2C%20instagram" - }, - { - "name": "JVM Meetup #65", - "description": "Di era digital yang terus berkembang, Artificial Intelligence (AI) bukan lagi teknologi masa depantapi alat bantu masa kini yang siap mendongkrak efisiensi, kreativitas, dan produktivitas kita semua, terutama para penggiat IT dan pelaku usaha!", - "start_date": "2025-05-15T13:00:00+07:00", - "end_date": "2025-05-15T17:00:00+07:00", - "thumbnail": "https://scontent.fsoc1-1.fna.fbcdn.net/v/t39.30808-6/495382435_2144684952621110_8304851620615091109_n.jpg?_nc_cat=104&ccb=1-7&_nc_sid=833d8c&_nc_eui2=AeHwnU4KixfUsslo3M90ahT8GwMEI7zWSnQbAwQjvNZKdP3RHiUjdDZtuF1dIMvorL0nW0rDGOD6tPDi0i9ERK96&_nc_ohc=R1NyXnu-FKEQ7kNvwGbO-iN&_nc_oc=Adluf-uw1tmzkNME9vNhwkTv5-x_GHMfsLj1bzGyYds_ODq3UnWMq2DthyUisF9e3gU&_nc_zt=23&_nc_ht=scontent.fsoc1-1.fna&_nc_gid=kykMk9nPtcrtk-InL6tF6w&oh=00_AfLZSrEPpV83lVFRMz19n9RzfnsM4o7K7TijpdAjWYViiA&oe=6835231B", - "type": "onsite", - "price": 0, - "location": "Telkom Landmark Tower (Lt.31)", - "detail_link": "s.id/jvm65" - } -] diff --git a/apps/landing/src/data/hackathons.json b/apps/landing/src/data/hackathons.json deleted file mode 100644 index 0a68626..0000000 --- a/apps/landing/src/data/hackathons.json +++ /dev/null @@ -1,178 +0,0 @@ -[ - { - "team_name": "Lineproject", - "project_title": "LaporMerdeka", - "description": "Platform pelaporan infrastruktur publik Indonesia yang memungkinkan warga melaporkan masalah dengan cepat dan mudah untuk Indonesia yang lebih baik.", - "repo_link": "https://github.com/MANFIT7/lapormerdeka", - "screenshot": "https://drive.google.com/open?id=1tbOJKacQGsfldr5TsWtzNKL65iCpADz2", - "file_name": "Screenshot 2025-08-22 062036 - Fafnir.png" - }, - { - "team_name": "Aliansi switch", - "project_title": "News-ai", - "description": "ai agent untuk memilah beritah hoax dengan asli", - "repo_link": "https://github.com/7FIl/News-AI", - "screenshot": "https://drive.google.com/open?id=1IYoOB1zqL70tpxaeopdS6VtuL8hoqpPn", - "file_name": "Screenshot 2025-08-22 223626 - 7Fil.png" - }, - { - "team_name": "Sodev Sedap", - "project_title": "Sejarah Alternatif ID", - "description": "Website AI Agent yang dapat memberikan user pov bagaimana jika user ada di situasi tersebut menggunakan reka adegan dengan pendekatan teks dengan gaya novel", - "repo_link": "https://github.com/rizalkr/sejarah-alternatif-id/tree/main", - "screenshot": "https://drive.google.com/open?id=1TMUgayvtI45gU79I-0SokQnPJP6LHQaC", - "file_name": "Screenshot 2025-08-23 115137 - Rizal Kurnia.png" - }, - { - "team_name": "Muhammad Harafsan Alhad", - "project_title": "Elysia AI Kemerdekaan Indonesia", - "description": "“Sebuah chatbot AI interaktif yang menampilkan Elysia (dari Honkai Impact) yang menjawab pertanyaan tentang Kemerdekaan Indonesia dengan gaya khas Elysia, lengkap dengan fitur kuis interaktif.", - "repo_link": "https://github.com/rafsanalhad/elysia-ai-kemerdekaan", - "screenshot": "https://drive.google.com/open?id=1_QE59lgHNbJfuVikJ5KT0_85tbJc6pMo", - "file_name": "Screenshot 2025-08-23 131756 - Ralhad Alhad.png" - }, - { - "team_name": "RaflanGT", - "project_title": "Ecobot", - "description": "EcoBot adalah AI Agent yang hadir untuk menjawab tantangan pengelolaan sampah dan keterbatasan digitalisasi di masyarakat. Melalui WhatsApp yang akrab bagi warga, EcoBot memandu pemilahan sampah dengan analisis gambar berbasis AI sekaligus menumbuhkan kesadaran lingkungan. Kemerdekaan bukan hanya bebas dari penjajahan, tetapi juga kesadaran kolektif untuk mengelola hal-hal sederhana yang berdampak besar. Dengan langkah kecil seperti ini, desa dan masyarakat dapat mandiri secara digital, menjaga lingkungan, dan bersama-sama membawa Indonesia terus maju.", - "repo_link": "https://github.com/mycoderisyad/raflangt-ecobot", - "screenshot": "https://drive.google.com/open?id=1lj5DMJfxSrCwyNSLIlq-GQohJHCUDU1-", - "file_name": "Screenshot 2025-08-23 223413 - MRisyad Raflan.png" - }, - { - "team_name": "Tchh Tidak Akan", - "project_title": "Merdeka Quiziz", - "description": "Merdeka Quiziz merupakan web kuis yang menggunakan tema Kemerdekaan Indonesia dengan fitur gamifikasi yang membuat kuis menjadi menyenangkan, dimana setiap kuis dibuat oleh Mera (AI) dan dipersonalisasi untuk pengguna. Selain itu di Merdeka Quiziz pengguna juga dapat membahas sejarah Indonesia bersama Mera (AI).", - "repo_link": "https://gitlab.com/personal-projects9094234/merdeka-quiziz", - "screenshot": "https://drive.google.com/open?id=1U_UMaYABLjbO38GA9DopXebDWznFKQcI", - "file_name": "Screenshot 2025-08-24 at 09.15.06 - Khen Cahyo.png" - }, - { - "team_name": "Pengen Ikut tapi Bingung Mau Buat Apa", - "project_title": "IMMPHNEN (Ingin Menjadi Mesin Pencari Handal Namun Enggan Ngecrawl)", - "description": "Mesin pencari yang didesain untuk memerdekakan para pencari informasi dari tracker-tracker yang berlebihan (lelah bukan habis mencari A, nongol iklan A dimana-mana?). Memiliki fitur ringkasan pencarian, serta filter negatif penelusuran (judi & pornografi). Dibuat dengan LangSearch dan Lunos(ChatGPT 5.0).", - "repo_link": "https://gitlab.com/myracledev/py-search-engine", - "screenshot": "https://drive.google.com/open?id=1xNFtvGSLfWwdA4Mx46S7bx2nmwpt5CXA", - "file_name": "{CBBB6849-BC8E-4435-9C6A-8C88C83287DF} - Mohamad Yusuf Rizaldi.png" - }, - { - "team_name": "Ayam Geprek", - "project_title": "SURA AI (Suara Rakyat)", - "description": "SIngkatnya ini itu AI yang jadi mewakili hati rakyat Indonesia (bukan dpr). Dia bukan sekadar asisten digital, kenapa? ya karena dia kritis, cerdas, dan punya selera sinis yang bikin narasi kekuasaan gampang dibongkar. Gayanya penuh satir, dan sering pakai perumpamaan yang sangat panas. Sura AI hadir untuk menantang pemikiran, membakar semangat, dan memberikan perspektif yang ngga takut ngomong jujur tentang realita sosial dan politik.", - "repo_link": "https://github.com/Roti18/sura-ai", - "screenshot": "https://drive.google.com/open?id=1uUWGu08NimQ1qV9E-xu0h39HyoAYrclS", - "file_name": "Screenshot 2025-08-24 204538 - Roti 1.png" - }, - { - "team_name": "Fae", - "project_title": "Daily Commit", - "description": "Daily Commit adalah semacam alarm commit yang bakal ngingetin kamu kalau seharian nggak ada commit di GitHub. Tapi kalau rajin, dia juga bisa jadi cheerleader digital yang muji-muji kamu.", - "repo_link": "https://github.com/far-id/send-mail-mailry.git", - "screenshot": "https://drive.google.com/open?id=1GAvN4RxW_gAvOfew7LbbHANEx2F3lC5y", - "file_name": "GITHUB~1.PNG" - }, - { - "team_name": "garudaStack", - "project_title": "Tani AI", - "description": "Tani AI adalah AI agent andalan anda untuk membantu dalam perkembangan, produktifitas serta analisis untuk komoditas pertanian anda.", - "repo_link": "FE : https://github.com/Jazaniest/garuda-ai-frontend.git BE : https://github.com/Rifaldy1292/be-hackaton.git", - "screenshot": "https://drive.google.com/open?id=173dS3v9sAJXMOxs7uY_SG-TUW9wIo_WM", - "file_name": "Tani AI - M Abdillah Aljazani.png" - }, - { - "team_name": "Roki Miftah Kamaludin", - "project_title": "Mengenang Pahlawan", - "description": "Mengenang Pahlawan adalah platform digital untuk mengenang dan mempelajari kisah pahlawan nasional Indonesia. Aplikasi ini menyajikan biografi, foto, serta informasi resmi terkait penetapan gelar pahlawan.\n\nSelain sebagai ensiklopedia digital, platform ini juga dilengkapi fitur interaktif seperti kuis edukatif, pencarian, dan poin penghargaan.", - "repo_link": "https://github.com/rokimiftah/mengenang-pahlawan", - "screenshot": "https://drive.google.com/open?id=140c-FyndYCAOCtKChbRaENc9fgWvQwU2", - "file_name": "mengenang-pahlawan - Roki Miftah Kamaludin.png" - }, - { - "team_name": "LokerHunter", - "project_title": "LokerKerja", - "description": "Sebuah platform job matching yang memanfaatkan analisis CV atau portofolio untuk mengidentifikasi keahlian utama pengguna dan melakukan inferensi otomatis terhadap posisi pekerjaan yang paling sesuai.\n\nHasil analisis ini digunakan untuk memberikan rekomendasi daftar lowongan yang relevan dengan profil keterampilan pengguna. Selain itu, pengguna dapat berlangganan newsletter agar selalu mendapatkan informasi lowongan terbaru yang sesuai dengan hasil analisis CV mereka, yang kemudian akan dikirimkan langsung melalui email.\n\nMapping ke Sponsor\nUNLI = Digunakan untuk vision & reasoning engine dalam analisis CV/portofolio (misalnya parsing teks dari PDF/gambar, lalu inferensi posisi kerja yang cocok).\nLunos = Digunakan untuk parsing terstruktur (PDF ke JSON), normalisasi data, dan orkestrasi pipeline analisis.\nMailry = Digunakan untuk layanan email newsletter, agar pengguna bisa berlangganan update lowongan yang sesuai dengan profil keterampilannya.", - "repo_link": "https://github.com/iegl3/LokerKerja", - "screenshot": "https://drive.google.com/open?id=1qludNU5DnNPFtzowSnB_mYkPB00Ll4Rz", - "file_name": "demo - Eagle.png" - }, - { - "team_name": "Kami Gila Roblox", - "project_title": "Pitara: Pintu Sejarah Nusantara", - "description": "Pitara adalah platform yang bertujuan untuk meningkatkan literasi sejarah dan melawan hoaks di Indonesia. Platform ini menyediakan fitur chat AI untuk belajar sejarah, AI fact-checker untuk memverifikasi berita, forum diskusi, dan fitur pembuatan artikel otomatis. Pitara juga menjaga retensi pengguna melalui newsletter mingguan.", - "repo_link": "https://github.com/JackBerck/pitara", - "screenshot": "https://drive.google.com/open?id=1GIXZkRrRn21hpNMcip-QdGayLr8m2qCG", - "file_name": "screencapture-127-0-0-1-8000-2025-08-24-22_56_54 - Zaki Dzulfikar.png" - }, - { - "team_name": "Hidup Jokowi", - "project_title": "Historia", - "description": "Historia, sebuah platform revolusioner yang menjembatani masa lalu dengan masa kini. kami memanfaatkan kekuatan kecerdasan buatan (AI) untuk menganalisis dan memberikan narasi pada foto-foto dan dokumen bersejarah Indonesia. cukup unggah sebuah gambar, dan biarkan teknologi kami mengungkap cerita, tokoh, serta konteks di balik momen beku tersebut. mari jelajahi kembali perjuangan bangsa dengan cara yang belum pernah ada sebelumnya.", - "repo_link": "https://github.com/mybday123/historia", - "screenshot": "https://drive.google.com/open?id=1RaMiswa7Fy5m3V1xsoODvKabEwTspu2y", - "file_name": "Historia_-_Preview - Julian Mifta Yama Fauzan.png" - }, - { - "team_name": "CORTEZA FAMILY", - "project_title": "Garuda Shield - Criminal Website Detector", - "description": "Garuda Shield - Criminal Website Detector: Adalah Web analysis berbasis Crawling yang memanfaatkan AI Untuk mendeteksi anomali pada suatu web menggunakan: LunosTech, Mailry, Unli.Dev serta Crawler Tools", - "repo_link": "https://github.com/c0rt3z4/hackathon-imphnen", - "screenshot": "https://drive.google.com/open?id=1AKJ7pN7zUEAoJEcZFAxGVtSE582r2Hw5", - "file_name": "Capture - Calm.PNG" - }, - { - "team_name": "Oziral", - "project_title": "Kerja Merdeka - AI Agent Pendamping Pelamar Kerja", - "description": "Kerja Merdeka – AI Agent Pendamping Pelamar Kerja adalah platform berbasis kecerdasan buatan yang membantu pencari kerja menyusun CV dan Cover Letter yang relevan, berlatih interview secara interaktif, hingga mengirimkan lamaran dalam satu alur terpadu.", - "repo_link": "frontend : https://github.com/lakhatekno/imphnen-frontend, backend: https://github.com/Contsol-dev/kerja-merdeka-be", - "screenshot": "https://drive.google.com/open?id=1l7IOCTj1tRJTtFgq0Wq8CJ8NaDYZ-DS1", - "file_name": "Screenshot 2025-08-24 230728 - Muhammad Iqbal Ghozy.png" - }, - { - "team_name": "ak mw heketon", - "project_title": "MerdekAI", - "description": "Kita sedang mengembangkan sebuah chatbot AI versi low budget yang tetap powerful dan fungsional. Meskipun budget pembuatan murah bahkan gratis dibanding ChatGPT, fitur-fiturnya gak kalah lengkap. Chatbot ini mendukung:\n\nChat Completion (percakapan interaktif seperti ChatGPT)\n\nText-to-Voice (mengubah teks menjadi suara)\n\nImage Generation (membuat gambar dari prompt)\n\nImage Recognition (mengidentifikasi dan mendeskripsikan gambar)\n\nJadi, meskipun gak ada dana keluar, project ini dirancang supaya tetap memberikan pengalaman mirip ChatGPT dengan fitur-fitur AI kekinian ygy.", - "repo_link": "https://github.com/kevinalvarel/merdekai", - "screenshot": "https://drive.google.com/open?id=1U24L8_4c2nM088olI7V8LaqUGcOfg1YH", - "file_name": "merdekai.my.id_ - Muhammad Kevin Alvarel.png" - }, - { - "team_name": "Er Project", - "project_title": "Agentic Merdeka", - "description": "Multi-modal AI Chat interface, dengan kombinasi beberapa capability. Diantaranya:\n\nConversation, Image Analisis, Generate Embeddings Vector, Generate voice, Dan yang terakhir Generate Gambar, bisa build character ai sendiri, select persona dll\n\nFramework:\nNextjs 15+ (app router)\n\nDatabseses:\nFirebase untuk penyimpanan chat history dan login\n\nDilengkapi proteksi CSRF, Next Middleware dan Authentikasi menggunakan mailry\n\nSEMUA ITU DAPAT DI AKSES melalui satu web interface. Ini sudah malas, JANGAN ANGGAP PROYEK INI RAJIN🗿", - "repo_link": "https://github.com/ErRickow/ai-agent-hackathon", - "screenshot": "https://drive.google.com/open?id=1EkVWezUIXc_F_9IeM3LeX47TFDl2j2Va", - "file_name": "download - Er Rickow.png" - }, - { - "team_name": "NamamuCore", - "project_title": "Namamu - Startup Name Generator", - "description": "Namamu.web.id merupakan situs generator nama sederhana yang memudahkan brainstorming ide platform, dengan tambahan fitur pengiriman hasil ke email.", - "repo_link": "https://github.com/nooradn/namamu-name-gen", - "screenshot": "https://drive.google.com/open?id=1EW6wBuujpHkhT1tW-_j6TklSgqvZt7wa", - "file_name": "preview - Noor Adn.png" - }, - { - "team_name": "Tim GakTau.Dev", - "project_title": "Quiz Kemerdekaan", - "description": "Sebuah aplikasi kuis interaktif berbasis AI untuk membantu pelajar dan penggemar sejarah Indonesia memahami peristiwa kemerdekaan dengan cara yang menyenangkan", - "repo_link": "https://github.com/RAYDENFLY/Quiz-Merdeka/tree/main", - "screenshot": "https://drive.google.com/open?id=1c0A6ijx_pNCmh87g_EvlyUAAV7zAT8Li", - "file_name": "Gambar WhatsApp 2025-08-24 pukul 21.36.26_53cb7a14 - RAYDENFLY.jpg" - }, - { - "team_name": "Icikiwir semilir", - "project_title": "Chef AI", - "description": "chat bot untuk mendapatkan resep dari AI", - "repo_link": "https://github.com/ranggacey/chef", - "screenshot": "https://drive.google.com/open?id=1JCz7pEfM_nF--ZsAZvQSlUonJCjTZkh6", - "file_name": "Screenshot 2025-08-24 235059 - Diablo volfir.png" - }, - { - "team_name": "Greatvitech Team", - "project_title": "Patriotisme Quiz", - "description": "Sebuah aplikasi quiz bertema patriotisme, pengguna bisa menjawab soal - soal yang berkaitan dengan patriotisme, serta soal digenerate langsung oleh ai", - "repo_link": "frontend: https://github.com/farhanangwa12/patriot-frontend backend: https://github.com/farhanangwa12/patriot-backend", - "screenshot": "https://drive.google.com/open?id=1IAKk_ShPo51_CmqaClgv1ulXKrlf2fHA", - "file_name": "Screenshot 2025-08-24 221129 - farhan hokado.png" - } -] diff --git a/apps/landing/src/data/hero-stats.json b/apps/landing/src/data/hero-stats.json index 1455173..b65dca7 100644 --- a/apps/landing/src/data/hero-stats.json +++ b/apps/landing/src/data/hero-stats.json @@ -1,5 +1,5 @@ [ { "value": "250K+", "label": "Member" }, - { "value": "500+", "label": "Meme Harian" }, - { "value": "24/7", "label": "Yapping" } + { "value": "7", "label": "Platform" }, + { "value": "24/7", "label": "Community" } ] diff --git a/apps/landing/src/data/testimonials.json b/apps/landing/src/data/testimonials.json deleted file mode 100644 index a587c16..0000000 --- a/apps/landing/src/data/testimonials.json +++ /dev/null @@ -1,44 +0,0 @@ -[ - { - "id": 1, - "name": "Andi Pratama", - "role": "Backend Developer", - "text": "Komunitas ini sangat membantu perkembangan karir saya. Saya bisa belajar teknologi terbaru dan berkolaborasi dengan developer lain.", - "image": "https://picsum.photos/100/100?random=1" - }, - { - "id": 2, - "name": "Sarah Wijaya", - "role": "Frontend Engineer", - "text": "Acara sharing session-nya sangat inspiratif. Saya jadi termotivasi untuk terus mengembangkan skill di bidang frontend development.", - "image": "https://picsum.photos/100/100?random=2" - }, - { - "id": 3, - "name": "Rizal Fauzi", - "role": "Fullstack Developer", - "text": "Bergabung di komunitas ini membuka banyak kesempatan networking dan project menarik. Sangat recommended untuk developer semua level!", - "image": "https://picsum.photos/100/100?random=5" - }, - { - "id": 4, - "name": "Dewi Lestari", - "role": "Mobile Developer", - "text": "Materi workshop-nya praktis dan langsung applicable. Mentor-mentornya juga berpengalaman di industri.", - "image": "https://picsum.photos/100/100?random=9" - }, - { - "id": 5, - "name": "Fajar Setiawan", - "role": "DevOps Engineer", - "text": "Komunitas yang solid dan saling support. Tidak pernah ragu untuk bertanya karena semua anggota sangat responsif.", - "image": "https://picsum.photos/100/100?random=10" - }, - { - "id": 6, - "name": "Budi Santoso", - "role": "UI/UX Designer", - "text": "Kolaborasi antara designer dan developer di komunitas ini sangat smooth. Banyak belajar best practices untuk workflow yang lebih baik.", - "image": "https://picsum.photos/100/100?random=6" - } -] diff --git a/package-lock.json b/package-lock.json index f4d93ef..a5447e1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -206,6 +206,7 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -2050,6 +2051,7 @@ "integrity": "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" @@ -2061,6 +2063,7 @@ "integrity": "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "tslib": "^2.4.0" } @@ -4811,6 +4814,7 @@ "integrity": "sha512-LSd2qWA1y4eyWoE/WbzF10MUtat0OBXaepjH555NqlOxmFevC7cImWvPQTJ9x5k4kkL0sR9Wwdy8hZ3xp151WA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@module-federation/runtime": "2.3.0", "@module-federation/webpack-bundler-runtime": "2.3.0" @@ -6997,6 +7001,7 @@ "integrity": "sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "playwright": "1.58.2" }, @@ -8355,6 +8360,7 @@ "integrity": "sha512-FolcIAH5FW4J2FET+qwjd1kNeFbCkd0VLuIHO0thyolEjaPSxw5qxG67DA7BZGm6PVcoiSgPLks1DL6eZ8c+fA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@module-federation/runtime-tools": "0.21.6", "@rspack/binding": "1.6.8", @@ -8476,6 +8482,7 @@ "integrity": "sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "json-schema-traverse": "^1.0.0", @@ -8857,6 +8864,7 @@ "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/core": "^7.21.3", "@svgr/babel-preset": "8.1.0", @@ -9124,6 +9132,7 @@ "dev": true, "hasInstallScript": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "@swc/counter": "^0.1.3", "@swc/types": "^0.1.25" @@ -9375,6 +9384,7 @@ "integrity": "sha512-2egEBHUMasdypIzrprsu8g+OEVd7Vp2MM3a2eVlM/cyFYto0nGz5BX5BTgh/ShZZI9ed+ozEq+Ngt+rgmUs8tw==", "dev": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "tslib": "^2.8.0" } @@ -9385,6 +9395,7 @@ "integrity": "sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==", "dev": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "@swc/counter": "^0.1.3" } @@ -9688,6 +9699,7 @@ "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.95.2.tgz", "integrity": "sha512-/wGkvLj/st5Ud1Q76KF1uFxScV7WeqN1slQx5280ycwAyYkIPGaRZAEgHxe3bjirSd5Zpwkj6zNcR4cqYni/ZA==", "license": "MIT", + "peer": true, "dependencies": { "@tanstack/query-core": "5.95.2" }, @@ -9766,6 +9778,7 @@ "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", @@ -9944,6 +9957,7 @@ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", @@ -10112,6 +10126,7 @@ "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "*", "@types/json-schema": "*" @@ -10256,6 +10271,7 @@ "integrity": "sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -10297,6 +10313,7 @@ "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -10307,6 +10324,7 @@ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "devOptional": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -10463,6 +10481,7 @@ "integrity": "sha512-rLoGZIf9afaRBYsPUMtvkDWykwXwUPL60HebR4JgTI8mxfFe2cQTu3AGitANp4b9B2QlVru6WzjgB2IzJKiCSA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.58.0", "@typescript-eslint/types": "8.58.0", @@ -11168,6 +11187,7 @@ "integrity": "sha512-/irhyeAcKS2u6Zokagf9tqZJ0t8S6kMZq4ZG9BHZv7I+fkRrYfQX4w7geYeC2r6obThz39PDxvXQzZX+qXqGeg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vitest/utils": "4.1.2", "fflate": "^0.8.2", @@ -11712,6 +11732,7 @@ "integrity": "sha512-nrUSn7hzt7J6JWgWGz78ZYI8wj+gdIJdk0Ynjpp8l+trkn58Uqsf6RYrYkEK+3X18EX+TNdtJI0WxAtc+L84SQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "argparse": "^2.0.1" }, @@ -11747,6 +11768,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -11829,6 +11851,7 @@ "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -12946,6 +12969,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -13249,24 +13273,6 @@ "dev": true, "license": "MIT" }, - "node_modules/chokidar": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", - "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "readdirp": "^5.0.0" - }, - "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/chrome-trace-event": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", @@ -14317,18 +14323,6 @@ "dev": true, "license": "BSD-2-Clause" }, - "node_modules/data-uri-to-buffer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", - "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">= 12" - } - }, "node_modules/data-urls": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-4.0.0.tgz", @@ -14946,6 +14940,7 @@ "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "iconv-lite": "^0.6.2" } @@ -15292,6 +15287,7 @@ "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -15412,6 +15408,7 @@ "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "dev": true, "license": "MIT", + "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -15578,6 +15575,7 @@ "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -15594,6 +15592,7 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -16260,32 +16259,6 @@ } } }, - "node_modules/fetch-blob": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", - "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "paypal", - "url": "https://paypal.me/jimmywarting" - } - ], - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "node-domexception": "^1.0.0", - "web-streams-polyfill": "^3.0.3" - }, - "engines": { - "node": "^12.20 || >= 14.13" - } - }, "node_modules/fflate": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", @@ -16776,21 +16749,6 @@ "node": ">= 14.17" } }, - "node_modules/formdata-polyfill": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", - "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "fetch-blob": "^3.1.2" - }, - "engines": { - "node": ">=12.20.0" - } - }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -19199,6 +19157,7 @@ "integrity": "sha512-j1n1IuTX1VQjIy3tT7cyGbX7nvQOsFLoIqobZv4ttI5axP923gA44zUj6miiA6R5Aoms4sEGVIIcucXUbRI14g==", "dev": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "copy-anything": "^2.0.1", "parse-node-version": "^1.0.1", @@ -20408,50 +20367,6 @@ "license": "MIT", "optional": true }, - "node_modules/node-domexception": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", - "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", - "deprecated": "Use your platform's native DOMException instead", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "github", - "url": "https://paypal.me/jimmywarting" - } - ], - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=10.5.0" - } - }, - "node_modules/node-fetch": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", - "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.4", - "formdata-polyfill": "^4.0.10" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/node-fetch" - } - }, "node_modules/node-mock-http": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/node-mock-http/-/node-mock-http-1.0.4.tgz", @@ -20544,6 +20459,7 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "dependencies": { "@ltd/j-toml": "^1.38.0", "@napi-rs/wasm-runtime": "0.2.4", @@ -20925,6 +20841,7 @@ "resolved": "https://registry.npmjs.org/openapi-fetch/-/openapi-fetch-0.17.0.tgz", "integrity": "sha512-PsbZR1wAPcG91eEthKhN+Zn92FMHxv+/faECIwjXdxfTODGSGegYv0sc1Olz+HYPvKOuoXfp+0pA2XVt2cI0Ig==", "license": "MIT", + "peer": true, "dependencies": { "openapi-typescript-helpers": "^0.1.0" } @@ -21623,6 +21540,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -22788,6 +22706,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -22797,6 +22716,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -22809,6 +22729,7 @@ "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.72.0.tgz", "integrity": "sha512-V4v6jubaf6JAurEaVnT9aUPKFbNtDgohj5CIgVGyPHvT9wRx5OZHVjz31GsxnPNI278XMu+ruFz+wGOscHaLKw==", "license": "MIT", + "peer": true, "engines": { "node": ">=18.0.0" }, @@ -22833,13 +22754,15 @@ "version": "19.2.4", "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.4.tgz", "integrity": "sha512-W+EWGn2v0ApPKgKKCy/7s7WHXkboGcsrXE+2joLyVxkbyVQfO3MUEaUQDHoSmb8TFFrSKYa9mw64WZHNHSDzYA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/react-redux": { "version": "9.2.0", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", "license": "MIT", + "peer": true, "dependencies": { "@types/use-sync-external-store": "^0.0.6", "use-sync-external-store": "^1.4.0" @@ -23013,22 +22936,6 @@ "node": ">= 6" } }, - "node_modules/readdirp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", - "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/recharts": { "version": "3.8.1", "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.8.1.tgz", @@ -23083,7 +22990,8 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/redux-thunk": { "version": "3.1.0", @@ -23390,6 +23298,7 @@ "integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -23765,6 +23674,7 @@ "integrity": "sha512-N+7WK20/wOr7CzA2snJcUSSNTCzeCGUTFY3OgeQP3mZ1aj9NMQ0mSTXwlrnd89j33zzQJGqIN52GIOmYrfq46A==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "chokidar": "^4.0.0", "immutable": "^5.0.2", @@ -23786,6 +23696,7 @@ "integrity": "sha512-+VUy01yfDqNmIVMd/LLKl2TTtY0ovZN0rTonh+FhKr65mFwIYgU9WzgIZKS7U9/SPCQvWTsTGx9jyt+qRm/XFw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@bufbuild/protobuf": "^2.5.0", "buffer-builder": "^0.2.0", @@ -24379,6 +24290,7 @@ "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -26243,7 +26155,8 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" + "license": "0BSD", + "peer": true }, "node_modules/tsyringe": { "version": "4.10.0", @@ -26406,6 +26319,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -26854,6 +26768,7 @@ "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -26969,6 +26884,7 @@ "integrity": "sha512-xjR1dMTVHlFLh98JE3i/f/WePqJsah4A0FK9cc8Ehp9Udk0AZk6ccpIZhh1qJ/yxVWRZ+Q54ocnD8TXmkhspGg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vitest/expect": "4.1.2", "@vitest/mocker": "4.1.2", @@ -27132,18 +27048,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/web-streams-polyfill": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", - "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">= 8" - } - }, "node_modules/webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", @@ -27160,6 +27064,7 @@ "integrity": "sha512-HU1JOuV1OavsZ+mfigY0j8d1TgQgbZ6M+J75zDkpEAwYeXjWSqrGJtgnPblJjd/mAyTNQ7ygw0MiKOn6etz8yw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", @@ -27864,6 +27769,7 @@ "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=10.0.0" }, @@ -28050,6 +27956,7 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" }