menampilkan user saat ini ke profile
This commit is contained in:
parent
f00544cb28
commit
dc3c0bebbb
|
@ -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<User> {
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
|
||||
|
||||
export default function DashboardPage() {
|
||||
return (
|
||||
<>
|
||||
|
|
|
@ -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<User[]> {
|
||||
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<User> {
|
||||
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<User> {
|
||||
export async function createUser(
|
||||
params: CreateUserParams
|
||||
): Promise<UserFromSupabase> {
|
||||
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<User> {
|
||||
export async function updateUser(
|
||||
userId: string,
|
||||
params: UpdateUserParams
|
||||
): Promise<UserFromSupabase> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<User> {
|
||||
export async function banUser(userId: string): Promise<UserFromSupabase> {
|
||||
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<User> {
|
||||
export async function unbanUser(userId: string): Promise<UserFromSupabase> {
|
||||
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<void> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@ export default async function Layout({
|
|||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<SidebarProvider>
|
||||
|
|
|
@ -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 (
|
||||
<div className="flex-1 w-full flex flex-col gap-12">
|
||||
<div className="w-full">
|
||||
|
@ -23,11 +31,15 @@ export default async function ProtectedPage() {
|
|||
user
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2 items-start">
|
||||
<div className="flex gap-2 items-start">
|
||||
<h2 className="font-bold text-2xl mb-4">Your user details</h2>
|
||||
<pre className="text-xs font-mono p-3 rounded border max-h-32 overflow-auto">
|
||||
<pre className="text-xs font-mono p-3 rounded border overflow-auto">
|
||||
{JSON.stringify(userDetail, null, 2)}
|
||||
</pre>
|
||||
<pre className="text-xs font-mono p-3 rounded border overflow-auto">
|
||||
{JSON.stringify(user, null, 2)}
|
||||
</pre>
|
||||
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="font-bold text-2xl mb-4">Next steps</h2>
|
||||
|
|
|
@ -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<typeof Sidebar>) {
|
||||
const [user, setUser] = React.useState<User | null>(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 (
|
||||
<Sidebar collapsible="icon" {...props}>
|
||||
<SidebarHeader>
|
||||
|
@ -30,7 +52,7 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
|||
<NavReports reports={navData.reports} />
|
||||
</SidebarContent>
|
||||
<SidebarFooter>
|
||||
<NavUser user={navData.user} />
|
||||
<NavUser user={user} />
|
||||
</SidebarFooter>
|
||||
<SidebarRail />
|
||||
</Sidebar>
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
"use client";
|
||||
|
||||
import {
|
||||
Folder,
|
||||
Forward,
|
||||
|
|
|
@ -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 (
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem>
|
||||
|
@ -58,12 +64,14 @@ export function NavUser({
|
|||
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
|
||||
>
|
||||
<Avatar className="h-8 w-8 rounded-lg">
|
||||
<AvatarImage src={user.avatar} alt={user.name} />
|
||||
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
|
||||
<AvatarImage src={userAvatar || ""} alt={getFullName()} />
|
||||
<AvatarFallback className="rounded-lg">
|
||||
{getInitials()}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||
<span className="truncate font-semibold">{user.name}</span>
|
||||
<span className="truncate text-xs">{user.email}</span>
|
||||
<span className="truncate font-semibold">{getFullName()}</span>
|
||||
<span className="truncate text-xs">{userEmail}</span>
|
||||
</div>
|
||||
<ChevronsUpDown className="ml-auto size-4" />
|
||||
</SidebarMenuButton>
|
||||
|
@ -77,12 +85,16 @@ export function NavUser({
|
|||
<DropdownMenuLabel className="p-0 font-normal">
|
||||
<div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
|
||||
<Avatar className="h-8 w-8 rounded-lg">
|
||||
<AvatarImage src={user.avatar} alt={user.name} />
|
||||
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
|
||||
<AvatarImage src={userAvatar || ""} alt={getFullName()} />
|
||||
<AvatarFallback className="rounded-lg">
|
||||
{getInitials()}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||
<span className="truncate font-semibold">{user.name}</span>
|
||||
<span className="truncate text-xs">{user.email}</span>
|
||||
<span className="truncate font-semibold">
|
||||
{getFullName()}
|
||||
</span>
|
||||
<span className="truncate text-xs">{userEmail}</span>
|
||||
</div>
|
||||
</div>
|
||||
</DropdownMenuLabel>
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
import { PrismaClient } from "@prisma/client";
|
||||
|
||||
const prismaClientSingleton = () => {
|
||||
return new PrismaClient();
|
||||
};
|
||||
|
||||
declare const globalThis: {
|
||||
prismaGlobal: ReturnType<typeof prismaClientSingleton>;
|
||||
} & typeof global;
|
||||
|
||||
const db = globalThis.prismaGlobal ?? prismaClientSingleton();
|
||||
|
||||
export default db;
|
||||
|
||||
if (process.env.NODE_ENV !== "production") globalThis.prismaGlobal = db;
|
|
@ -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"
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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])
|
||||
}
|
||||
|
||||
|
|
|
@ -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<string, any>;
|
||||
raw_app_meta_data?: Record<string, any>;
|
||||
profile?: Profile;
|
||||
}
|
||||
import { z } from "zod";
|
||||
|
||||
export interface CreateUserParams {
|
||||
email: string;
|
||||
password: string;
|
||||
phone?: string;
|
||||
user_metadata?: Record<string, any>;
|
||||
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<string, any>;
|
||||
}
|
||||
const timestampSchema = z.date().nullable();
|
||||
|
||||
export interface InviteUserParams {
|
||||
email: string;
|
||||
user_metadata?: Record<string, any>;
|
||||
}
|
||||
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<typeof UserSchema>;
|
||||
|
||||
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<typeof UserSupabaseSchema>;
|
||||
|
||||
// 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<typeof ProfileSchema>;
|
||||
|
||||
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<typeof CreateUserParamsSchema>;
|
||||
export type UpdateUserParams = z.infer<typeof UpdateUserParamsSchema>;
|
||||
export type InviteUserParams = z.infer<typeof InviteUserParamsSchema>;
|
||||
|
|
Loading…
Reference in New Issue