diff --git a/app/(auth-pages)/forgot-password/page.tsx b/app/(auth-pages)/forgot-password/page.tsx deleted file mode 100644 index bcf9725..0000000 --- a/app/(auth-pages)/forgot-password/page.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { forgotPasswordAction } from "@/app/actions"; -import { FormMessage, Message } from "@/components/form-message"; -import { SubmitButton } from "@/components/submit-button"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import Link from "next/link"; -import { SmtpMessage } from "../smtp-message"; - -export default async function ForgotPassword(props: { - searchParams: Promise; -}) { - const searchParams = await props.searchParams; - return ( - <> -
-
-

Reset Password

-

- Already have an account?{" "} - - Sign in - -

-
-
- - - - Reset Password - - -
-
- - - ); -} diff --git a/app/(auth-pages)/layout.tsx b/app/(auth-pages)/layout.tsx deleted file mode 100644 index e038de1..0000000 --- a/app/(auth-pages)/layout.tsx +++ /dev/null @@ -1,9 +0,0 @@ -export default async function Layout({ - children, -}: { - children: React.ReactNode; -}) { - return ( -
{children}
- ); -} diff --git a/app/(auth-pages)/sign-in/page.tsx b/app/(auth-pages)/sign-in/page.tsx deleted file mode 100644 index 7628cc7..0000000 --- a/app/(auth-pages)/sign-in/page.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { signInAction } from "@/app/actions"; -import { FormMessage, Message } from "@/components/form-message"; -import { SubmitButton } from "@/components/submit-button"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import Link from "next/link"; - -export default async function Login(props: { searchParams: Promise }) { - const searchParams = await props.searchParams; - return ( -
-

Sign in

-

- Don't have an account?{" "} - - Sign up - -

-
- - -
- - - Forgot Password? - -
- - - Sign in - - -
-
- ); -} diff --git a/app/(auth-pages)/sign-up/page.tsx b/app/(auth-pages)/sign-up/page.tsx deleted file mode 100644 index 31b5a6d..0000000 --- a/app/(auth-pages)/sign-up/page.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { signUpAction } from "@/app/actions"; -import { FormMessage, Message } from "@/components/form-message"; -import { SubmitButton } from "@/components/submit-button"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import Link from "next/link"; -import { SmtpMessage } from "../smtp-message"; - -export default async function Signup(props: { - searchParams: Promise; -}) { - const searchParams = await props.searchParams; - if ("message" in searchParams) { - return ( -
- -
- ); - } - - return ( - <> -
-

Sign up

-

- Already have an account?{" "} - - Sign in - -

-
- - - - - - Sign up - - -
-
- - - ); -} diff --git a/app/(auth-pages)/smtp-message.tsx b/app/(auth-pages)/smtp-message.tsx deleted file mode 100644 index 84c21fc..0000000 --- a/app/(auth-pages)/smtp-message.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { ArrowUpRight, InfoIcon } from "lucide-react"; -import Link from "next/link"; - -export function SmtpMessage() { - return ( -
- -
- - Note: Emails are rate limited. Enable Custom SMTP to - increase the rate limit. - -
- - Learn more - -
-
-
- ); -} diff --git a/app/(public)/register-company/page.tsx b/app/(public)/register-company/page.tsx new file mode 100644 index 0000000..f94e82c --- /dev/null +++ b/app/(public)/register-company/page.tsx @@ -0,0 +1,91 @@ +"use client"; + +import { MedReportTitle } from "@/components/MedReportTitle"; +import React from "react"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import Image from "next/image"; +import medReportBigLogo from "@/assets/medReportBigLogo.png"; +import { SubmitButton } from "@/components/submit-button"; +import { yupResolver } from "@hookform/resolvers/yup"; +import { useForm } from "react-hook-form"; +import { companySchema } from "@/lib/validations/companySchema"; +import { CompanySubmitData } from "@/lib/types/company"; +import { submitCompanyRegistration } from "@/lib/services/register-company.service"; +import { useRouter } from "next/navigation"; + +export default function RegisterCompany() { + const router = useRouter(); + const { + register, + handleSubmit, + formState: { errors, isValid, isSubmitting }, + } = useForm({ + resolver: yupResolver(companySchema), + mode: "onChange", + }); + + async function onSubmit(data: CompanySubmitData) { + const formData = new FormData(); + Object.entries(data).forEach(([key, value]) => { + if (value !== undefined) formData.append(key, value); + }); + + try { + await submitCompanyRegistration(formData); + router.push("/register-company/success"); + } catch (err: unknown) { + if (err instanceof Error) { + alert("Server validation error: " + err.message); + } + alert("Server validation error"); + } + } + + return ( +
+
+ +

Ettevõtte andmed

+

+ Pakkumise saamiseks palun sisesta ettevõtte andmed millega MedReport + kasutada kavatsed. +

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ + Küsi pakkumist + +
+
+
+ MedReport +
+
+ ); +} diff --git a/app/(public)/register-company/success/page.tsx b/app/(public)/register-company/success/page.tsx new file mode 100644 index 0000000..ef72008 --- /dev/null +++ b/app/(public)/register-company/success/page.tsx @@ -0,0 +1,21 @@ +import { MedReportTitle } from "@/components/MedReportTitle"; +import Image from "next/image"; +import sucess from "@/assets/success.png"; +import Link from "next/link"; +import { Button } from "@/components/ui/button"; + +export default function CompanyRegistrationSuccess() { + return ( +
+ +
+ Success +

Päring edukalt saadetud!

+

Saadame teile esimesel võimalusel vastuse

+
+ +
+ ); +} diff --git a/app/(public)/sign-in/page.tsx b/app/(public)/sign-in/page.tsx new file mode 100644 index 0000000..b0b1b5f --- /dev/null +++ b/app/(public)/sign-in/page.tsx @@ -0,0 +1,22 @@ +import { Button } from "@/components/ui/button"; +import Link from "next/link"; +import React from "react"; + +export default async function SignIn() { + return ( +
+ + + + +
+ ); +} diff --git a/app/actions.ts b/app/actions.ts deleted file mode 100644 index dbf8a26..0000000 --- a/app/actions.ts +++ /dev/null @@ -1,134 +0,0 @@ -"use server"; - -import { encodedRedirect } from "@/utils/utils"; -import { createClient } from "@/utils/supabase/server"; -import { headers } from "next/headers"; -import { redirect } from "next/navigation"; - -export const signUpAction = async (formData: FormData) => { - const email = formData.get("email")?.toString(); - const password = formData.get("password")?.toString(); - const supabase = await createClient(); - const origin = (await headers()).get("origin"); - - if (!email || !password) { - return encodedRedirect( - "error", - "/sign-up", - "Email and password are required", - ); - } - - const { error } = await supabase.auth.signUp({ - email, - password, - options: { - emailRedirectTo: `${origin}/auth/callback`, - }, - }); - - if (error) { - console.error(error.code + " " + error.message); - return encodedRedirect("error", "/sign-up", error.message); - } else { - return encodedRedirect( - "success", - "/sign-up", - "Thanks for signing up! Please check your email for a verification link.", - ); - } -}; - -export const signInAction = async (formData: FormData) => { - const email = formData.get("email") as string; - const password = formData.get("password") as string; - const supabase = await createClient(); - - const { error } = await supabase.auth.signInWithPassword({ - email, - password, - }); - - if (error) { - return encodedRedirect("error", "/sign-in", error.message); - } - - return redirect("/protected"); -}; - -export const forgotPasswordAction = async (formData: FormData) => { - const email = formData.get("email")?.toString(); - const supabase = await createClient(); - const origin = (await headers()).get("origin"); - const callbackUrl = formData.get("callbackUrl")?.toString(); - - if (!email) { - return encodedRedirect("error", "/forgot-password", "Email is required"); - } - - const { error } = await supabase.auth.resetPasswordForEmail(email, { - redirectTo: `${origin}/auth/callback?redirect_to=/protected/reset-password`, - }); - - if (error) { - console.error(error.message); - return encodedRedirect( - "error", - "/forgot-password", - "Could not reset password", - ); - } - - if (callbackUrl) { - return redirect(callbackUrl); - } - - return encodedRedirect( - "success", - "/forgot-password", - "Check your email for a link to reset your password.", - ); -}; - -export const resetPasswordAction = async (formData: FormData) => { - const supabase = await createClient(); - - const password = formData.get("password") as string; - const confirmPassword = formData.get("confirmPassword") as string; - - if (!password || !confirmPassword) { - encodedRedirect( - "error", - "/protected/reset-password", - "Password and confirm password are required", - ); - } - - if (password !== confirmPassword) { - encodedRedirect( - "error", - "/protected/reset-password", - "Passwords do not match", - ); - } - - const { error } = await supabase.auth.updateUser({ - password: password, - }); - - if (error) { - encodedRedirect( - "error", - "/protected/reset-password", - "Password update failed", - ); - } - - encodedRedirect("success", "/protected/reset-password", "Password updated"); -}; - -export const signOutAction = async () => { - const supabase = await createClient(); - await supabase.auth.signOut(); - return redirect("/sign-in"); -}; diff --git a/app/auth/callback/route.ts b/app/auth/callback/route.ts deleted file mode 100644 index dd415a4..0000000 --- a/app/auth/callback/route.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { createClient } from "@/utils/supabase/server"; -import { NextResponse } from "next/server"; - -export async function GET(request: Request) { - // The `/auth/callback` route is required for the server-side auth flow implemented - // by the SSR package. It exchanges an auth code for the user's session. - // https://supabase.com/docs/guides/auth/server-side/nextjs - const requestUrl = new URL(request.url); - const code = requestUrl.searchParams.get("code"); - const origin = requestUrl.origin; - const redirectTo = requestUrl.searchParams.get("redirect_to")?.toString(); - - if (code) { - const supabase = await createClient(); - await supabase.auth.exchangeCodeForSession(code); - } - - if (redirectTo) { - return NextResponse.redirect(`${origin}${redirectTo}`); - } - - // URL to redirect to after sign up process completes - return NextResponse.redirect(`${origin}/protected`); -} diff --git a/app/favicon.ico b/app/favicon.ico deleted file mode 100644 index 718d6fe..0000000 Binary files a/app/favicon.ico and /dev/null differ diff --git a/app/globals.css b/app/globals.css index f450d1e..b069159 100644 --- a/app/globals.css +++ b/app/globals.css @@ -3,24 +3,48 @@ @tailwind utilities; @layer base { + @font-face { + font-family: 'Inter Display'; + src: url('../fonts/InterDisplay-Regular.woff2') format('woff2'); + font-weight: 400; + font-style: normal; + font-display: swap; + } + + @font-face { + font-family: 'Inter Display'; + src: url('../fonts/InterDisplay-Medium.woff2') format('woff2'); + font-weight: 500; + font-style: medium; + font-display: swap; + } + + h1 { + @apply text-foreground text-2xl font-semibold tracking-tight + } + + p { + @apply font-inter text-muted-foreground text-sm + } + :root { --background: 0 0% 100%; - --foreground: 0 0% 3.9%; + --foreground: 240 10% 4%; --card: 0 0% 100%; --card-foreground: 0 0% 3.9%; --popover: 0 0% 100%; - --popover-foreground: 0 0% 3.9%; - --primary: 0 0% 9%; + --popover-foreground: 356 100% 97%; + --primary: 145 78% 18%; --primary-foreground: 0 0% 98%; --secondary: 0 0% 96.1%; --secondary-foreground: 0 0% 9%; --muted: 0 0% 96.1%; - --muted-foreground: 0 0% 45.1%; + --muted-foreground: 240 4% 41%; --accent: 0 0% 96.1%; --accent-foreground: 0 0% 9%; --destructive: 0 84.2% 60.2%; --destructive-foreground: 0 0% 98%; - --border: 0 0% 89.8%; + --border: 240 6% 90%; --input: 0 0% 89.8%; --ring: 0 0% 3.9%; --radius: 0.5rem; @@ -30,33 +54,6 @@ --chart-4: 43 74% 66%; --chart-5: 27 87% 67%; } - - .dark { - --background: 0 0% 3.9%; - --foreground: 0 0% 98%; - --card: 0 0% 3.9%; - --card-foreground: 0 0% 98%; - --popover: 0 0% 3.9%; - --popover-foreground: 0 0% 98%; - --primary: 0 0% 98%; - --primary-foreground: 0 0% 9%; - --secondary: 0 0% 14.9%; - --secondary-foreground: 0 0% 98%; - --muted: 0 0% 14.9%; - --muted-foreground: 0 0% 63.9%; - --accent: 0 0% 14.9%; - --accent-foreground: 0 0% 98%; - --destructive: 0 62.8% 30.6%; - --destructive-foreground: 0 0% 98%; - --border: 0 0% 14.9%; - --input: 0 0% 14.9%; - --ring: 0 0% 83.1%; - --chart-1: 220 70% 50%; - --chart-2: 160 60% 45%; - --chart-3: 30 80% 55%; - --chart-4: 280 65% 60%; - --chart-5: 340 75% 55%; - } } @layer base { @@ -66,4 +63,4 @@ body { @apply bg-background text-foreground; } -} +} \ No newline at end of file diff --git a/app/icon.ico b/app/icon.ico new file mode 100644 index 0000000..ad7ea1f Binary files /dev/null and b/app/icon.ico differ diff --git a/app/layout.tsx b/app/layout.tsx index 8f6dcb7..57a4a6f 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,21 +1,22 @@ -import DeployButton from "@/components/deploy-button"; import { EnvVarWarning } from "@/components/env-var-warning"; import HeaderAuth from "@/components/header-auth"; -import { ThemeSwitcher } from "@/components/theme-switcher"; import { hasEnvVars } from "@/utils/supabase/check-env-vars"; import { Geist } from "next/font/google"; import { ThemeProvider } from "next-themes"; -import Link from "next/link"; import "./globals.css"; +import { Metadata } from "next"; const defaultUrl = process.env.VERCEL_URL ? `https://${process.env.VERCEL_URL}` : "http://localhost:3000"; -export const metadata = { +export const metadata: Metadata = { metadataBase: new URL(defaultUrl), - title: "Next.js and Supabase Starter Kit", - description: "The fastest way to build apps with Next.js and Supabase", + title: "MedReport", + description: "MedReport", + icons: { + icon: "icon.ico", + }, }; const geistSans = Geist({ @@ -41,33 +42,12 @@ export default function RootLayout({
{children}
- -
diff --git a/app/opengraph-image.png b/app/opengraph-image.png deleted file mode 100644 index 57595e6..0000000 Binary files a/app/opengraph-image.png and /dev/null differ diff --git a/app/page.tsx b/app/page.tsx index 9144694..de1e223 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,16 +1,5 @@ -import Hero from "@/components/hero"; -import ConnectSupabaseSteps from "@/components/tutorial/connect-supabase-steps"; -import SignUpUserSteps from "@/components/tutorial/sign-up-user-steps"; -import { hasEnvVars } from "@/utils/supabase/check-env-vars"; +import { MedReportTitle } from "@/components/MedReportTitle"; export default async function Home() { - return ( - <> - -
-

Next steps

- {hasEnvVars ? : } -
- - ); + return ; } diff --git a/app/protected/page.tsx b/app/protected/page.tsx deleted file mode 100644 index 5508aba..0000000 --- a/app/protected/page.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import FetchDataSteps from "@/components/tutorial/fetch-data-steps"; -import { createClient } from "@/utils/supabase/server"; -import { InfoIcon } from "lucide-react"; -import { redirect } from "next/navigation"; - -export default async function ProtectedPage() { - const supabase = await createClient(); - - const { - data: { user }, - } = await supabase.auth.getUser(); - - if (!user) { - return redirect("/sign-in"); - } - - return ( -
-
-
- - This is a protected page that you can only see as an authenticated - user -
-
-
-

Your user details

-
-          {JSON.stringify(user, null, 2)}
-        
-
-
-

Next steps

- -
-
- ); -} diff --git a/app/protected/reset-password/page.tsx b/app/protected/reset-password/page.tsx deleted file mode 100644 index 9cd7084..0000000 --- a/app/protected/reset-password/page.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { resetPasswordAction } from "@/app/actions"; -import { FormMessage, Message } from "@/components/form-message"; -import { SubmitButton } from "@/components/submit-button"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; - -export default async function ResetPassword(props: { - searchParams: Promise; -}) { - const searchParams = await props.searchParams; - return ( -
-

Reset password

-

- Please enter your new password below. -

- - - - - - Reset password - - - - ); -} diff --git a/app/twitter-image.png b/app/twitter-image.png deleted file mode 100644 index 57595e6..0000000 Binary files a/app/twitter-image.png and /dev/null differ diff --git a/assets/MedReportSmallLogo.tsx b/assets/MedReportSmallLogo.tsx new file mode 100644 index 0000000..8d5ec33 --- /dev/null +++ b/assets/MedReportSmallLogo.tsx @@ -0,0 +1,16 @@ +export const MedReportSmallLogo = () => { + return ( + + + + ); +}; diff --git a/assets/medReportBigLogo.png b/assets/medReportBigLogo.png new file mode 100644 index 0000000..50f9e72 Binary files /dev/null and b/assets/medReportBigLogo.png differ diff --git a/assets/success.png b/assets/success.png new file mode 100644 index 0000000..9947cbb Binary files /dev/null and b/assets/success.png differ diff --git a/components/MedReportTitle.tsx b/components/MedReportTitle.tsx new file mode 100644 index 0000000..c33c809 --- /dev/null +++ b/components/MedReportTitle.tsx @@ -0,0 +1,10 @@ +import { MedReportSmallLogo } from "@/assets/MedReportSmallLogo"; + +export const MedReportTitle = () => ( +
+ + + MedReport + +
+); diff --git a/components/header-auth.tsx b/components/header-auth.tsx index eb9d65c..eb84a38 100644 --- a/components/header-auth.tsx +++ b/components/header-auth.tsx @@ -1,4 +1,4 @@ -import { signOutAction } from "@/app/actions"; +import { signOutAction } from "@/app/example/actions"; import { hasEnvVars } from "@/utils/supabase/check-env-vars"; import Link from "next/link"; import { Badge } from "./ui/badge"; @@ -41,7 +41,7 @@ export default async function AuthButton() { disabled className="opacity-75 cursor-none pointer-events-none" > - Sign up + Sign up @@ -62,9 +62,6 @@ export default async function AuthButton() { - ); } diff --git a/components/ui/button.tsx b/components/ui/button.tsx index 57c9fe4..eb1bf90 100644 --- a/components/ui/button.tsx +++ b/components/ui/button.tsx @@ -5,11 +5,12 @@ import { cva, type VariantProps } from "class-variance-authority"; import { cn } from "@/lib/utils"; const buttonVariants = cva( - "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", + "inline-flex items-center justify-center whitespace-nowrap rounded-xl text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", { variants: { variant: { - default: "bg-primary text-primary-foreground hover:bg-primary/90", + default: + "bg-primary text-primary-foreground font-inter font-medium hover:bg-primary/90", destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90", outline: @@ -30,7 +31,7 @@ const buttonVariants = cva( variant: "default", size: "default", }, - }, + } ); export interface ButtonProps @@ -49,7 +50,7 @@ const Button = React.forwardRef( {...props} /> ); - }, + } ); Button.displayName = "Button"; diff --git a/components/ui/label.tsx b/components/ui/label.tsx index 84f8b0c..a982e7e 100644 --- a/components/ui/label.tsx +++ b/components/ui/label.tsx @@ -7,7 +7,7 @@ import { cva, type VariantProps } from "class-variance-authority"; import { cn } from "@/lib/utils"; const labelVariants = cva( - "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70", + "text-sm text-foreground font-inter font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" ); const Label = React.forwardRef< diff --git a/fonts/InterDisplay-Medium.woff2 b/fonts/InterDisplay-Medium.woff2 new file mode 100644 index 0000000..f6157fa Binary files /dev/null and b/fonts/InterDisplay-Medium.woff2 differ diff --git a/fonts/InterDisplay-Regular.woff2 b/fonts/InterDisplay-Regular.woff2 new file mode 100644 index 0000000..b5a45e8 Binary files /dev/null and b/fonts/InterDisplay-Regular.woff2 differ diff --git a/lib/services/register-company.service.ts b/lib/services/register-company.service.ts new file mode 100644 index 0000000..c030462 --- /dev/null +++ b/lib/services/register-company.service.ts @@ -0,0 +1,31 @@ +"use server"; + +import * as yup from "yup"; +import { companySchema } from "@/lib/validations/companySchema"; + +export async function submitCompanyRegistration(formData: FormData) { + const data = { + companyName: formData.get("companyName")?.toString() || "", + contactPerson: formData.get("contactPerson")?.toString() || "", + email: formData.get("email")?.toString() || "", + phone: formData.get("phone")?.toString() || "", + }; + + try { + await companySchema.validate(data, { abortEarly: false }); + + console.log("Valid data:", data); + } catch (validationError) { + if (validationError instanceof yup.ValidationError) { + const errors = validationError.inner.map((err) => ({ + path: err.path, + message: err.message, + })); + throw new Error( + "Validation failed: " + + errors.map((e) => `${e.path}: ${e.message}`).join(", ") + ); + } + throw validationError; + } +} diff --git a/lib/types/company.ts b/lib/types/company.ts new file mode 100644 index 0000000..7fbc4d6 --- /dev/null +++ b/lib/types/company.ts @@ -0,0 +1,6 @@ +export interface CompanySubmitData { + companyName: string; + contactPerson: string; + email: string; + phone?: string; +} diff --git a/lib/validations/companySchema.ts b/lib/validations/companySchema.ts new file mode 100644 index 0000000..b265f81 --- /dev/null +++ b/lib/validations/companySchema.ts @@ -0,0 +1,8 @@ +import * as yup from "yup"; + +export const companySchema = yup.object({ + companyName: yup.string().required("Company name is required"), + contactPerson: yup.string().required("Contact person is required"), + email: yup.string().email("Invalid email").required("Email is required"), + phone: yup.string().optional(), +}); diff --git a/package-lock.json b/package-lock.json index 0ecda3c..dff7240 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,15 +24,18 @@ "xml-js": "^1.6.11" }, "devDependencies": { + "@hookform/resolvers": "^5.0.1", "@types/node": "22.10.2", "@types/react": "^19.0.2", "@types/react-dom": "19.0.2", "postcss": "8.4.49", + "react-hook-form": "^7.57.0", "supabase": "^2.23.4", "tailwind-merge": "^2.5.2", "tailwindcss": "3.4.17", "tailwindcss-animate": "^1.0.7", - "typescript": "5.7.2" + "typescript": "5.7.2", + "yup": "^1.6.1" } }, "node_modules/@alloc/quick-lru": { @@ -90,6 +93,18 @@ "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz", "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==" }, + "node_modules/@hookform/resolvers": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-5.0.1.tgz", + "integrity": "sha512-u/+Jp83luQNx9AdyW2fIPGY6Y7NG68eN2ZW8FOJYL+M0i4s49+refdJdOp/A9n9HFQtQs3HIDHQvX3ZET2o7YA==", + "dev": true, + "dependencies": { + "@standard-schema/utils": "^0.3.0" + }, + "peerDependencies": { + "react-hook-form": "^7.55.0" + } + }, "node_modules/@img/sharp-darwin-arm64": { "version": "0.34.1", "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.1.tgz", @@ -1267,6 +1282,12 @@ "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz", "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==" }, + "node_modules/@standard-schema/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", + "dev": true + }, "node_modules/@supabase/auth-js": { "version": "2.69.1", "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.69.1.tgz", @@ -2965,6 +2986,12 @@ "node": "^18.17.0 || >=20.5.0" } }, + "node_modules/property-expr": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz", + "integrity": "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==", + "dev": true + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -3010,6 +3037,22 @@ "react": "^19.0.0" } }, + "node_modules/react-hook-form": { + "version": "7.57.0", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.57.0.tgz", + "integrity": "sha512-RbEks3+cbvTP84l/VXGUZ+JMrKOS8ykQCRYdm5aYsxnDquL0vspsyNhGRO7pcH6hsZqWlPOjLye7rJqdtdAmlg==", + "dev": true, + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-hook-form" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18 || ^19" + } + }, "node_modules/react-remove-scroll": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.3.tgz", @@ -3583,6 +3626,12 @@ "node": ">=0.8" } }, + "node_modules/tiny-case": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz", + "integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==", + "dev": true + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -3595,6 +3644,12 @@ "node": ">=8.0" } }, + "node_modules/toposort": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", + "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==", + "dev": true + }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", @@ -3611,6 +3666,18 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" }, + "node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/typescript": { "version": "5.7.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", @@ -3902,6 +3969,18 @@ "engines": { "node": ">= 14" } + }, + "node_modules/yup": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/yup/-/yup-1.6.1.tgz", + "integrity": "sha512-JED8pB50qbA4FOkDol0bYF/p60qSEDQqBD0/qeIrUCG1KbPBIQ776fCUNb9ldbPcSTxA69g/47XTo4TqWiuXOA==", + "dev": true, + "dependencies": { + "property-expr": "^2.0.5", + "tiny-case": "^1.0.3", + "toposort": "^2.0.2", + "type-fest": "^2.19.0" + } } } } diff --git a/package.json b/package.json index f284980..2ffa858 100644 --- a/package.json +++ b/package.json @@ -25,14 +25,17 @@ "xml-js": "^1.6.11" }, "devDependencies": { + "@hookform/resolvers": "^5.0.1", "@types/node": "22.10.2", "@types/react": "^19.0.2", "@types/react-dom": "19.0.2", + "react-hook-form": "^7.57.0", "postcss": "8.4.49", "supabase": "^2.23.4", "tailwind-merge": "^2.5.2", "tailwindcss": "3.4.17", "tailwindcss-animate": "^1.0.7", - "typescript": "5.7.2" + "typescript": "5.7.2", + "yup": "^1.6.1" } } diff --git a/tailwind.config.ts b/tailwind.config.ts index 41668a3..9aaeca9 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -72,6 +72,9 @@ const config = { "accordion-down": "accordion-down 0.2s ease-out", "accordion-up": "accordion-up 0.2s ease-out", }, + fontFamily: { + inter: ['"Inter Display"', "Geist"], + }, }, }, plugins: [require("tailwindcss-animate")],