From c7e0e2905c5b17ff488f6cc1f91cd05708897091 Mon Sep 17 00:00:00 2001 From: vergiLgood1 Date: Mon, 17 Feb 2025 23:41:35 +0700 Subject: [PATCH] init prisma and edge functions --- .../{contact-admin => contact-us}/page.tsx | 4 +- .../app/(auth-pages)/email-recovery/page.tsx | 26 + .../app/(auth-pages)/verify-otp/page.tsx | 2 - sigap-website/app/error/page.tsx | 0 sigap-website/app/success/page.tsx | 0 .../{contact-admin.tsx => contact-us.tsx} | 11 +- .../components/auth/email-recovery.tsx | 110 +++ .../components/auth/login-form-2.tsx | 8 +- sigap-website/components/auth/login-form.tsx | 5 +- sigap-website/components/ui/switch.tsx | 29 + sigap-website/lib/db.ts | 15 + sigap-website/package-lock.json | 632 +++++++++++++++++- sigap-website/package.json | 21 +- .../migration.sql | 43 ++ .../prisma/migrations/migration_lock.toml | 3 + sigap-website/prisma/schema.prisma | 57 ++ sigap-website/utils/supabase/middleware.ts | 14 +- supabase/.env | 17 +- supabase/config.toml | 5 + supabase/functions/resend/index.ts | 52 +- .../_templates/email-user-invite.tsx | 154 ----- .../send-email/_templates/magic-link.tsx | 130 ---- supabase/functions/send-email/deno.json | 7 +- supabase/functions/send-email/index.ts | 100 +-- 24 files changed, 1031 insertions(+), 414 deletions(-) rename sigap-website/app/(auth-pages)/{contact-admin => contact-us}/page.tsx (94%) create mode 100644 sigap-website/app/(auth-pages)/email-recovery/page.tsx create mode 100644 sigap-website/app/error/page.tsx create mode 100644 sigap-website/app/success/page.tsx rename sigap-website/components/auth/{contact-admin.tsx => contact-us.tsx} (94%) create mode 100644 sigap-website/components/auth/email-recovery.tsx create mode 100644 sigap-website/components/ui/switch.tsx create mode 100644 sigap-website/lib/db.ts create mode 100644 sigap-website/prisma/migrations/20250217140338_first_prisma_migration/migration.sql create mode 100644 sigap-website/prisma/migrations/migration_lock.toml create mode 100644 sigap-website/prisma/schema.prisma delete mode 100644 supabase/functions/send-email/_templates/email-user-invite.tsx delete mode 100644 supabase/functions/send-email/_templates/magic-link.tsx diff --git a/sigap-website/app/(auth-pages)/contact-admin/page.tsx b/sigap-website/app/(auth-pages)/contact-us/page.tsx similarity index 94% rename from sigap-website/app/(auth-pages)/contact-admin/page.tsx rename to sigap-website/app/(auth-pages)/contact-us/page.tsx index f85f8be..e4373b7 100644 --- a/sigap-website/app/(auth-pages)/contact-admin/page.tsx +++ b/sigap-website/app/(auth-pages)/contact-us/page.tsx @@ -1,4 +1,4 @@ -import { ContactAdminForm } from "@/components/auth/contact-admin"; +import { ContactUsForm } from "@/components/auth/contact-us"; import { Button } from "@/components/ui/button"; import { GalleryVerticalEnd, Globe } from "lucide-react"; @@ -16,7 +16,7 @@ export default async function ContactAdminPage() {
- +
diff --git a/sigap-website/app/(auth-pages)/email-recovery/page.tsx b/sigap-website/app/(auth-pages)/email-recovery/page.tsx new file mode 100644 index 0000000..de906de --- /dev/null +++ b/sigap-website/app/(auth-pages)/email-recovery/page.tsx @@ -0,0 +1,26 @@ +import RecoveryEmailForm from "@/components/auth/email-recovery"; +import { VerifyOtpForm } from "@/components/auth/verify-otp-form"; + +import { GalleryVerticalEnd } from "lucide-react"; + +export default async function VerifyOtpPage() { + return ( +
+
+
+ +
+ +
+ Sigap Tech. +
+
+
+
+ +
+
+
+
+ ); +} diff --git a/sigap-website/app/(auth-pages)/verify-otp/page.tsx b/sigap-website/app/(auth-pages)/verify-otp/page.tsx index 5580a19..6f34cb1 100644 --- a/sigap-website/app/(auth-pages)/verify-otp/page.tsx +++ b/sigap-website/app/(auth-pages)/verify-otp/page.tsx @@ -1,5 +1,3 @@ -import { ContactAdminForm } from "@/components/auth/contact-admin"; -import { InputOTPForm } from "@/components/auth/otp-form"; import { VerifyOtpForm } from "@/components/auth/verify-otp-form"; import { GalleryVerticalEnd } from "lucide-react"; diff --git a/sigap-website/app/error/page.tsx b/sigap-website/app/error/page.tsx new file mode 100644 index 0000000..e69de29 diff --git a/sigap-website/app/success/page.tsx b/sigap-website/app/success/page.tsx new file mode 100644 index 0000000..e69de29 diff --git a/sigap-website/components/auth/contact-admin.tsx b/sigap-website/components/auth/contact-us.tsx similarity index 94% rename from sigap-website/components/auth/contact-admin.tsx rename to sigap-website/components/auth/contact-us.tsx index cd6c719..a026045 100644 --- a/sigap-website/components/auth/contact-admin.tsx +++ b/sigap-website/components/auth/contact-us.tsx @@ -22,10 +22,12 @@ import { Textarea } from "../ui/textarea"; import { SubmitButton } from "../submit-button"; import Link from "next/link"; -export function ContactAdminForm() { +export function ContactUsForm() { const typeMessage = [ { value: "1", label: "Request to become a user" }, { value: "2", label: "OTP problem" }, + { value: "3", label: "Request for a feature" }, + { value: "4", label: "Other" }, ]; return ( @@ -33,7 +35,7 @@ export function ContactAdminForm() { Contact Us - Deploy your new project in one-click. + Fill in the form below to contact the admin @@ -112,10 +114,7 @@ export function ContactAdminForm() {
Already have an account? - + Login
diff --git a/sigap-website/components/auth/email-recovery.tsx b/sigap-website/components/auth/email-recovery.tsx new file mode 100644 index 0000000..13a7305 --- /dev/null +++ b/sigap-website/components/auth/email-recovery.tsx @@ -0,0 +1,110 @@ +"use client"; + +import { useState } from "react"; +import { useRouter } from "next/router"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useForm } from "react-hook-form"; +import { z } from "zod"; +import { toast } from "@/hooks/use-toast"; +import { Button } from "@/components/ui/button"; +import { + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { SubmitButton } from "@/components/submit-button"; +import Link from "next/link"; + +const FormSchema = z.object({ + email: z.string().email({ + message: "Please enter a valid email address.", + }), +}); + +export default function RecoveryEmailForm() { + // const router = useRouter(); + const form = useForm>({ + resolver: zodResolver(FormSchema), + defaultValues: { + email: "", + }, + }); + + function onSubmit(data: z.infer) { + setTimeout(() => { + toast({ + title: "Recovery email sent", + description: `We've sent a recovery link to ${data.email}`, + }); + // Redirect to a confirmation page or back to login + // router.push("/login"); + }, 2000); + } + + return ( +
+ + + Account Recovery + + Enter your email to receive a recovery link + + + +
+ + ( + + Email + + + + + We'll send a recovery link to this email + + + + )} + /> + + Send Recovery Link + + + +
+ + + +
+
+ ); +} diff --git a/sigap-website/components/auth/login-form-2.tsx b/sigap-website/components/auth/login-form-2.tsx index b0a8bfc..1a87fd0 100644 --- a/sigap-website/components/auth/login-form-2.tsx +++ b/sigap-website/components/auth/login-form-2.tsx @@ -63,6 +63,12 @@ export function LoginForm2({ className="bg-[#1C1C1C] border-gray-800 text-white placeholder:text-gray-500 focus:border-emerald-600 focus:ring-emerald-600" required /> + + Forgot Email? +
@@ -97,7 +103,7 @@ export function LoginForm2({
Don't have an account? Contact Us diff --git a/sigap-website/components/auth/login-form.tsx b/sigap-website/components/auth/login-form.tsx index 26bff7d..1b38c67 100644 --- a/sigap-website/components/auth/login-form.tsx +++ b/sigap-website/components/auth/login-form.tsx @@ -86,10 +86,7 @@ export function LoginForm({
Don't have an account?{" "} - + Contact Us
diff --git a/sigap-website/components/ui/switch.tsx b/sigap-website/components/ui/switch.tsx new file mode 100644 index 0000000..5f4117f --- /dev/null +++ b/sigap-website/components/ui/switch.tsx @@ -0,0 +1,29 @@ +"use client" + +import * as React from "react" +import * as SwitchPrimitives from "@radix-ui/react-switch" + +import { cn } from "@/lib/utils" + +const Switch = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)) +Switch.displayName = SwitchPrimitives.Root.displayName + +export { Switch } diff --git a/sigap-website/lib/db.ts b/sigap-website/lib/db.ts new file mode 100644 index 0000000..e5d7482 --- /dev/null +++ b/sigap-website/lib/db.ts @@ -0,0 +1,15 @@ +import { PrismaClient } from "@prisma/client"; + +const prismaClientSingleton = () => { + return new PrismaClient(); +}; + +declare const globalThis: { + prismaGlobal: ReturnType; +} & typeof global; + +const prisma = globalThis.prismaGlobal ?? prismaClientSingleton(); + +export default prisma; + +if (process.env.NODE_ENV !== "production") globalThis.prismaGlobal = prisma; diff --git a/sigap-website/package-lock.json b/sigap-website/package-lock.json index 5b9a2e9..a49f51d 100644 --- a/sigap-website/package-lock.json +++ b/sigap-website/package-lock.json @@ -1,11 +1,16 @@ { "name": "sigap-website", + "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { + "name": "sigap-website", + "version": "1.0.0", + "license": "ISC", "dependencies": { "@hookform/resolvers": "^4.0.0", + "@prisma/client": "^6.3.1", "@radix-ui/react-avatar": "^1.1.3", "@radix-ui/react-checkbox": "^1.1.1", "@radix-ui/react-collapsible": "^1.1.3", @@ -15,6 +20,7 @@ "@radix-ui/react-select": "^2.1.6", "@radix-ui/react-separator": "^1.1.2", "@radix-ui/react-slot": "^1.1.2", + "@radix-ui/react-switch": "^1.1.3", "@radix-ui/react-toast": "^1.2.6", "@radix-ui/react-tooltip": "^1.1.8", "@react-email/components": "0.0.33", @@ -35,15 +41,18 @@ "zod": "^3.24.2" }, "devDependencies": { - "@types/node": "22.10.2", + "@types/node": "^22.10.2", "@types/react": "^19.0.2", "@types/react-dom": "19.0.2", "postcss": "8.4.49", + "prisma": "^6.3.1", "react-email": "3.0.7", + "supabase": "^2.12.1", "tailwind-merge": "^2.6.0", "tailwindcss": "3.4.17", "tailwindcss-animate": "^1.0.7", - "typescript": "5.7.2" + "ts-node": "^10.9.2", + "typescript": "^5.7.2" } }, "node_modules/@alloc/quick-lru": { @@ -378,6 +387,30 @@ "node": ">=6.9.0" } }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "node_modules/@emnapi/runtime": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz", @@ -1241,6 +1274,19 @@ "node": ">=12" } }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.8", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", @@ -1482,6 +1528,78 @@ "node": ">=14" } }, + "node_modules/@prisma/client": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.3.1.tgz", + "integrity": "sha512-ARAJaPs+eBkemdky/XU3cvGRl+mIPHCN2lCXsl5Vlb0E2gV+R6IN7aCI8CisRGszEZondwIsW9Iz8EJkTdykyA==", + "hasInstallScript": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "peerDependencies": { + "prisma": "*", + "typescript": ">=5.1.0" + }, + "peerDependenciesMeta": { + "prisma": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/@prisma/debug": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.3.1.tgz", + "integrity": "sha512-RrEBkd+HLZx+ydfmYT0jUj7wjLiS95wfTOSQ+8FQbvb6vHh5AeKfEPt/XUQ5+Buljj8hltEfOslEW57/wQIVeA==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@prisma/engines": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.3.1.tgz", + "integrity": "sha512-sXdqEVLyGAJ5/iUoG/Ea5AdHMN71m6PzMBWRQnLmhhOejzqAaEr8rUd623ql6OJpED4s/U4vIn4dg1qkF7vGag==", + "devOptional": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "6.3.1", + "@prisma/engines-version": "6.3.0-17.acc0b9dd43eb689cbd20c9470515d719db10d0b0", + "@prisma/fetch-engine": "6.3.1", + "@prisma/get-platform": "6.3.1" + } + }, + "node_modules/@prisma/engines-version": { + "version": "6.3.0-17.acc0b9dd43eb689cbd20c9470515d719db10d0b0", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.3.0-17.acc0b9dd43eb689cbd20c9470515d719db10d0b0.tgz", + "integrity": "sha512-R/ZcMuaWZT2UBmgX3Ko6PAV3f8//ZzsjRIG1eKqp3f2rqEqVtCv+mtzuH2rBPUC9ujJ5kCb9wwpxeyCkLcHVyA==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@prisma/fetch-engine": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.3.1.tgz", + "integrity": "sha512-HOf/0umOgt+/S2xtZze+FHKoxpVg4YpVxROr6g2YG09VsI3Ipyb+rGvD6QGbCqkq5NTWAAZoOGNL+oy7t+IhaQ==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "6.3.1", + "@prisma/engines-version": "6.3.0-17.acc0b9dd43eb689cbd20c9470515d719db10d0b0", + "@prisma/get-platform": "6.3.1" + } + }, + "node_modules/@prisma/get-platform": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.3.1.tgz", + "integrity": "sha512-AYLq6Hk9xG73JdLWJ3Ip9Wg/vlP7xPvftGBalsPzKDOHr/ImhwJ09eS8xC2vNT12DlzGxhfk8BkL0ve2OriNhQ==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "6.3.1" + } + }, "node_modules/@radix-ui/number": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.0.tgz", @@ -2105,6 +2223,35 @@ } } }, + "node_modules/@radix-ui/react-switch": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.1.3.tgz", + "integrity": "sha512-1nc+vjEOQkJVsJtWPSiISGT6OKm4SiOdjMo+/icLxo2G4vxz1GntC5MzfL4v8ey9OEfw787QCD1y3mUv0NiFEQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-primitive": "2.0.2", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-previous": "1.1.0", + "@radix-ui/react-use-size": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-toast": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.6.tgz", @@ -2754,6 +2901,34 @@ "tslib": "^2.8.0" } }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/cookie": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", @@ -2837,6 +3012,42 @@ "node": ">= 0.6" } }, + "node_modules/acorn": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/ansi-regex": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", @@ -2975,6 +3186,23 @@ "node": "^4.5.0 || >= 5.9" } }, + "node_modules/bin-links": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/bin-links/-/bin-links-5.0.0.tgz", + "integrity": "sha512-sdleLVfCjBtgO5cNjA2HVRvWBJAHs4zwenaCPMNJAJU0yNxpzj80IpjOIimkpkr+mhlA+how5poQtt53PygbHA==", + "dev": true, + "license": "ISC", + "dependencies": { + "cmd-shim": "^7.0.0", + "npm-normalize-package-bin": "^4.0.0", + "proc-log": "^5.0.0", + "read-cmd-shim": "^5.0.0", + "write-file-atomic": "^6.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -3191,6 +3419,16 @@ "node": ">= 6" } }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, "node_modules/class-variance-authority": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", @@ -3254,6 +3492,16 @@ "node": ">=6" } }, + "node_modules/cmd-shim": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-7.0.0.tgz", + "integrity": "sha512-rtpaCbr164TPPh+zFdkWpCyZuKkjpAzODfaZCf/SVJZzJN+4bHQb/LP3Jzq5/+84um3XXY8r548XiWKSborwVw==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, "node_modules/color": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", @@ -3347,6 +3595,13 @@ "node": ">= 0.10" } }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "license": "MIT" + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -3381,6 +3636,16 @@ "devOptional": true, "license": "MIT" }, + "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", + "engines": { + "node": ">= 12" + } + }, "node_modules/debounce": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/debounce/-/debounce-2.0.0.tgz", @@ -3457,6 +3722,16 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/dlv": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", @@ -3757,6 +4032,30 @@ "reusify": "^1.0.4" } }, + "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", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -3786,6 +4085,19 @@ "url": "https://github.com/sponsors/isaacs" } }, + "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", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/fraction.js": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", @@ -3944,6 +4256,20 @@ "entities": "^4.4.0" } }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -3965,6 +4291,16 @@ ], "license": "BSD-3-Clause" }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -4255,6 +4591,13 @@ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc" } }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, "node_modules/marked": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/marked/-/marked-7.0.4.tgz", @@ -4360,6 +4703,36 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/minizlib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.1.tgz", + "integrity": "sha512-umcy022ILvb5/3Djuu8LWeqUa8D68JaBzlttKeMWen48SjabqS3iY5w/vzeMzMUNhLDifyhbOwKDSznB1vvrwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.0.4", + "rimraf": "^5.0.5" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -4499,6 +4872,45 @@ "node": "^10 || ^12 || >=14" } }, + "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==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "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", + "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-releases": { "version": "2.0.19", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", @@ -4539,6 +4951,16 @@ "node": ">=0.10.0" } }, + "node_modules/npm-normalize-package-bin": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-4.0.0.tgz", + "integrity": "sha512-TZKxPvItzai9kN9H/TkmCtx/ZN/hvr3vUycjlfmH0ootY9yFBzNOpiXAdIn1Iteqsvk4lQn6B5PTrt+n6h8k/w==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -4884,6 +5306,34 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/prisma": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.3.1.tgz", + "integrity": "sha512-JKCZWvBC3enxk51tY4TWzS4b5iRt4sSU1uHn2I183giZTvonXaQonzVtjLzpOHE7qu9MxY510kAtFGJwryKe3Q==", + "devOptional": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/engines": "6.3.1" + }, + "bin": { + "prisma": "build/index.js" + }, + "engines": { + "node": ">=18.18" + }, + "optionalDependencies": { + "fsevents": "2.3.3" + }, + "peerDependencies": { + "typescript": ">=5.1.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/prismjs": { "version": "1.29.0", "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz", @@ -4893,6 +5343,16 @@ "node": ">=6" } }, + "node_modules/proc-log": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-5.0.0.tgz", + "integrity": "sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, "node_modules/proto-list": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", @@ -5387,6 +5847,16 @@ "pify": "^2.3.0" } }, + "node_modules/read-cmd-shim": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-5.0.0.tgz", + "integrity": "sha512-SEbJV7tohp3DAAILbEMPXavBjAnMN0tVnh4+9G8ihV4Pq3HYF9h8QNez9zkJ1ILkv9G2BjdzwctznGZXgu/HGw==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -5480,6 +5950,22 @@ "node": ">=0.10.0" } }, + "node_modules/rimraf": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", + "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -5930,6 +6416,26 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/supabase": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/supabase/-/supabase-2.12.1.tgz", + "integrity": "sha512-vB6LX1KGrqku8AFlp2vJw49IUB9g6Rz2b84qpcWSZ3mMDFumA6hDSbXbFJUnr3hcvyPzoOsQlhMTZN7a6o3hfA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "bin-links": "^5.0.0", + "https-proxy-agent": "^7.0.2", + "node-fetch": "^3.3.2", + "tar": "7.4.3" + }, + "bin": { + "supabase": "bin/supabase" + }, + "engines": { + "npm": ">=8" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -6015,6 +6521,34 @@ "tailwindcss": ">=3.0.0 || insiders" } }, + "node_modules/tar": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", + "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -6064,6 +6598,57 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/ts-node/node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT" + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -6074,7 +6659,7 @@ "version": "5.7.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -6170,6 +6755,13 @@ "dev": true, "license": "MIT" }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "license": "MIT" + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -6190,6 +6782,16 @@ "defaults": "^1.0.3" } }, + "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", + "engines": { + "node": ">= 8" + } + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", @@ -6312,6 +6914,20 @@ "node": ">=8" } }, + "node_modules/write-file-atomic": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-6.0.0.tgz", + "integrity": "sha512-GmqrO8WJ1NuzJ2DrziEI2o57jKAVIQNf8a18W3nCYU3H7PNWqCCVTeH6/NQE93CIllIgQS98rrmVkYgTX9fFJQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, "node_modules/ws": { "version": "8.18.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", @@ -6353,6 +6969,16 @@ "node": ">= 14" } }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/zod": { "version": "3.24.2", "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz", diff --git a/sigap-website/package.json b/sigap-website/package.json index 5effa37..852d4ab 100644 --- a/sigap-website/package.json +++ b/sigap-website/package.json @@ -7,6 +7,7 @@ }, "dependencies": { "@hookform/resolvers": "^4.0.0", + "@prisma/client": "^6.3.1", "@radix-ui/react-avatar": "^1.1.3", "@radix-ui/react-checkbox": "^1.1.1", "@radix-ui/react-collapsible": "^1.1.3", @@ -16,6 +17,7 @@ "@radix-ui/react-select": "^2.1.6", "@radix-ui/react-separator": "^1.1.2", "@radix-ui/react-slot": "^1.1.2", + "@radix-ui/react-switch": "^1.1.3", "@radix-ui/react-toast": "^1.2.6", "@radix-ui/react-tooltip": "^1.1.8", "@react-email/components": "0.0.33", @@ -36,14 +38,27 @@ "zod": "^3.24.2" }, "devDependencies": { - "@types/node": "22.10.2", + "@types/node": "^22.10.2", "@types/react": "^19.0.2", "@types/react-dom": "19.0.2", "postcss": "8.4.49", + "prisma": "^6.3.1", "react-email": "3.0.7", + "supabase": "^2.12.1", "tailwind-merge": "^2.6.0", "tailwindcss": "3.4.17", "tailwindcss-animate": "^1.0.7", - "typescript": "5.7.2" - } + "ts-node": "^10.9.2", + "typescript": "^5.7.2" + }, + "name": "sigap-website", + "version": "1.0.0", + "description": " \"Next.js

Next.js and Supabase Starter Kit

", + "main": "postcss.config.js", + "directories": { + "lib": "lib" + }, + "keywords": [], + "author": "", + "license": "ISC" } diff --git a/sigap-website/prisma/migrations/20250217140338_first_prisma_migration/migration.sql b/sigap-website/prisma/migrations/20250217140338_first_prisma_migration/migration.sql new file mode 100644 index 0000000..e7fadcf --- /dev/null +++ b/sigap-website/prisma/migrations/20250217140338_first_prisma_migration/migration.sql @@ -0,0 +1,43 @@ +-- CreateEnum +CREATE TYPE "roles" AS ENUM ('admin', 'staff', 'user'); + +-- CreateTable +CREATE TABLE "users" ( + "id" TEXT NOT NULL, + "email" TEXT NOT NULL, + "emailVerified" BOOLEAN NOT NULL DEFAULT false, + "password" TEXT, + "firstName" TEXT, + "lastName" TEXT, + "avatar" TEXT, + "role" "roles" NOT NULL DEFAULT 'user', + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "lastSignedIn" TIMESTAMP(3), + "metadata" JSONB, + + CONSTRAINT "users_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "profiles" ( + "id" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "bio" TEXT, + "phone" TEXT, + "address" TEXT, + "city" TEXT, + "country" TEXT, + "birthDate" TIMESTAMP(3), + + CONSTRAINT "profiles_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "users_email_key" ON "users"("email"); + +-- CreateIndex +CREATE UNIQUE INDEX "profiles_userId_key" ON "profiles"("userId"); + +-- AddForeignKey +ALTER TABLE "profiles" ADD CONSTRAINT "profiles_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/sigap-website/prisma/migrations/migration_lock.toml b/sigap-website/prisma/migrations/migration_lock.toml new file mode 100644 index 0000000..648c57f --- /dev/null +++ b/sigap-website/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (e.g., Git) +provider = "postgresql" \ No newline at end of file diff --git a/sigap-website/prisma/schema.prisma b/sigap-website/prisma/schema.prisma new file mode 100644 index 0000000..be5c6a2 --- /dev/null +++ b/sigap-website/prisma/schema.prisma @@ -0,0 +1,57 @@ +// This is your Prisma schema file, +// learn more about it in the docs: https://pris.ly/d/prisma-schema + +// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions? +// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init + +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") + directUrl = env("DIRECT_URL") +} + +enum Role { + admin + staff + user + + @@map("roles") +} + +model User { + id String @id + email String @unique + emailVerified Boolean @default(false) + password String? + firstName String? + lastName String? + avatar String? + role Role @default(user) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + lastSignedIn DateTime? + metadata Json? + + // Relations (optional examples) + profile Profile? + + @@map("users") // Maps to Supabase's 'users' table +} + +model Profile { + id String @id @default(uuid()) + userId String @unique + bio String? + phone String? + address String? + city String? + country String? + birthDate DateTime? + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + @@map("profiles") // Maps to Supabase's 'profiles' table +} diff --git a/sigap-website/utils/supabase/middleware.ts b/sigap-website/utils/supabase/middleware.ts index 8619ec0..638d5db 100644 --- a/sigap-website/utils/supabase/middleware.ts +++ b/sigap-website/utils/supabase/middleware.ts @@ -22,17 +22,17 @@ export const updateSession = async (request: NextRequest) => { }, setAll(cookiesToSet) { cookiesToSet.forEach(({ name, value }) => - request.cookies.set(name, value), + request.cookies.set(name, value) ); response = NextResponse.next({ request, }); cookiesToSet.forEach(({ name, value, options }) => - response.cookies.set(name, value, options), + response.cookies.set(name, value, options) ); }, }, - }, + } ); // This will refresh session if expired - required for Server Components @@ -40,14 +40,14 @@ export const updateSession = async (request: NextRequest) => { const user = await supabase.auth.getUser(); // protected routes - if (request.nextUrl.pathname.startsWith("/protected") && user.error) { - return NextResponse.redirect(new URL("/sign-in", request.url)); - } - if (request.nextUrl.pathname === "/" && !user.error) { return NextResponse.redirect(new URL("/protected", request.url)); } + if (request.nextUrl.pathname.startsWith("/protected") && user.error) { + return NextResponse.redirect(new URL("/sign-in", request.url)); + } + return response; } catch (e) { // If you are here, a Supabase client could not be created! diff --git a/supabase/.env b/supabase/.env index 38d8121..a69dd76 100644 --- a/supabase/.env +++ b/supabase/.env @@ -2,12 +2,21 @@ # https://app.supabase.com/project/_/settings/api # Supabase Production URL -NEXT_PUBLIC_SUPABASE_URL=https://cppejroeyonsqxulinaj.supabase.co -NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImNwcGVqcm9leW9uc3F4dWxpbmFqIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MzkzNjExMjYsImV4cCI6MjA1NDkzNzEyNn0.36XzD3ANlAmAYObYGxXkkydXLjN0VPv2rMNHnQcHoZU +NEXT_PUBLIC_SUPABASE_URL="https://cppejroeyonsqxulinaj.supabase.co" +NEXT_PUBLIC_SUPABASE_ANON_KEY="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImNwcGVqcm9leW9uc3F4dWxpbmFqIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MzkzNjExMjYsImV4cCI6MjA1NDkzNzEyNn0.36XzD3ANlAmAYObYGxXkkydXLjN0VPv2rMNHnQcHoZU" # Supabase Local URL # NEXT_PUBLIC_SUPABASE_URL=http://127.0.0.1:54321 # NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0 -RESEND_API_KEY=re_WtXdegYe_Ey3yiShKfZZtjCyY1agkEaSi -SEND_EMAIL_HOOK_SECRET=VciYkhJetkc5r9l1ItUWOVmm4mc4B0CUXCq3I3+zDRwTEhOjpuLgMo2N6KNYjFCLAC/7Kdw2e3eo+fqA \ No newline at end of file +# Supabase Service Role Secret Key +SERVICE_ROLE_SECRET="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImNwcGVqcm9leW9uc3F4dWxpbmFqIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImlhdCI6MTczOTM2MTEyNiwiZXhwIjoyMDU0OTM3MTI2fQ.iYIVeUChLIcC7NRaeJ6dViI9JiUZSMUKufFsDTfAkjA" +RESEND_API_KEY_TES="re_WtXdegYe_Ey3yiShKfZZtjCyY1agkEaSi" +SEND_EMAIL_HOOK_SECRET_TES="jeroAB/CXdS721OiHV0Ac0yRcxO7eNihgjblH62xMhLBNc6OwK3DQnkbrHjTlSw5anml2onNTolG3SzZ" + +# db connection string +# Connect to Supabase via connection pooling with Supavisor. +DATABASE_URL="postgresql://prisma.cppejroeyonsqxulinaj:prisma@aws-0-ap-southeast-1.pooler.supabase.com:6543/postgres?pgbouncer=true" + +# Direct connection to the database. Used for migrations. +DIRECT_URL="postgresql://prisma.cppejroeyonsqxulinaj:prisma@aws-0-ap-southeast-1.pooler.supabase.com:5432/postgres" diff --git a/supabase/config.toml b/supabase/config.toml index 532caf8..6a31568 100644 --- a/supabase/config.toml +++ b/supabase/config.toml @@ -288,3 +288,8 @@ s3_region = "env(S3_REGION)" s3_access_key = "env(S3_ACCESS_KEY)" # Configures AWS_SECRET_ACCESS_KEY for S3 bucket s3_secret_key = "env(S3_SECRET_KEY)" + +[auth.hook.send_email] +enabled = true +uri = "http://host.docker.internal:54321/functions/v1/send-email" +secrets = "v1,whsec_jeroAB/CXdS721OiHV0Ac0yRcxO7eNihgjblH62xMhLBNc6OwK3DQnkbrHjTlSw5anml2onNTolG3SzZ" \ No newline at end of file diff --git a/supabase/functions/resend/index.ts b/supabase/functions/resend/index.ts index 44a8339..6c8dcfa 100644 --- a/supabase/functions/resend/index.ts +++ b/supabase/functions/resend/index.ts @@ -1,30 +1,32 @@ -import { serve } from "https://deno.land/std@0.168.0/http/server.ts"; +// Follow this setup guide to integrate the Deno language server with your editor: +// https://deno.land/manual/getting_started/setup_your_environment +// This enables autocomplete, go to definition, etc. -const RESEND_API_KEY = 're_123456789'; +// Setup type definitions for built-in Supabase Runtime APIs +import "jsr:@supabase/functions-js/edge-runtime.d.ts" -const handler = async (_request: Request): Promise => { - const res = await fetch('https://api.resend.com/emails', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${RESEND_API_KEY}` - }, - body: JSON.stringify({ - from: 'Acme ', - to: ['delivered@resend.dev'], - subject: 'hello world', - html: 'it works!', - }) - }); +console.log("Hello from Functions!") - const data = await res.json(); +Deno.serve(async (req) => { + const { name } = await req.json() + const data = { + message: `Hello ${name}!`, + } - return new Response(JSON.stringify(data), { - status: 200, - headers: { - 'Content-Type': 'application/json', - }, - }); -}; + return new Response( + JSON.stringify(data), + { headers: { "Content-Type": "application/json" } }, + ) +}) -serve(handler); +/* To invoke locally: + + 1. Run `supabase start` (see: https://supabase.com/docs/reference/cli/supabase-start) + 2. Make an HTTP request: + + curl -i --location --request POST 'http://127.0.0.1:54321/functions/v1/resend' \ + --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0' \ + --header 'Content-Type: application/json' \ + --data '{"name":"Functions"}' + +*/ diff --git a/supabase/functions/send-email/_templates/email-user-invite.tsx b/supabase/functions/send-email/_templates/email-user-invite.tsx deleted file mode 100644 index ed08e5e..0000000 --- a/supabase/functions/send-email/_templates/email-user-invite.tsx +++ /dev/null @@ -1,154 +0,0 @@ -import { - Body, - Button, - Column, - Container, - Head, - Heading, - Hr, - Html, - Img, - Link, - Preview, - Row, - Section, - Tailwind, - Text, -} from "@react-email/components"; -import * as React from "react"; - -interface VercelInviteUserEmailProps { - username?: string; - userImage?: string; - invitedByUsername?: string; - invitedByEmail?: string; - teamName?: string; - teamImage?: string; - inviteLink?: string; - inviteFromIp?: string; - inviteFromLocation?: string; -} - -const baseUrl = process.env.VERCEL_URL - ? `https://${process.env.VERCEL_URL}` - : ""; - -export const VercelInviteUserEmail = ({ - username, - userImage, - invitedByUsername, - invitedByEmail, - teamName, - teamImage, - inviteLink, - inviteFromIp, - inviteFromLocation, -}: VercelInviteUserEmailProps) => { - const previewText = `Join ${invitedByUsername} on Vercel`; - - return ( - - - {previewText} - - - -
- Vercel -
- - Join {teamName} on Vercel - - - Hello {username}, - - - {invitedByUsername} ( - - {invitedByEmail} - - ) has invited you to the {teamName} team on{" "} - Vercel. - -
- - - - - - invited you to - - - - - -
-
- -
- - or copy and paste this URL into your browser:{" "} - - {inviteLink} - - -
- - This invitation was intended for{" "} - {username}. This invite was - sent from {inviteFromIp}{" "} - located in{" "} - {inviteFromLocation}. If you - were not expecting this invitation, you can ignore this email. If - you are concerned about your account's safety, please reply to - this email to get in touch with us. - -
- -
- - ); -}; - -VercelInviteUserEmail.PreviewProps = { - username: "alanturing", - userImage: `${baseUrl}/static/vercel-user.png`, - invitedByUsername: "Alan", - invitedByEmail: "alan.turing@example.com", - teamName: "Enigma", - teamImage: `${baseUrl}/static/vercel-team.png`, - inviteLink: "https://vercel.com/teams/invite/foo", - inviteFromIp: "204.13.186.218", - inviteFromLocation: "São Paulo, Brazil", -} as VercelInviteUserEmailProps; - -export default VercelInviteUserEmail; diff --git a/supabase/functions/send-email/_templates/magic-link.tsx b/supabase/functions/send-email/_templates/magic-link.tsx deleted file mode 100644 index db0c9cb..0000000 --- a/supabase/functions/send-email/_templates/magic-link.tsx +++ /dev/null @@ -1,130 +0,0 @@ -import { - Body, - Container, - Head, - Heading, - Html, - Link, - Preview, - Text, -} from "npm:@react-email/components@0.0.22"; -import * as React from "npm:react@18.3.1"; - -interface MagicLinkEmailProps { - supabase_url: string; - email_action_type: string; - redirect_to: string; - token_hash: string; - token: string; -} - -export const MagicLinkEmail = ({ - token, - supabase_url, - email_action_type, - redirect_to, - token_hash, -}: MagicLinkEmailProps) => ( - - - Log in with this magic link - - - Login - - Click here to log in with this magic link - - - Or, copy and paste this temporary login code: - - {token} - - If you didn't try to login, you can safely ignore this email. - - - - ACME Corp - - , the famouse demo corp. - - - - -); - -export default MagicLinkEmail; - -const main = { - backgroundColor: "#ffffff", -}; - -const container = { - paddingLeft: "12px", - paddingRight: "12px", - margin: "0 auto", -}; - -const h1 = { - color: "#333", - fontFamily: - "-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif", - fontSize: "24px", - fontWeight: "bold", - margin: "40px 0", - padding: "0", -}; - -const link = { - color: "#2754C5", - fontFamily: - "-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif", - fontSize: "14px", - textDecoration: "underline", -}; - -const text = { - color: "#333", - fontFamily: - "-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif", - fontSize: "14px", - margin: "24px 0", -}; - -const footer = { - color: "#898989", - fontFamily: - "-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif", - fontSize: "12px", - lineHeight: "22px", - marginTop: "12px", - marginBottom: "24px", -}; - -const code = { - display: "inline-block", - padding: "16px 4.5%", - width: "90.5%", - backgroundColor: "#f4f4f4", - borderRadius: "5px", - border: "1px solid #eee", - color: "#333", -}; diff --git a/supabase/functions/send-email/deno.json b/supabase/functions/send-email/deno.json index eb226b3..f6ca845 100644 --- a/supabase/functions/send-email/deno.json +++ b/supabase/functions/send-email/deno.json @@ -1,8 +1,3 @@ { - "imports": { - "react": "npm:react@18.3.1", - "@react-email/components": "npm:@react-email/components@0.0.22", - "standardwebhooks": "npm:standardwebhooks@1.0.0", - "resend": "npm:resend@4.0.0" - } + "imports": {} } diff --git a/supabase/functions/send-email/index.ts b/supabase/functions/send-email/index.ts index c70b96d..157ad6e 100644 --- a/supabase/functions/send-email/index.ts +++ b/supabase/functions/send-email/index.ts @@ -1,66 +1,44 @@ -// Follow this setup guide to integrate the Deno language server with your editor: -// https://deno.land/manual/getting_started/setup_your_environment -// This enables autocomplete, go to definition, etc. +import { Webhook } from "https://esm.sh/standardwebhooks@1.0.0"; +import { Resend } from "npm:resend"; -// Setup type definitions for built-in Supabase Runtime APIs -import "jsr:@supabase/functions-js/edge-runtime.d.ts" -import React from 'npm:react@18.3.1' -import { Webhook } from 'https://esm.sh/standardwebhooks@1.0.0' -import { Resend } from 'npm:resend@4.0.0' -import { renderAsync } from 'npm:@react-email/components@0.0.22' -import { MagicLinkEmail } from './_templates/magic-link.tsx' - -const resend = new Resend(Deno.env.get('RESEND_API_KEY') as string) -const hookSecret = Deno.env.get('SEND_EMAIL_HOOK_SECRET') as string +const resend = new Resend("re_WtXdegYe_Ey3yiShKfZZtjCyY1agkEaSi"); +const hookSecret = + "jeroAB/CXdS721OiHV0Ac0yRcxO7eNihgjblH62xMhLBNc6OwK3DQnkbrHjTlSw5anml2onNTolG3SzZ" as string; Deno.serve(async (req) => { - if (req.method !== 'POST') { - return new Response('not allowed', { status: 400 }) + if (req.method !== "POST") { + return new Response("not allowed", { status: 400 }); } - const payload = await req.text() - const headers = Object.fromEntries(req.headers) - const wh = new Webhook(hookSecret) + const payload = await req.text(); + const headers = Object.fromEntries(req.headers); + const wh = new Webhook(hookSecret); try { - const { - user, - email_data: { token, token_hash, redirect_to, email_action_type }, - } = wh.verify(payload, headers) as { + const { user, email_data } = wh.verify(payload, headers) as { user: { - email: string - } + email: string; + }; email_data: { - token: string - token_hash: string - redirect_to: string - email_action_type: string - site_url: string - token_new: string - token_hash_new: string - } - } - - const html = await renderAsync( - React.createElement(MagicLinkEmail, { - supabase_url: Deno.env.get('SUPABASE_URL') ?? '', - token, - token_hash, - redirect_to, - email_action_type, - }) - ) + token: string; + token_hash: string; + redirect_to: string; + email_action_type: string; + site_url: string; + token_new: string; + token_hash_new: string; + }; + }; const { error } = await resend.emails.send({ - from: 'welcome ', + from: "welcome ", to: [user.email], - subject: 'Supa Custom MagicLink!', - html, - }) + subject: "Welcome to my site!", + text: `Confirm you signup with this code: ${email_data.token}`, + }); if (error) { - throw error + throw error; } } catch (error) { - console.log(error) return new Response( JSON.stringify({ error: { @@ -70,27 +48,15 @@ Deno.serve(async (req) => { }), { status: 401, - headers: { 'Content-Type': 'application/json' }, + headers: { "Content-Type": "application/json" }, } - ) + ); } - const responseHeaders = new Headers() - responseHeaders.set('Content-Type', 'application/json') + const responseHeaders = new Headers(); + responseHeaders.set("Content-Type", "application/json"); return new Response(JSON.stringify({}), { status: 200, headers: responseHeaders, - }) -}) - -/* To invoke locally: - - 1. Run `supabase start` (see: https://supabase.com/docs/reference/cli/supabase-start) - 2. Make an HTTP request: - - curl -i --location --request POST 'http://127.0.0.1:54321/functions/v1/send-email' \ - --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0' \ - --header 'Content-Type: application/json' \ - --data '{"name":"Functions"}' - -*/ + }); +});