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
|
// src/app/(auth-pages)/actions.ts
|
||||||
"use server";
|
"use server";
|
||||||
|
|
||||||
|
import db from "@/lib/db";
|
||||||
import { SignInFormData } from "@/src/models/auth/sign-in.model";
|
import { SignInFormData } from "@/src/models/auth/sign-in.model";
|
||||||
import { VerifyOtpFormData } from "@/src/models/auth/verify-otp.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 { authRepository } from "@/src/repositories/authentication.repository";
|
||||||
|
import { createClient } from "@/utils/supabase/server";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
|
|
||||||
export async function signIn(
|
export async function signIn(
|
||||||
|
@ -15,7 +17,7 @@ export async function signIn(
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
message: "Check your email for the login link!",
|
message: "Check your email for the login link!",
|
||||||
redirectTo: result.redirectTo
|
redirectTo: result.redirectTo,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Authentication error:", error);
|
console.error("Authentication error:", error);
|
||||||
|
@ -37,7 +39,7 @@ export async function verifyOtp(
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
message: "Successfully authenticated!",
|
message: "Successfully authenticated!",
|
||||||
redirectTo: result.redirectTo
|
redirectTo: result.redirectTo,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("OTP verification error:", error);
|
console.error("OTP verification error:", error);
|
||||||
|
@ -56,7 +58,7 @@ export async function signOut() {
|
||||||
const result = await authRepository.signOut();
|
const result = await authRepository.signOut();
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
redirectTo: result.redirectTo
|
redirectTo: result.redirectTo,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Sign out error:", 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() {
|
export default function DashboardPage() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
|
@ -1,158 +1,238 @@
|
||||||
"use server"
|
"use server";
|
||||||
|
|
||||||
import { CreateUserParams, InviteUserParams, UpdateUserParams, User } from "@/src/models/users/users.model"
|
import db from "@/lib/db";
|
||||||
import { createClient } from "@supabase/supabase-js"
|
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
|
// 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
|
// Fetch all users
|
||||||
export async function fetchUsers(): Promise<User[]> {
|
export async function fetchUsers(): Promise<User[]> {
|
||||||
const { data, error } = await supabase.auth.admin.listUsers()
|
// const { data, error } = await supabase.auth.admin.listUsers();
|
||||||
|
|
||||||
if (error) {
|
// if (error) {
|
||||||
console.error("Error fetching users:", error)
|
// console.error("Error fetching users:", error);
|
||||||
throw new Error(error.message)
|
// 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 => ({
|
return users;
|
||||||
...user,
|
}
|
||||||
updated_at: user.updated_at || "",
|
|
||||||
})) as User[]
|
// 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
|
// 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({
|
const { data, error } = await supabase.auth.admin.createUser({
|
||||||
email: params.email,
|
email: params.email,
|
||||||
password: params.password,
|
password: params.password,
|
||||||
phone: params.phone,
|
phone: params.phone,
|
||||||
user_metadata: params.user_metadata,
|
|
||||||
email_confirm: params.email_confirm,
|
email_confirm: params.email_confirm,
|
||||||
})
|
});
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error("Error creating user:", error)
|
console.error("Error creating user:", error);
|
||||||
throw new Error(error.message)
|
throw new Error(error.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...data.user,
|
...data.user,
|
||||||
updated_at: data.user.updated_at || "",
|
};
|
||||||
} as User
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update an existing 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, {
|
const { data, error } = await supabase.auth.admin.updateUserById(userId, {
|
||||||
email: params.email,
|
email: params.email,
|
||||||
phone: params.phone,
|
phone: params.phone,
|
||||||
password: params.password,
|
password: params.password,
|
||||||
user_metadata: params.user_metadata,
|
});
|
||||||
})
|
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error("Error updating user:", error)
|
console.error("Error updating user:", error);
|
||||||
throw new Error(error.message)
|
throw new Error(error.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...data.user,
|
...data.user,
|
||||||
updated_at: data.user.updated_at || "",
|
};
|
||||||
} as User
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete a user
|
// Delete a user
|
||||||
export async function deleteUser(userId: string): Promise<void> {
|
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) {
|
if (error) {
|
||||||
console.error("Error deleting user:", error)
|
console.error("Error deleting user:", error);
|
||||||
throw new Error(error.message)
|
throw new Error(error.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send password recovery email
|
// Send password recovery email
|
||||||
export async function sendPasswordRecovery(email: string): Promise<void> {
|
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, {
|
const { error } = await supabase.auth.resetPasswordForEmail(email, {
|
||||||
redirectTo: `${process.env.NEXT_PUBLIC_SITE_URL}/auth/reset-password`,
|
redirectTo: `${process.env.NEXT_PUBLIC_SITE_URL}/auth/reset-password`,
|
||||||
})
|
});
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error("Error sending password recovery:", error)
|
console.error("Error sending password recovery:", error);
|
||||||
throw new Error(error.message)
|
throw new Error(error.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send magic link
|
// Send magic link
|
||||||
export async function sendMagicLink(email: string): Promise<void> {
|
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({
|
const { error } = await supabase.auth.signInWithOtp({
|
||||||
email,
|
email,
|
||||||
options: {
|
options: {
|
||||||
emailRedirectTo: `${process.env.NEXT_PUBLIC_SITE_URL}/auth/callback`,
|
emailRedirectTo: `${process.env.NEXT_PUBLIC_SITE_URL}/auth/callback`,
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error("Error sending magic link:", error)
|
console.error("Error sending magic link:", error);
|
||||||
throw new Error(error.message)
|
throw new Error(error.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ban a user
|
// 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)
|
// Ban for 100 years (effectively permanent)
|
||||||
const banUntil = new Date()
|
const banUntil = new Date();
|
||||||
banUntil.setFullYear(banUntil.getFullYear() + 100)
|
banUntil.setFullYear(banUntil.getFullYear() + 100);
|
||||||
|
|
||||||
const { data, error } = await supabase.auth.admin.updateUserById(userId, {
|
const { data, error } = await supabase.auth.admin.updateUserById(userId, {
|
||||||
ban_duration: "100h",
|
ban_duration: "100h",
|
||||||
})
|
});
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error("Error banning user:", error)
|
console.error("Error banning user:", error);
|
||||||
throw new Error(error.message)
|
throw new Error(error.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...data.user,
|
...data.user,
|
||||||
updated_at: data.user.updated_at || "",
|
};
|
||||||
} as User
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unban a 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, {
|
const { data, error } = await supabase.auth.admin.updateUserById(userId, {
|
||||||
ban_duration: "none",
|
ban_duration: "none",
|
||||||
})
|
});
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error("Error unbanning user:", error)
|
console.error("Error unbanning user:", error);
|
||||||
throw new Error(error.message)
|
throw new Error(error.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...data.user,
|
...data.user,
|
||||||
updated_at: data.user.updated_at || "",
|
};
|
||||||
} as User
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Invite a user
|
// Invite a user
|
||||||
export async function inviteUser(params: InviteUserParams): Promise<void> {
|
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, {
|
const { error } = await supabase.auth.admin.inviteUserByEmail(params.email, {
|
||||||
data: params.user_metadata,
|
|
||||||
redirectTo: `${process.env.NEXT_PUBLIC_SITE_URL}/auth/callback`,
|
redirectTo: `${process.env.NEXT_PUBLIC_SITE_URL}/auth/callback`,
|
||||||
})
|
});
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error("Error inviting user:", error)
|
console.error("Error inviting user:", error);
|
||||||
throw new Error(error.message)
|
throw new Error(error.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,7 @@ export default async function Layout({
|
||||||
}: {
|
}: {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}) {
|
}) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SidebarProvider>
|
<SidebarProvider>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import FetchDataSteps from "@/components/tutorial/fetch-data-steps";
|
import FetchDataSteps from "@/components/tutorial/fetch-data-steps";
|
||||||
|
import db from "@/lib/db";
|
||||||
import { createClient } from "@/utils/supabase/server";
|
import { createClient } from "@/utils/supabase/server";
|
||||||
import { InfoIcon } from "lucide-react";
|
import { InfoIcon } from "lucide-react";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
|
@ -14,6 +15,13 @@ export default async function ProtectedPage() {
|
||||||
return redirect("/sign-in");
|
return redirect("/sign-in");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const userDetail = await db.users.findUnique({
|
||||||
|
where: {
|
||||||
|
id: user.id,
|
||||||
|
},
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex-1 w-full flex flex-col gap-12">
|
<div className="flex-1 w-full flex flex-col gap-12">
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
|
@ -23,11 +31,15 @@ export default async function ProtectedPage() {
|
||||||
user
|
user
|
||||||
</div>
|
</div>
|
||||||
</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>
|
<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)}
|
{JSON.stringify(user, null, 2)}
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h2 className="font-bold text-2xl mb-4">Next steps</h2>
|
<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 { NavReports } from "@/components/admin/navigations/nav-report";
|
||||||
import { NavUser } from "@/components/admin/navigations/nav-user";
|
import { NavUser } from "@/components/admin/navigations/nav-user";
|
||||||
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Sidebar,
|
Sidebar,
|
||||||
SidebarContent,
|
SidebarContent,
|
||||||
|
@ -18,7 +17,30 @@ import { NavPreMain } from "./navigations/nav-pre-main";
|
||||||
import { navData } from "@/prisma/data/nav";
|
import { navData } from "@/prisma/data/nav";
|
||||||
import { TeamSwitcher } from "../team-switcher";
|
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>) {
|
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 (
|
return (
|
||||||
<Sidebar collapsible="icon" {...props}>
|
<Sidebar collapsible="icon" {...props}>
|
||||||
<SidebarHeader>
|
<SidebarHeader>
|
||||||
|
@ -30,7 +52,7 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
||||||
<NavReports reports={navData.reports} />
|
<NavReports reports={navData.reports} />
|
||||||
</SidebarContent>
|
</SidebarContent>
|
||||||
<SidebarFooter>
|
<SidebarFooter>
|
||||||
<NavUser user={navData.user} />
|
<NavUser user={user} />
|
||||||
</SidebarFooter>
|
</SidebarFooter>
|
||||||
<SidebarRail />
|
<SidebarRail />
|
||||||
</Sidebar>
|
</Sidebar>
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
"use client";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Folder,
|
Folder,
|
||||||
Forward,
|
Forward,
|
||||||
|
|
|
@ -1,19 +1,8 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import {
|
import { ChevronsUpDown } from "lucide-react";
|
||||||
BadgeCheck,
|
|
||||||
Bell,
|
|
||||||
ChevronsUpDown,
|
|
||||||
CreditCard,
|
|
||||||
LogOut,
|
|
||||||
Sparkles,
|
|
||||||
} from "lucide-react";
|
|
||||||
|
|
||||||
import {
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||||
Avatar,
|
|
||||||
AvatarFallback,
|
|
||||||
AvatarImage,
|
|
||||||
} from "@/components/ui/avatar";
|
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
|
@ -36,18 +25,35 @@ import {
|
||||||
IconLogout,
|
IconLogout,
|
||||||
IconSparkles,
|
IconSparkles,
|
||||||
} from "@tabler/icons-react";
|
} from "@tabler/icons-react";
|
||||||
|
import { Profile, User } from "@/src/models/users/users.model";
|
||||||
|
|
||||||
export function NavUser({
|
export function NavUser({ user }: { user: User | null }) {
|
||||||
user,
|
|
||||||
}: {
|
|
||||||
user: {
|
|
||||||
name: string;
|
|
||||||
email: string;
|
|
||||||
avatar: string;
|
|
||||||
};
|
|
||||||
}) {
|
|
||||||
const { isMobile } = useSidebar();
|
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 (
|
return (
|
||||||
<SidebarMenu>
|
<SidebarMenu>
|
||||||
<SidebarMenuItem>
|
<SidebarMenuItem>
|
||||||
|
@ -58,12 +64,14 @@ export function NavUser({
|
||||||
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
|
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
|
||||||
>
|
>
|
||||||
<Avatar className="h-8 w-8 rounded-lg">
|
<Avatar className="h-8 w-8 rounded-lg">
|
||||||
<AvatarImage src={user.avatar} alt={user.name} />
|
<AvatarImage src={userAvatar || ""} alt={getFullName()} />
|
||||||
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
|
<AvatarFallback className="rounded-lg">
|
||||||
|
{getInitials()}
|
||||||
|
</AvatarFallback>
|
||||||
</Avatar>
|
</Avatar>
|
||||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||||
<span className="truncate font-semibold">{user.name}</span>
|
<span className="truncate font-semibold">{getFullName()}</span>
|
||||||
<span className="truncate text-xs">{user.email}</span>
|
<span className="truncate text-xs">{userEmail}</span>
|
||||||
</div>
|
</div>
|
||||||
<ChevronsUpDown className="ml-auto size-4" />
|
<ChevronsUpDown className="ml-auto size-4" />
|
||||||
</SidebarMenuButton>
|
</SidebarMenuButton>
|
||||||
|
@ -77,12 +85,16 @@ export function NavUser({
|
||||||
<DropdownMenuLabel className="p-0 font-normal">
|
<DropdownMenuLabel className="p-0 font-normal">
|
||||||
<div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
|
<div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
|
||||||
<Avatar className="h-8 w-8 rounded-lg">
|
<Avatar className="h-8 w-8 rounded-lg">
|
||||||
<AvatarImage src={user.avatar} alt={user.name} />
|
<AvatarImage src={userAvatar || ""} alt={getFullName()} />
|
||||||
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
|
<AvatarFallback className="rounded-lg">
|
||||||
|
{getInitials()}
|
||||||
|
</AvatarFallback>
|
||||||
</Avatar>
|
</Avatar>
|
||||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||||
<span className="truncate font-semibold">{user.name}</span>
|
<span className="truncate font-semibold">
|
||||||
<span className="truncate text-xs">{user.email}</span>
|
{getFullName()}
|
||||||
|
</span>
|
||||||
|
<span className="truncate text-xs">{userEmail}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</DropdownMenuLabel>
|
</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-switch": "^1.1.3",
|
||||||
"@radix-ui/react-tabs": "^1.1.3",
|
"@radix-ui/react-tabs": "^1.1.3",
|
||||||
"@radix-ui/react-tooltip": "^1.1.8",
|
"@radix-ui/react-tooltip": "^1.1.8",
|
||||||
"@supabase/ssr": "latest",
|
"@supabase/ssr": "^0.4.1",
|
||||||
"@supabase/supabase-js": "latest",
|
"@supabase/supabase-js": "latest",
|
||||||
"@tabler/icons-react": "^3.30.0",
|
"@tabler/icons-react": "^3.30.0",
|
||||||
"@tanstack/react-query": "^5.66.9",
|
"@tanstack/react-query": "^5.66.9",
|
||||||
|
@ -2588,6 +2588,19 @@
|
||||||
"react-dom": "^18.0 || ^19.0 || ^19.0.0-rc"
|
"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": {
|
"node_modules/@selderee/plugin-htmlparser2": {
|
||||||
"version": "0.11.0",
|
"version": "0.11.0",
|
||||||
"resolved": "https://registry.npmjs.org/@selderee/plugin-htmlparser2/-/plugin-htmlparser2-0.11.0.tgz",
|
"resolved": "https://registry.npmjs.org/@selderee/plugin-htmlparser2/-/plugin-htmlparser2-0.11.0.tgz",
|
||||||
|
@ -2666,18 +2679,29 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@supabase/ssr": {
|
"node_modules/@supabase/ssr": {
|
||||||
"version": "0.5.2",
|
"version": "0.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/@supabase/ssr/-/ssr-0.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/@supabase/ssr/-/ssr-0.4.1.tgz",
|
||||||
"integrity": "sha512-n3plRhr2Bs8Xun1o4S3k1CDv17iH5QY9YcoEvXX3bxV1/5XSasA0mNXYycFmADIdtdE6BG9MRjP5CGIs8qxC8A==",
|
"integrity": "sha512-000i7y4ITXjXU0T1JytZYU33VbUNklX9YN47hCweaLKsTBAEigJJJCeq3G+/IiwEggBt58Vu0KQ3UGXON7OmDQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/cookie": "^0.6.0",
|
"cookie": "^0.6.0"
|
||||||
"cookie": "^0.7.0"
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@rollup/rollup-linux-x64-gnu": "^4.9.5"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@supabase/supabase-js": "^2.43.4"
|
"@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": {
|
"node_modules/@supabase/storage-js": {
|
||||||
"version": "2.7.1",
|
"version": "2.7.1",
|
||||||
"resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.7.1.tgz",
|
"resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.7.1.tgz",
|
||||||
|
@ -2829,12 +2853,6 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/@types/cors": {
|
||||||
"version": "2.8.17",
|
"version": "2.8.17",
|
||||||
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz",
|
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz",
|
||||||
|
@ -3429,6 +3447,7 @@
|
||||||
"version": "0.7.2",
|
"version": "0.7.2",
|
||||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
|
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
|
||||||
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
|
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
"@radix-ui/react-switch": "^1.1.3",
|
"@radix-ui/react-switch": "^1.1.3",
|
||||||
"@radix-ui/react-tabs": "^1.1.3",
|
"@radix-ui/react-tabs": "^1.1.3",
|
||||||
"@radix-ui/react-tooltip": "^1.1.8",
|
"@radix-ui/react-tooltip": "^1.1.8",
|
||||||
"@supabase/ssr": "latest",
|
"@supabase/ssr": "^0.4.1",
|
||||||
"@supabase/supabase-js": "latest",
|
"@supabase/supabase-js": "latest",
|
||||||
"@tabler/icons-react": "^3.30.0",
|
"@tabler/icons-react": "^3.30.0",
|
||||||
"@tanstack/react-query": "^5.66.9",
|
"@tanstack/react-query": "^5.66.9",
|
||||||
|
|
|
@ -133,6 +133,7 @@ model profiles {
|
||||||
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
|
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
|
||||||
user_id String @unique @db.Uuid
|
user_id String @unique @db.Uuid
|
||||||
avatar String? @db.VarChar(255)
|
avatar String? @db.VarChar(255)
|
||||||
|
username String? @unique @db.VarChar(255)
|
||||||
first_name String? @db.VarChar(255)
|
first_name String? @db.VarChar(255)
|
||||||
last_name String? @db.VarChar(255)
|
last_name String? @db.VarChar(255)
|
||||||
bio String? @db.VarChar
|
bio String? @db.VarChar
|
||||||
|
@ -141,41 +142,31 @@ model profiles {
|
||||||
users users @relation(fields: [user_id], references: [id])
|
users users @relation(fields: [user_id], references: [id])
|
||||||
|
|
||||||
@@index([user_id])
|
@@index([user_id])
|
||||||
|
@@index([username])
|
||||||
}
|
}
|
||||||
|
|
||||||
model users {
|
model users {
|
||||||
id String @id @db.Uuid
|
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
|
||||||
email String @unique @db.VarChar(255)
|
role roles @default(user)
|
||||||
email_verified Boolean @default(false)
|
email String @unique @db.VarChar(255)
|
||||||
first_name String? @db.VarChar(255)
|
phone String? @unique @db.VarChar(20)
|
||||||
last_name String? @db.VarChar(255)
|
encrypted_password String? @db.VarChar(255)
|
||||||
avatar String? @db.VarChar(255)
|
invited_at DateTime? @db.Timestamptz(6)
|
||||||
role roles @default(user)
|
confirmed_at DateTime? @db.Timestamptz(6)
|
||||||
created_at DateTime @default(now())
|
email_confirmed_at DateTime? @db.Timestamptz(6)
|
||||||
updated_at DateTime
|
recovery_sent_at DateTime? @db.Timestamptz(6)
|
||||||
banned_until DateTime?
|
last_sign_in_at DateTime? @db.Timestamptz(6)
|
||||||
confirmation_sent_at DateTime?
|
app_metadata Json?
|
||||||
confirmation_token String? @db.VarChar(255)
|
user_metadata Json?
|
||||||
deleted_at DateTime?
|
created_at DateTime @default(now()) @db.Timestamptz(6)
|
||||||
email_change String? @db.VarChar(255)
|
updated_at DateTime @default(now()) @db.Timestamptz(6)
|
||||||
email_change_sent_at DateTime?
|
banned_until DateTime? @db.Timestamptz(6)
|
||||||
email_change_token String? @db.VarChar(255)
|
is_anonymous Boolean @default(false)
|
||||||
email_confirmed_at DateTime?
|
profile profiles?
|
||||||
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?
|
|
||||||
|
|
||||||
|
@@index([is_anonymous])
|
||||||
|
@@index([created_at])
|
||||||
|
@@index([updated_at])
|
||||||
@@index([role])
|
@@index([role])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,53 +1,136 @@
|
||||||
export interface User {
|
import { z } from "zod";
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CreateUserParams {
|
import { JsonValue } from "@prisma/client/runtime/library";
|
||||||
email: string;
|
import { roles } from "@prisma/client";
|
||||||
password: string;
|
import { AuthError } from "@supabase/supabase-js";
|
||||||
phone?: string;
|
|
||||||
user_metadata?: Record<string, any>;
|
|
||||||
email_confirm?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UpdateUserParams {
|
const timestampSchema = z.date().nullable();
|
||||||
email?: string;
|
|
||||||
phone?: string;
|
|
||||||
password?: string;
|
|
||||||
user_metadata?: Record<string, any>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface InviteUserParams {
|
const AppMetadataSchema = z
|
||||||
email: string;
|
.object({
|
||||||
user_metadata?: Record<string, any>;
|
provider: z.string(),
|
||||||
}
|
providers: z.array(z.string()),
|
||||||
|
})
|
||||||
|
.nullable();
|
||||||
|
|
||||||
export interface Profile {
|
const UserMetadataSchema = z.object({
|
||||||
id: string;
|
email_verified: z.boolean().optional(),
|
||||||
user_id: string;
|
});
|
||||||
avatar?: string;
|
|
||||||
first_name?: string;
|
const IdentityDataSchema = z.object({
|
||||||
last_name?: string;
|
email: z.string().email(),
|
||||||
bio: string;
|
email_verified: z.boolean(),
|
||||||
address?: string;
|
phone_verified: z.boolean(),
|
||||||
birthdate?: string;
|
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