From dc3c0bebbb664ff152acad3dc4fb6faf9be599f8 Mon Sep 17 00:00:00 2001 From: vergiLgood1 Date: Sun, 2 Mar 2025 23:47:02 +0700 Subject: [PATCH] menampilkan user saat ini ke profile --- sigap-website/app/(auth-pages)/action.ts | 40 +++- .../app/protected/(admin)/dashboard/page.tsx | 1 - .../dashboard/user-management/action.ts | 198 ++++++++++++------ .../app/protected/(admin)/layout.tsx | 1 + sigap-website/app/protected/page.tsx | 16 +- .../components/admin/app-sidebar.tsx | 26 ++- .../admin/navigations/nav-report.tsx | 2 - .../components/admin/navigations/nav-user.tsx | 72 ++++--- sigap-website/lib/db.ts | 15 ++ sigap-website/package-lock.json | 43 ++-- sigap-website/package.json | 2 +- sigap-website/prisma/schema.prisma | 53 ++--- sigap-website/src/models/users/users.model.ts | 181 +++++++++++----- 13 files changed, 457 insertions(+), 193 deletions(-) create mode 100644 sigap-website/lib/db.ts diff --git a/sigap-website/app/(auth-pages)/action.ts b/sigap-website/app/(auth-pages)/action.ts index 95c002c..f5a8279 100644 --- a/sigap-website/app/(auth-pages)/action.ts +++ b/sigap-website/app/(auth-pages)/action.ts @@ -1,10 +1,12 @@ // src/app/(auth-pages)/actions.ts "use server"; - +import db from "@/lib/db"; import { SignInFormData } from "@/src/models/auth/sign-in.model"; import { VerifyOtpFormData } from "@/src/models/auth/verify-otp.model"; +import { User } from "@/src/models/users/users.model"; import { authRepository } from "@/src/repositories/authentication.repository"; +import { createClient } from "@/utils/supabase/server"; import { redirect } from "next/navigation"; export async function signIn( @@ -15,7 +17,7 @@ export async function signIn( return { success: true, message: "Check your email for the login link!", - redirectTo: result.redirectTo + redirectTo: result.redirectTo, }; } catch (error) { console.error("Authentication error:", error); @@ -37,7 +39,7 @@ export async function verifyOtp( return { success: true, message: "Successfully authenticated!", - redirectTo: result.redirectTo + redirectTo: result.redirectTo, }; } catch (error) { console.error("OTP verification error:", error); @@ -56,7 +58,7 @@ export async function signOut() { const result = await authRepository.signOut(); return { success: true, - redirectTo: result.redirectTo + redirectTo: result.redirectTo, }; } catch (error) { console.error("Sign out error:", error); @@ -69,3 +71,33 @@ export async function signOut() { }; } } + +// get current user +export async function getCurrentUser(): Promise { + const supabase = await createClient(); + + const { + data: { user }, + error, + } = await supabase.auth.getUser(); + + if (error) { + console.error("Error fetching current user:", error); + throw new Error(error.message); + } + + const userDetail = await db.users.findUnique({ + where: { + id: user?.id, + }, + include: { + profile: true, + }, + }); + + if (!userDetail) { + throw new Error("User not found"); + } + + return userDetail; +} diff --git a/sigap-website/app/protected/(admin)/dashboard/page.tsx b/sigap-website/app/protected/(admin)/dashboard/page.tsx index 105382e..2191616 100644 --- a/sigap-website/app/protected/(admin)/dashboard/page.tsx +++ b/sigap-website/app/protected/(admin)/dashboard/page.tsx @@ -1,5 +1,4 @@ - export default function DashboardPage() { return ( <> diff --git a/sigap-website/app/protected/(admin)/dashboard/user-management/action.ts b/sigap-website/app/protected/(admin)/dashboard/user-management/action.ts index 63373f5..635965a 100644 --- a/sigap-website/app/protected/(admin)/dashboard/user-management/action.ts +++ b/sigap-website/app/protected/(admin)/dashboard/user-management/action.ts @@ -1,158 +1,238 @@ -"use server" +"use server"; -import { CreateUserParams, InviteUserParams, UpdateUserParams, User } from "@/src/models/users/users.model" -import { createClient } from "@supabase/supabase-js" +import db from "@/lib/db"; +import { + CreateUserParams, + InviteUserParams, + UpdateUserParams, + User, + UserFromSupabase, +} from "@/src/models/users/users.model"; +import { createClient } from "@supabase/supabase-js"; +import { createClient as supabaseUser } from "@/utils/supabase/server"; // Initialize Supabase client with admin key -const supabase = createClient(process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.SERVICE_ROLE_SECRET!, { - auth: { - autoRefreshToken: false, - persistSession: false, - }, -}) // Fetch all users export async function fetchUsers(): Promise { - const { data, error } = await supabase.auth.admin.listUsers() + // const { data, error } = await supabase.auth.admin.listUsers(); - if (error) { - console.error("Error fetching users:", error) - throw new Error(error.message) + // if (error) { + // console.error("Error fetching users:", error); + // throw new Error(error.message); + // } + + // return data.users.map((user) => ({ + // ...user, + // })) as User[]; + + const users = await db.users.findMany({ + include: { + profile: true, + }, + }); + + if (!users) { + throw new Error("Users not found"); } - return data.users.map(user => ({ - ...user, - updated_at: user.updated_at || "", - })) as User[] + return users; +} + +// get current user +export async function getCurrentUser(): Promise { + const supabase = await supabaseUser(); + + const { + data: { user }, + error, + } = await supabase.auth.getUser(); + + if (error) { + console.error("Error fetching current user:", error); + throw new Error(error.message); + } + + const userDetail = await db.users.findUnique({ + where: { + id: user?.id, + }, + include: { + profile: true, + }, + }); + + if (!userDetail) { + throw new Error("User not found"); + } + + return userDetail; } // Create a new user -export async function createUser(params: CreateUserParams): Promise { +export async function createUser( + params: CreateUserParams +): Promise { + const supabase = createClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.SERVICE_ROLE_SECRET! + ); + const { data, error } = await supabase.auth.admin.createUser({ email: params.email, password: params.password, phone: params.phone, - user_metadata: params.user_metadata, email_confirm: params.email_confirm, - }) + }); if (error) { - console.error("Error creating user:", error) - throw new Error(error.message) + console.error("Error creating user:", error); + throw new Error(error.message); } return { ...data.user, - updated_at: data.user.updated_at || "", - } as User + }; } // Update an existing user -export async function updateUser(userId: string, params: UpdateUserParams): Promise { +export async function updateUser( + userId: string, + params: UpdateUserParams +): Promise { + const supabase = createClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.SERVICE_ROLE_SECRET! + ); + const { data, error } = await supabase.auth.admin.updateUserById(userId, { email: params.email, phone: params.phone, password: params.password, - user_metadata: params.user_metadata, - }) + }); if (error) { - console.error("Error updating user:", error) - throw new Error(error.message) + console.error("Error updating user:", error); + throw new Error(error.message); } return { ...data.user, - updated_at: data.user.updated_at || "", - } as User + }; } // Delete a user export async function deleteUser(userId: string): Promise { - const { error } = await supabase.auth.admin.deleteUser(userId) + const supabase = createClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.SERVICE_ROLE_SECRET! + ); + + const { error } = await supabase.auth.admin.deleteUser(userId); if (error) { - console.error("Error deleting user:", error) - throw new Error(error.message) + console.error("Error deleting user:", error); + throw new Error(error.message); } } // Send password recovery email export async function sendPasswordRecovery(email: string): Promise { + const supabase = createClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.SERVICE_ROLE_SECRET! + ); + const { error } = await supabase.auth.resetPasswordForEmail(email, { redirectTo: `${process.env.NEXT_PUBLIC_SITE_URL}/auth/reset-password`, - }) + }); if (error) { - console.error("Error sending password recovery:", error) - throw new Error(error.message) + console.error("Error sending password recovery:", error); + throw new Error(error.message); } } // Send magic link export async function sendMagicLink(email: string): Promise { + const supabase = createClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.SERVICE_ROLE_SECRET! + ); + const { error } = await supabase.auth.signInWithOtp({ email, options: { emailRedirectTo: `${process.env.NEXT_PUBLIC_SITE_URL}/auth/callback`, }, - }) + }); if (error) { - console.error("Error sending magic link:", error) - throw new Error(error.message) + console.error("Error sending magic link:", error); + throw new Error(error.message); } } // Ban a user -export async function banUser(userId: string): Promise { +export async function banUser(userId: string): Promise { + const supabase = createClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.SERVICE_ROLE_SECRET! + ); + // Ban for 100 years (effectively permanent) - const banUntil = new Date() - banUntil.setFullYear(banUntil.getFullYear() + 100) + const banUntil = new Date(); + banUntil.setFullYear(banUntil.getFullYear() + 100); const { data, error } = await supabase.auth.admin.updateUserById(userId, { ban_duration: "100h", - }) + }); if (error) { - console.error("Error banning user:", error) - throw new Error(error.message) + console.error("Error banning user:", error); + throw new Error(error.message); } return { ...data.user, - updated_at: data.user.updated_at || "", - } as User + }; } // Unban a user -export async function unbanUser(userId: string): Promise { +export async function unbanUser(userId: string): Promise { + const supabase = createClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.SERVICE_ROLE_SECRET! + ); + const { data, error } = await supabase.auth.admin.updateUserById(userId, { ban_duration: "none", - }) + }); if (error) { - console.error("Error unbanning user:", error) - throw new Error(error.message) + console.error("Error unbanning user:", error); + throw new Error(error.message); } return { ...data.user, - updated_at: data.user.updated_at || "", - } as User + }; } // Invite a user export async function inviteUser(params: InviteUserParams): Promise { + const supabase = createClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.SERVICE_ROLE_SECRET! + ); + const { error } = await supabase.auth.admin.inviteUserByEmail(params.email, { - data: params.user_metadata, redirectTo: `${process.env.NEXT_PUBLIC_SITE_URL}/auth/callback`, - }) + }); if (error) { - console.error("Error inviting user:", error) - throw new Error(error.message) + console.error("Error inviting user:", error); + throw new Error(error.message); } } - diff --git a/sigap-website/app/protected/(admin)/layout.tsx b/sigap-website/app/protected/(admin)/layout.tsx index a9b246f..7a1272e 100644 --- a/sigap-website/app/protected/(admin)/layout.tsx +++ b/sigap-website/app/protected/(admin)/layout.tsx @@ -34,6 +34,7 @@ export default async function Layout({ }: { children: React.ReactNode; }) { + return ( <> diff --git a/sigap-website/app/protected/page.tsx b/sigap-website/app/protected/page.tsx index 5508aba..6ebbe83 100644 --- a/sigap-website/app/protected/page.tsx +++ b/sigap-website/app/protected/page.tsx @@ -1,4 +1,5 @@ import FetchDataSteps from "@/components/tutorial/fetch-data-steps"; +import db from "@/lib/db"; import { createClient } from "@/utils/supabase/server"; import { InfoIcon } from "lucide-react"; import { redirect } from "next/navigation"; @@ -14,6 +15,13 @@ export default async function ProtectedPage() { return redirect("/sign-in"); } + const userDetail = await db.users.findUnique({ + where: { + id: user.id, + }, + + }); + return (
@@ -23,11 +31,15 @@ export default async function ProtectedPage() { user
-
+

Your user details

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

Next steps

diff --git a/sigap-website/components/admin/app-sidebar.tsx b/sigap-website/components/admin/app-sidebar.tsx index 89388b6..ac92040 100644 --- a/sigap-website/components/admin/app-sidebar.tsx +++ b/sigap-website/components/admin/app-sidebar.tsx @@ -6,7 +6,6 @@ import { NavMain } from "@/components/admin/navigations/nav-main"; import { NavReports } from "@/components/admin/navigations/nav-report"; import { NavUser } from "@/components/admin/navigations/nav-user"; - import { Sidebar, SidebarContent, @@ -18,7 +17,30 @@ import { NavPreMain } from "./navigations/nav-pre-main"; import { navData } from "@/prisma/data/nav"; import { TeamSwitcher } from "../team-switcher"; +import { Profile, User } from "@/src/models/users/users.model"; +import { getCurrentUser } from "@/app/protected/(admin)/dashboard/user-management/action"; + + export function AppSidebar({ ...props }: React.ComponentProps) { + const [user, setUser] = React.useState(null); + const [isLoading, setIsLoading] = React.useState(true); + + React.useEffect(() => { + async function fetchUser() { + try { + setIsLoading(true); + const userData = await getCurrentUser(); + setUser(userData); + } catch (error) { + console.error("Failed to fetch user:", error); + } finally { + setIsLoading(false); + } + } + + fetchUser(); + }, []); + return ( @@ -30,7 +52,7 @@ export function AppSidebar({ ...props }: React.ComponentProps) { - + diff --git a/sigap-website/components/admin/navigations/nav-report.tsx b/sigap-website/components/admin/navigations/nav-report.tsx index 03a090a..3a967d0 100644 --- a/sigap-website/components/admin/navigations/nav-report.tsx +++ b/sigap-website/components/admin/navigations/nav-report.tsx @@ -1,5 +1,3 @@ -"use client"; - import { Folder, Forward, diff --git a/sigap-website/components/admin/navigations/nav-user.tsx b/sigap-website/components/admin/navigations/nav-user.tsx index 5fee84b..3c97646 100644 --- a/sigap-website/components/admin/navigations/nav-user.tsx +++ b/sigap-website/components/admin/navigations/nav-user.tsx @@ -1,19 +1,8 @@ "use client"; -import { - BadgeCheck, - Bell, - ChevronsUpDown, - CreditCard, - LogOut, - Sparkles, -} from "lucide-react"; +import { ChevronsUpDown } from "lucide-react"; -import { - Avatar, - AvatarFallback, - AvatarImage, -} from "@/components/ui/avatar"; +import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { DropdownMenu, DropdownMenuContent, @@ -36,18 +25,35 @@ import { IconLogout, IconSparkles, } from "@tabler/icons-react"; +import { Profile, User } from "@/src/models/users/users.model"; -export function NavUser({ - user, -}: { - user: { - name: string; - email: string; - avatar: string; - }; -}) { +export function NavUser({ user }: { user: User | null }) { const { isMobile } = useSidebar(); + // Use profile data with fallbacks + const firstName = user?.profile?.first_name || ""; + const lastName = user?.profile?.last_name || ""; + const userEmail = user?.email || ""; + const userAvatar = user?.profile?.avatar || ""; + + const getFullName = () => { + return `${firstName} ${lastName}`.trim() || "User"; + }; + + // Generate initials for avatar fallback + const getInitials = () => { + if (firstName && lastName) { + return `${firstName[0]}${lastName[0]}`.toUpperCase(); + } + if (firstName) { + return firstName[0].toUpperCase(); + } + if (userEmail) { + return userEmail[0].toUpperCase(); + } + return "U"; + }; + return ( @@ -58,12 +64,14 @@ export function NavUser({ className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground" > - - CN + + + {getInitials()} +
- {user.name} - {user.email} + {getFullName()} + {userEmail}
@@ -77,12 +85,16 @@ export function NavUser({
- - CN + + + {getInitials()} +
- {user.name} - {user.email} + + {getFullName()} + + {userEmail}
diff --git a/sigap-website/lib/db.ts b/sigap-website/lib/db.ts new file mode 100644 index 0000000..0e2ce72 --- /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 db = globalThis.prismaGlobal ?? prismaClientSingleton(); + +export default db; + +if (process.env.NODE_ENV !== "production") globalThis.prismaGlobal = db; diff --git a/sigap-website/package-lock.json b/sigap-website/package-lock.json index 7d88891..6442925 100644 --- a/sigap-website/package-lock.json +++ b/sigap-website/package-lock.json @@ -22,7 +22,7 @@ "@radix-ui/react-switch": "^1.1.3", "@radix-ui/react-tabs": "^1.1.3", "@radix-ui/react-tooltip": "^1.1.8", - "@supabase/ssr": "latest", + "@supabase/ssr": "^0.4.1", "@supabase/supabase-js": "latest", "@tabler/icons-react": "^3.30.0", "@tanstack/react-query": "^5.66.9", @@ -2588,6 +2588,19 @@ "react-dom": "^18.0 || ^19.0 || ^19.0.0-rc" } }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.9.tgz", + "integrity": "sha512-FwBHNSOjUTQLP4MG7y6rR6qbGw4MFeQnIBrMe161QGaQoBQLqSUEKlHIiVgF3g/mb3lxlxzJOpIBhaP+C+KP2A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@selderee/plugin-htmlparser2": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@selderee/plugin-htmlparser2/-/plugin-htmlparser2-0.11.0.tgz", @@ -2666,18 +2679,29 @@ } }, "node_modules/@supabase/ssr": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@supabase/ssr/-/ssr-0.5.2.tgz", - "integrity": "sha512-n3plRhr2Bs8Xun1o4S3k1CDv17iH5QY9YcoEvXX3bxV1/5XSasA0mNXYycFmADIdtdE6BG9MRjP5CGIs8qxC8A==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@supabase/ssr/-/ssr-0.4.1.tgz", + "integrity": "sha512-000i7y4ITXjXU0T1JytZYU33VbUNklX9YN47hCweaLKsTBAEigJJJCeq3G+/IiwEggBt58Vu0KQ3UGXON7OmDQ==", "license": "MIT", "dependencies": { - "@types/cookie": "^0.6.0", - "cookie": "^0.7.0" + "cookie": "^0.6.0" + }, + "optionalDependencies": { + "@rollup/rollup-linux-x64-gnu": "^4.9.5" }, "peerDependencies": { "@supabase/supabase-js": "^2.43.4" } }, + "node_modules/@supabase/ssr/node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/@supabase/storage-js": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.7.1.tgz", @@ -2829,12 +2853,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", - "license": "MIT" - }, "node_modules/@types/cors": { "version": "2.8.17", "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", @@ -3429,6 +3447,7 @@ "version": "0.7.2", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" diff --git a/sigap-website/package.json b/sigap-website/package.json index 7de0b6e..8400e27 100644 --- a/sigap-website/package.json +++ b/sigap-website/package.json @@ -27,7 +27,7 @@ "@radix-ui/react-switch": "^1.1.3", "@radix-ui/react-tabs": "^1.1.3", "@radix-ui/react-tooltip": "^1.1.8", - "@supabase/ssr": "latest", + "@supabase/ssr": "^0.4.1", "@supabase/supabase-js": "latest", "@tabler/icons-react": "^3.30.0", "@tanstack/react-query": "^5.66.9", diff --git a/sigap-website/prisma/schema.prisma b/sigap-website/prisma/schema.prisma index 3f3cf5b..fff891f 100644 --- a/sigap-website/prisma/schema.prisma +++ b/sigap-website/prisma/schema.prisma @@ -133,6 +133,7 @@ model profiles { id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid user_id String @unique @db.Uuid avatar String? @db.VarChar(255) + username String? @unique @db.VarChar(255) first_name String? @db.VarChar(255) last_name String? @db.VarChar(255) bio String? @db.VarChar @@ -141,41 +142,31 @@ model profiles { users users @relation(fields: [user_id], references: [id]) @@index([user_id]) + @@index([username]) } model users { - id String @id @db.Uuid - email String @unique @db.VarChar(255) - email_verified Boolean @default(false) - first_name String? @db.VarChar(255) - last_name String? @db.VarChar(255) - avatar String? @db.VarChar(255) - role roles @default(user) - created_at DateTime @default(now()) - updated_at DateTime - banned_until DateTime? - confirmation_sent_at DateTime? - confirmation_token String? @db.VarChar(255) - deleted_at DateTime? - email_change String? @db.VarChar(255) - email_change_sent_at DateTime? - email_change_token String? @db.VarChar(255) - email_confirmed_at DateTime? - encrypted_password String? @db.VarChar(255) - is_anonymous Boolean? @default(false) - is_sso_user Boolean? @default(false) - last_sign_in_at DateTime? - phone String? @db.VarChar(20) - phone_confirmed_at DateTime? - raw_app_meta_data Json? - raw_user_meta_data Json? - reauthentication_sent_at DateTime? - reauthentication_token String? @db.VarChar(255) - recovery_sent_at DateTime? - recovery_token String? @db.VarChar(255) - providers Json? @default("[]") - profiles profiles? + id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid + role roles @default(user) + email String @unique @db.VarChar(255) + phone String? @unique @db.VarChar(20) + encrypted_password String? @db.VarChar(255) + invited_at DateTime? @db.Timestamptz(6) + confirmed_at DateTime? @db.Timestamptz(6) + email_confirmed_at DateTime? @db.Timestamptz(6) + recovery_sent_at DateTime? @db.Timestamptz(6) + last_sign_in_at DateTime? @db.Timestamptz(6) + app_metadata Json? + user_metadata Json? + created_at DateTime @default(now()) @db.Timestamptz(6) + updated_at DateTime @default(now()) @db.Timestamptz(6) + banned_until DateTime? @db.Timestamptz(6) + is_anonymous Boolean @default(false) + profile profiles? + @@index([is_anonymous]) + @@index([created_at]) + @@index([updated_at]) @@index([role]) } diff --git a/sigap-website/src/models/users/users.model.ts b/sigap-website/src/models/users/users.model.ts index b38df47..546dfd3 100644 --- a/sigap-website/src/models/users/users.model.ts +++ b/sigap-website/src/models/users/users.model.ts @@ -1,53 +1,136 @@ -export interface User { - id: string; - email?: string; - phone?: string; - created_at: string; - updated_at: string; - last_sign_in_at?: string; - email_confirmed_at?: string; - phone_confirmed_at?: string; - invited_at?: string; - confirmation_sent_at?: string; - banned_until?: string; - factors?: { - id: string; - factor_type: string; - created_at: string; - updated_at: string; - }[]; - raw_user_meta_data?: Record; - raw_app_meta_data?: Record; - profile?: Profile; -} +import { z } from "zod"; -export interface CreateUserParams { - email: string; - password: string; - phone?: string; - user_metadata?: Record; - email_confirm?: boolean; -} +import { JsonValue } from "@prisma/client/runtime/library"; +import { roles } from "@prisma/client"; +import { AuthError } from "@supabase/supabase-js"; -export interface UpdateUserParams { - email?: string; - phone?: string; - password?: string; - user_metadata?: Record; -} +const timestampSchema = z.date().nullable(); -export interface InviteUserParams { - email: string; - user_metadata?: Record; -} +const AppMetadataSchema = z + .object({ + provider: z.string(), + providers: z.array(z.string()), + }) + .nullable(); -export interface Profile { - id: string; - user_id: string; - avatar?: string; - first_name?: string; - last_name?: string; - bio: string; - address?: string; - birthdate?: string; -} +const UserMetadataSchema = z.object({ + email_verified: z.boolean().optional(), +}); + +const IdentityDataSchema = z.object({ + email: z.string().email(), + email_verified: z.boolean(), + phone_verified: z.boolean(), + sub: z.string(), +}); + +const IdentitySchema = z.object({ + identity_id: z.string(), + id: z.string(), + user_id: z.string(), + identity_data: IdentityDataSchema, + provider: z.string(), + last_sign_in_at: timestampSchema, + created_at: timestampSchema, + updated_at: timestampSchema, + email: z.string().email(), +}); + +export const UserSchema = z.object({ + id: z.string(), + role: z.string(), + email: z.string().email(), + email_confirmed_at: timestampSchema, + invited_at: timestampSchema, + phone: z.string().nullable(), + confirmed_at: timestampSchema, + recovery_sent_at: timestampSchema, + last_sign_in_at: timestampSchema, + created_at: timestampSchema, + updated_at: timestampSchema, + is_anonymous: z.boolean(), + // Keep profile as optional since it's not in the response + profile: z + .object({ + id: z.string(), + user_id: z.string(), + avatar: z.string().nullable(), + first_name: z.string().nullable(), + last_name: z.string().nullable(), + bio: z.string().nullable(), + address: z.any().nullable(), + birth_date: timestampSchema, + }) + .nullable(), +}); + +export type User = z.infer; + +export const UserSupabaseSchema = z.object({ + id: z.string(), + aud: z.string(), + role: z.string().optional(), + email: z.string().email().optional(), + email_confirmed_at: z.string().optional(), + invited_at: z.string().optional(), + phone: z.string().optional(), + confirmed_at: z.string().optional(), + recovery_sent_at: z.string().optional(), + last_sign_in_at: z.string().optional(), + created_at: z.string(), + updated_at: z.string().optional(), + is_anonymous: z.boolean().optional(), +}); + +export type UserFromSupabase = z.infer; + +// Type helper + +export const ProfileSchema = z.object({ + id: z.string(), + user_id: z.string(), + avatar: z.string().optional(), + first_name: z.string().optional(), + last_name: z.string().optional(), + bio: z.string(), + address: z.string().optional(), + birthdate: z.string().optional(), +}); + +export type Profile = z.infer; + +export const CreateUserParamsSchema = z.object({ + email: z.string().email(), + password: z.string(), + phone: z.string().optional(), + user_metadata: z.record(z.any()).optional(), + email_confirm: z.boolean().optional(), +}); + +export const UpdateUserParamsSchema = z.object({ + email: z.string().email().optional(), + phone: z.string().optional(), + password: z.string().optional(), +}); + +export const InviteUserParamsSchema = z.object({ + email: z.string().email(), +}); + +export type UserResponse = + | { + data: { + user: User; + }; + error: null; + } + | { + data: { + user: null; + }; + error: AuthError; + }; + +export type CreateUserParams = z.infer; +export type UpdateUserParams = z.infer; +export type InviteUserParams = z.infer;