first commit

This commit is contained in:
2026-02-21 17:34:29 +07:00
commit fc7b96daf6
29 changed files with 7364 additions and 0 deletions
+109
View File
@@ -0,0 +1,109 @@
import { Link, useParams } from "react-router"
import {
ArrowLeft,
Calendar,
FileText,
Lock,
MessageSquare,
} from "lucide-react"
import { dummyForms } from "@/lib/dummy-data"
import { Navbar } from "@/components/shared/navbar"
import { Footer } from "@/components/shared/footer"
import { FormButton } from "@/components/shared/form-button"
import { QuestionPreview } from "@/components/forms/question-preview"
export default function FormPreviewPage() {
const { id } = useParams()
const form = dummyForms.find((f) => f.id === id)
if (!form) {
throw new Response("Form not found", { status: 404 })
}
return (
<div className="flex min-h-screen flex-col bg-background">
<Navbar />
<main className="flex-1">
<div className="mx-auto max-w-3xl px-4 py-8 lg:px-8">
<Link
to="/forms"
className="mb-6 inline-flex items-center gap-1.5 text-sm font-medium text-muted-foreground transition-colors hover:text-foreground"
>
<ArrowLeft className="h-4 w-4" />
Back to forms
</Link>
<div className="mb-8 rounded-xl border border-border bg-card shadow-sm">
<div className="h-2 rounded-t-xl bg-primary" />
<div className="p-6">
<div className="mb-4 flex flex-wrap items-center gap-3">
<span className="flex items-center gap-1 text-xs text-muted-foreground">
<Lock className="h-3 w-3" />
Read-only preview
</span>
</div>
<h1 className="text-xl font-bold text-foreground text-balance sm:text-2xl">
{form.title}
</h1>
<p className="mt-2 text-sm leading-relaxed text-muted-foreground">
{form.description}
</p>
<div className="mt-5 flex flex-wrap items-center gap-x-5 gap-y-2 border-t border-border pt-5 text-xs text-muted-foreground">
<span className="flex items-center gap-1.5">
<FileText className="h-3.5 w-3.5" />
{form.questions.length} questions
</span>
<span className="flex items-center gap-1.5">
<MessageSquare className="h-3.5 w-3.5" />
{form.responseCount} responses
</span>
<span className="flex items-center gap-1.5">
<Calendar className="h-3.5 w-3.5" />
Created {form.createdAt}
</span>
<span className="flex items-center gap-1.5">
<Calendar className="h-3.5 w-3.5" />
Updated {form.updatedAt}
</span>
</div>
</div>
</div>
<div className="flex flex-col gap-4">
{form.questions.map((question, index) => (
<QuestionPreview
key={question.id}
question={question}
index={index}
/>
))}
</div>
<div className="mt-8 flex flex-col items-center gap-3 rounded-xl border border-dashed border-border bg-card/50 p-6">
<p className="text-sm text-muted-foreground">
This is a read-only preview. Form submission is disabled.
</p>
<FormButton disabled size="lg">
Submit (Preview Mode)
</FormButton>
</div>
<div className="mt-6 flex justify-center">
<Link to="/forms">
<FormButton variant="ghost" size="sm">
<ArrowLeft className="h-4 w-4" />
Back to all forms
</FormButton>
</Link>
</div>
</div>
</main>
<Footer />
</div>
)
}
+84
View File
@@ -0,0 +1,84 @@
"use client"
import { useState } from "react"
import { Plus, Search } from "lucide-react"
import { dummyForms } from "@/lib/dummy-data"
import { Navbar } from "@/components/shared/navbar"
import { Footer } from "@/components/shared/footer"
import { FormInput } from "@/components/shared/form-input"
import { FormButton } from "@/components/shared/form-button"
import { FormCard } from "@/components/forms/form-card"
export default function FormsPage() {
const [search, setSearch] = useState("")
const filtered = dummyForms.filter((form) => {
return (
form.title.toLowerCase().includes(search.toLowerCase()) ||
form.description.toLowerCase().includes(search.toLowerCase())
)
})
return (
<div className="flex min-h-screen flex-col bg-background">
<Navbar />
<main className="flex-1">
<div className="mx-auto max-w-6xl px-4 py-8 lg:px-8">
<div className="mb-8 flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div>
<h1 className="text-2xl font-bold text-foreground">My Forms</h1>
<p className="mt-1 text-sm text-muted-foreground">
Manage and preview all your forms in one place
</p>
</div>
<FormButton size="md">
<Plus className="h-4 w-4" />
New Form
</FormButton>
</div>
<div className="mb-6">
<div className="relative max-w-md">
<Search className="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
<FormInput
placeholder="Search forms..."
value={search}
onChange={(e) => setSearch(e.target.value)}
className="pl-9"
/>
</div>
</div>
<p className="mb-4 text-sm text-muted-foreground">
Showing {filtered.length} of {dummyForms.length} forms
</p>
{filtered.length > 0 ? (
<div className="grid gap-5 sm:grid-cols-2">
{filtered.map((form) => (
<FormCard key={form.id} form={form} />
))}
</div>
) : (
<div className="flex flex-col items-center justify-center rounded-xl border border-dashed border-border py-16">
<p className="text-sm text-muted-foreground">
No forms found matching your criteria.
</p>
<FormButton
variant="ghost"
size="sm"
className="mt-3"
onClick={() => setSearch("")}
>
Clear filters
</FormButton>
</div>
)}
</div>
</main>
<Footer />
</div>
)
}
+13
View File
@@ -0,0 +1,13 @@
import type { Route } from "./+types/home";
import { Welcome } from "../welcome/welcome";
export function meta({}: Route.MetaArgs) {
return [
{ title: "New React Router App" },
{ name: "description", content: "Welcome to React Router!" },
];
}
export default function Home() {
return <Welcome />;
}
+83
View File
@@ -0,0 +1,83 @@
"use client"
import { Link } from "react-router";
import { useNavigate } from "react-router";
import { FormInput } from "@/components/shared/form-input"
import { FormButton } from "@/components/shared/form-button"
export default function LoginPage() {
const navigate = useNavigate();
function handleSubmit(e: React.FormEvent) {
e.preventDefault()
navigate("/forms")
}
return (
<div className="flex min-h-screen flex-col bg-background">
<div className="flex flex-1 items-center justify-center px-4 py-12">
<div className="w-full max-w-md">
<div className="mb-8 flex flex-col items-center gap-3">
<h1 className="text-2xl font-bold text-foreground">
Welcome back
</h1>
<p className="text-sm text-muted-foreground">
Sign in to your FormCraft account
</p>
</div>
<div className="rounded-xl border border-border bg-card p-6 shadow-sm">
<form onSubmit={handleSubmit} className="flex flex-col gap-4">
<FormInput
label="Email"
type="email"
placeholder="bagas@example.com"
required
autoComplete="email"
/>
<FormInput
label="Password"
type="password"
placeholder="Enter your password"
required
autoComplete="current-password"
/>
<div className="flex items-center justify-between">
<label className="flex items-center gap-2 text-sm text-muted-foreground">
<input
type="checkbox"
className="h-4 w-4 rounded border-input accent-primary"
/>
Remember me
</label>
<Link
to="#"
className="text-sm font-medium text-primary hover:underline"
>
Forgot password?
</Link>
</div>
<FormButton type="submit" size="lg" className="mt-2 w-full">
Sign in
</FormButton>
</form>
</div>
<p className="mt-6 text-center text-sm text-muted-foreground">
{"Don't have an account? "}
<Link
to="/register"
className="font-medium text-primary hover:underline"
>
Sign up
</Link>
</p>
</div>
</div>
</div>
)
}
+93
View File
@@ -0,0 +1,93 @@
"use client"
import { Link } from "react-router";
import { useNavigate } from "react-router";
import { FormInput } from "@/components/shared/form-input"
import { FormButton } from "@/components/shared/form-button"
export default function RegisterPage() {
const navigate = useNavigate();
function handleSubmit(e: React.FormEvent) {
e.preventDefault()
navigate("/forms")
}
return (
<div className="flex min-h-screen flex-col bg-background">
<div className="flex flex-1 items-center justify-center px-4 py-12">
<div className="w-full max-w-md">
<div className="mb-8 flex flex-col items-center gap-3">
<h1 className="text-2xl font-bold text-foreground">
Create your account
</h1>
<p className="text-sm text-muted-foreground">
Start building beautiful forms in minutes
</p>
</div>
<div className="rounded-xl border border-border bg-card p-6 shadow-sm">
<form onSubmit={handleSubmit} className="flex flex-col gap-4">
<div className="flex gap-3">
<FormInput
label="First name"
type="text"
placeholder="bagas"
required
autoComplete="given-name"
className="w-full"
/>
<FormInput
label="Last name"
type="text"
placeholder="pacil"
required
autoComplete="family-name"
className="w-full"
/>
</div>
<FormInput
label="Email"
type="email"
placeholder="bagas@example.com"
required
autoComplete="email"
/>
<FormInput
label="Password"
type="password"
placeholder="Create a password"
required
hint="Must be at least 8 characters"
autoComplete="new-password"
/>
<FormInput
label="Confirm password"
type="password"
placeholder="Confirm your password"
required
autoComplete="new-password"
/>
<FormButton type="submit" size="lg" className="mt-2 w-full">
Create account
</FormButton>
</form>
</div>
<p className="mt-6 text-center text-sm text-muted-foreground">
Already have an account?{" "}
<Link
to="/login"
className="font-medium text-primary hover:underline"
>
Sign in
</Link>
</p>
</div>
</div>
</div>
)
}