diff --git a/apps/app-portal/src/app/(admin)/admin/page.tsx b/apps/app-portal/src/app/(admin)/admin/page.tsx new file mode 100644 index 00000000..7a10e74e --- /dev/null +++ b/apps/app-portal/src/app/(admin)/admin/page.tsx @@ -0,0 +1,34 @@ +import React from "react"; +const tiles = [ + { + title: "Settings", + }, + { + title: "Applicants", + }, + { + title: "Stats", + }, +]; + +export default function AdminPage() { + return ( +
+
+

Admin Dashboard

+ +

Welcome to the admin portal.

+
+ +
+ {tiles.map((t) => ( +
+

{t.title}

+ +
Open →
+
+ ))} +
+
+ ); +} diff --git a/apps/app-portal/src/app/(admin)/admin/settings/page.tsx b/apps/app-portal/src/app/(admin)/admin/settings/page.tsx new file mode 100644 index 00000000..b85845c5 --- /dev/null +++ b/apps/app-portal/src/app/(admin)/admin/settings/page.tsx @@ -0,0 +1,14 @@ +import React from "react"; +import ShowDecisionToggle from "@/components/admin/ShowDecisionToggle"; +import DateControls from "@/components/admin/DateControl"; +import FormConfigEditor from "@/components/admin/FormConfigEditor"; + +export default function Page() { + return ( +
+ + + +
+ ); +} diff --git a/apps/app-portal/src/app/(admin)/layout.tsx b/apps/app-portal/src/app/(admin)/layout.tsx new file mode 100644 index 00000000..ebb71369 --- /dev/null +++ b/apps/app-portal/src/app/(admin)/layout.tsx @@ -0,0 +1,25 @@ +import React from "react"; +import Link from "next/link"; + +export default function AdminLayout() { + return ( +
+ + +
+
+

Admin Portal

+
+
+
+ ); +} diff --git a/apps/app-portal/src/app/api/v1/admin/form-config/route.ts b/apps/app-portal/src/app/api/v1/admin/form-config/route.ts new file mode 100644 index 00000000..dd7eef65 --- /dev/null +++ b/apps/app-portal/src/app/api/v1/admin/form-config/route.ts @@ -0,0 +1,59 @@ +import { NextResponse } from "next/server"; + +type QuestionType = "text" | "textarea"; + +type Question = { + id: string; + label: string; + type: QuestionType; +}; + +type Section = { + id: string; + title: string; + questions: Question[]; +}; + +type FormConfig = { + sections: Section[]; +}; + +const mockFormConfig: FormConfig = { + sections: [ + { + id: "basic", + title: "Basic Info", + questions: [ + { + id: "name", + label: "Full Name", + type: "text", + }, + { + id: "reason", + label: "Why are you applying?", + type: "textarea", + }, + ], + }, + { + id: "experience", + title: "Experience", + questions: [ + { + id: "background", + label: "Tell us about your background", + type: "textarea", + }, + ], + }, + ], +}; + +export async function GET() { + return NextResponse.json(mockFormConfig); +} + +export async function POST() { + return NextResponse.json({ message: "Not implemented" }, { status: 501 }); +} diff --git a/apps/app-portal/src/app/api/v1/dates/confirm-by/route.ts b/apps/app-portal/src/app/api/v1/dates/confirm-by/route.ts new file mode 100644 index 00000000..ecf3dac2 --- /dev/null +++ b/apps/app-portal/src/app/api/v1/dates/confirm-by/route.ts @@ -0,0 +1,27 @@ +import { NextResponse } from "next/server"; +import { SingletonKey } from "@/lib/admin/singleton-keys"; + +type SingletonResponse = { + key: SingletonKey; + value: string; +}; + +export async function GET() { + const data: SingletonResponse = { + key: "confirm-by", + value: "2026-06-15T23:59:00Z", + }; + + return NextResponse.json(data); +} + +export async function POST() { + return NextResponse.json( + { + message: "Not implemented", + }, + { + status: 501, + }, + ); +} diff --git a/apps/app-portal/src/app/api/v1/dates/registration-closed/route.ts b/apps/app-portal/src/app/api/v1/dates/registration-closed/route.ts new file mode 100644 index 00000000..0f88631e --- /dev/null +++ b/apps/app-portal/src/app/api/v1/dates/registration-closed/route.ts @@ -0,0 +1,27 @@ +import { NextResponse } from "next/server"; +import { SingletonKey } from "@/lib/admin/singleton-keys"; + +type SingletonResponse = { + key: SingletonKey; + value: string; +}; + +export async function GET() { + const data: SingletonResponse = { + key: "registration-closed", + value: "2026-06-15T23:59:00Z", + }; + + return NextResponse.json(data); +} + +export async function POST() { + return NextResponse.json( + { + message: "Not implemented", + }, + { + status: 501, + }, + ); +} diff --git a/apps/app-portal/src/app/api/v1/dates/registration-open/route.ts b/apps/app-portal/src/app/api/v1/dates/registration-open/route.ts new file mode 100644 index 00000000..72511a76 --- /dev/null +++ b/apps/app-portal/src/app/api/v1/dates/registration-open/route.ts @@ -0,0 +1,27 @@ +import { NextResponse } from "next/server"; +import { SingletonKey } from "@/lib/admin/singleton-keys"; + +type SingletonResponse = { + key: SingletonKey; + value: string; +}; + +export async function GET() { + const data: SingletonResponse = { + key: "registration-open", + value: "2026-06-15T23:59:00Z", + }; + + return NextResponse.json(data); +} + +export async function POST() { + return NextResponse.json( + { + message: "Not implemented", + }, + { + status: 501, + }, + ); +} diff --git a/apps/app-portal/src/app/api/v1/show-decision/route.ts b/apps/app-portal/src/app/api/v1/show-decision/route.ts new file mode 100644 index 00000000..769e7e1b --- /dev/null +++ b/apps/app-portal/src/app/api/v1/show-decision/route.ts @@ -0,0 +1,27 @@ +import { NextResponse } from "next/server"; +import { SingletonKey } from "@/lib/admin/singleton-keys"; + +type SingletonResponse = { + key: SingletonKey; + value: string; +}; + +export async function GET() { + const data: SingletonResponse = { + key: "show-decision", + value: "2026-06-15T23:59:00Z", + }; + + return NextResponse.json(data); +} + +export async function POST() { + return NextResponse.json( + { + message: "Not implemented", + }, + { + status: 501, + }, + ); +} diff --git a/apps/app-portal/src/components/admin/AdminSidebar.tsx b/apps/app-portal/src/components/admin/AdminSidebar.tsx new file mode 100644 index 00000000..c5508f6c --- /dev/null +++ b/apps/app-portal/src/components/admin/AdminSidebar.tsx @@ -0,0 +1,5 @@ +import React from "react"; + +export default function AdminSidebar() { + return
AdminSidebar here
; +} diff --git a/apps/app-portal/src/components/admin/DateControl.tsx b/apps/app-portal/src/components/admin/DateControl.tsx new file mode 100644 index 00000000..0becc7a5 --- /dev/null +++ b/apps/app-portal/src/components/admin/DateControl.tsx @@ -0,0 +1,13 @@ +import React from "react"; + +export default function DateControls() { + return ( +
+

Application Deadline

+ + + + +
+ ); +} diff --git a/apps/app-portal/src/components/admin/FormConfigEditor.tsx b/apps/app-portal/src/components/admin/FormConfigEditor.tsx new file mode 100644 index 00000000..4f56b78d --- /dev/null +++ b/apps/app-portal/src/components/admin/FormConfigEditor.tsx @@ -0,0 +1,18 @@ +import React from "react"; + +export default function FormConfigEditor() { + return ( +
+
+

Form Questions

+ + +
+ +
+ Add Question here + +
+
+ ); +} diff --git a/apps/app-portal/src/components/admin/ShowDecisionToggle.tsx b/apps/app-portal/src/components/admin/ShowDecisionToggle.tsx new file mode 100644 index 00000000..7fe12bb1 --- /dev/null +++ b/apps/app-portal/src/components/admin/ShowDecisionToggle.tsx @@ -0,0 +1,22 @@ +"use client"; + +import { useState } from "react"; +import React from "react"; + +export default function ShowDecisionToggle() { + const [enabled, setEnabled] = useState(false); + + return ( +
+

Show Decision

+ +
+ ); +} diff --git a/apps/app-portal/src/components/ui/badge.tsx b/apps/app-portal/src/components/ui/badge.tsx index 70697bb4..18cc90c5 100644 --- a/apps/app-portal/src/components/ui/badge.tsx +++ b/apps/app-portal/src/components/ui/badge.tsx @@ -3,24 +3,31 @@ import { cva, type VariantProps } from "class-variance-authority"; import { cn } from "@/lib/utils"; -const badgeVariants = cva("inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors", { - variants: { - variant: { - default: "border-transparent bg-black text-white", - secondary: "border-transparent bg-neutral-100 text-neutral-900", - destructive: "border-transparent bg-red-600 text-white", - outline: "text-neutral-950", +const badgeVariants = cva( + "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors", + { + variants: { + variant: { + default: "border-transparent bg-black text-white", + secondary: "border-transparent bg-neutral-100 text-neutral-900", + destructive: "border-transparent bg-red-600 text-white", + outline: "text-neutral-950", + }, + }, + defaultVariants: { + variant: "default", }, }, - defaultVariants: { - variant: "default", - }, -}); +); -export interface BadgeProps extends React.HTMLAttributes, VariantProps {} +export interface BadgeProps + extends React.HTMLAttributes, + VariantProps {} function Badge({ className, variant, ...props }: BadgeProps) { - return
; + return ( +
+ ); } export { Badge, badgeVariants }; diff --git a/apps/app-portal/src/components/ui/button.tsx b/apps/app-portal/src/components/ui/button.tsx index f9e012f6..e83b1544 100644 --- a/apps/app-portal/src/components/ui/button.tsx +++ b/apps/app-portal/src/components/ui/button.tsx @@ -37,7 +37,13 @@ export interface ButtonProps const Button = React.forwardRef( ({ className, variant, size, asChild = false, ...props }, ref) => { const Comp = asChild ? Slot : "button"; - return ; + return ( + + ); }, ); Button.displayName = "Button"; diff --git a/apps/app-portal/src/components/ui/card.tsx b/apps/app-portal/src/components/ui/card.tsx index aabcebb2..dc5a24d4 100644 --- a/apps/app-portal/src/components/ui/card.tsx +++ b/apps/app-portal/src/components/ui/card.tsx @@ -2,34 +2,82 @@ import * as React from "react"; import { cn } from "@/lib/utils"; -const Card = React.forwardRef>(({ className, ...props }, ref) => ( -
+const Card = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
)); Card.displayName = "Card"; -const CardHeader = React.forwardRef>(({ className, ...props }, ref) => ( -
+const CardHeader = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
)); CardHeader.displayName = "CardHeader"; -const CardTitle = React.forwardRef>( - ({ className, ...props }, ref) =>

, -); +const CardTitle = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)); CardTitle.displayName = "CardTitle"; -const CardDescription = React.forwardRef>( - ({ className, ...props }, ref) =>

, -); +const CardDescription = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)); CardDescription.displayName = "CardDescription"; -const CardContent = React.forwardRef>(({ className, ...props }, ref) => ( +const CardContent = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => (

)); CardContent.displayName = "CardContent"; -const CardFooter = React.forwardRef>(({ className, ...props }, ref) => ( -
+const CardFooter = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
)); CardFooter.displayName = "CardFooter"; -export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }; +export { + Card, + CardHeader, + CardFooter, + CardTitle, + CardDescription, + CardContent, +}; diff --git a/apps/app-portal/src/components/ui/checkbox.tsx b/apps/app-portal/src/components/ui/checkbox.tsx index 6e2536e5..625c280a 100644 --- a/apps/app-portal/src/components/ui/checkbox.tsx +++ b/apps/app-portal/src/components/ui/checkbox.tsx @@ -16,7 +16,9 @@ const Checkbox = React.forwardRef< )} {...props} > - + diff --git a/apps/app-portal/src/components/ui/dialog.tsx b/apps/app-portal/src/components/ui/dialog.tsx index 9148a4a8..13f373a4 100644 --- a/apps/app-portal/src/components/ui/dialog.tsx +++ b/apps/app-portal/src/components/ui/dialog.tsx @@ -15,7 +15,10 @@ const DialogOverlay = React.forwardRef< >(({ className, ...props }, ref) => ( )); @@ -45,13 +48,31 @@ const DialogContent = React.forwardRef< )); DialogContent.displayName = DialogPrimitive.Content.displayName; -const DialogHeader = ({ className, ...props }: React.HTMLAttributes) => ( -
+const DialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
); DialogHeader.displayName = "DialogHeader"; -const DialogFooter = ({ className, ...props }: React.HTMLAttributes) => ( -
+const DialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
); DialogFooter.displayName = "DialogFooter"; @@ -59,7 +80,14 @@ const DialogTitle = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - + )); DialogTitle.displayName = DialogPrimitive.Title.displayName; @@ -67,7 +95,11 @@ const DialogDescription = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - + )); DialogDescription.displayName = DialogPrimitive.Description.displayName; diff --git a/apps/app-portal/src/components/ui/form.tsx b/apps/app-portal/src/components/ui/form.tsx index 5db7a365..1badcdde 100644 --- a/apps/app-portal/src/components/ui/form.tsx +++ b/apps/app-portal/src/components/ui/form.tsx @@ -2,18 +2,30 @@ import * as React from "react"; import { Slot } from "@radix-ui/react-slot"; -import { Controller, FormProvider, useFormContext, type ControllerProps, type FieldPath, type FieldValues } from "react-hook-form"; +import { + Controller, + FormProvider, + useFormContext, + type ControllerProps, + type FieldPath, + type FieldValues, +} from "react-hook-form"; import { Label } from "@/components/ui/label"; import { cn } from "@/lib/utils"; const Form = FormProvider; -type FormFieldContextValue = FieldPath> = { +type FormFieldContextValue< + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath = FieldPath, +> = { name: TName; }; -const FormFieldContext = React.createContext({} as FormFieldContextValue); +const FormFieldContext = React.createContext( + {} as FormFieldContextValue, +); const FormField = < TFieldValues extends FieldValues = FieldValues, @@ -55,9 +67,14 @@ type FormItemContextValue = { id: string; }; -const FormItemContext = React.createContext({} as FormItemContextValue); +const FormItemContext = React.createContext( + {} as FormItemContextValue, +); -const FormItem = React.forwardRef>(({ className, ...props }, ref) => { +const FormItem = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => { const id = React.useId(); return ( @@ -68,23 +85,39 @@ const FormItem = React.forwardRef, React.ComponentPropsWithoutRef>( - ({ className, ...props }, ref) => { - const { error, formItemId } = useFormField(); +const FormLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => { + const { error, formItemId } = useFormField(); - return