@@ -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({
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 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}
-
-
-
-
-
-
-
- Join {teamName} on Vercel
-
-
- Hello {username},
-
-
- {invitedByUsername} (
-
- {invitedByEmail}
-
- ) has invited you to the {teamName} team on{" "}
- Vercel .
-
-
-
-
- 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"}'
-
-*/
+ });
+});