bug rls (not solved)
This commit is contained in:
parent
b2784cc108
commit
bc03c18a05
|
@ -1,10 +1,26 @@
|
|||
export default function DashboardPage() {
|
||||
import { createClient } from "@/utils/supabase/server";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
export default async function DashboardPage() {
|
||||
const supabase = await createClient();
|
||||
|
||||
const {
|
||||
data: { user },
|
||||
} = await supabase.auth.getUser();
|
||||
|
||||
if (!user) {
|
||||
return redirect("/sign-in");
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<header className="flex h-12 shrink-0 items-center justify-end border-b px-4 mb-2 transition-[width,height] ease-linear group-has-[[data-collapsible=icon]]/sidebar-wrapper:h-8"></header>
|
||||
<div className="flex flex-1 flex-col gap-4 p-4 pt-0">
|
||||
<div className="grid auto-rows-min gap-4 md:grid-cols-3">
|
||||
<div className="aspect-video rounded-xl bg-muted/50" />
|
||||
<div className="aspect-video rounded-xl bg-muted/50">
|
||||
<pre className="text-xs font-mono p-3 rounded border overflow-auto">
|
||||
{JSON.stringify(user, null, 2)}
|
||||
</pre>
|
||||
</div>
|
||||
<div className="aspect-video rounded-xl bg-muted/50" />
|
||||
<div className="aspect-video rounded-xl bg-muted/50" />
|
||||
</div>
|
||||
|
|
|
@ -9,6 +9,7 @@ import {
|
|||
UserResponse,
|
||||
} from "@/src/models/users/users.model";
|
||||
import { createClient } from "@/utils/supabase/server";
|
||||
import { createClient as createClientSide } from "@/utils/supabase/client";
|
||||
import { createAdminClient } from "@/utils/supabase/admin";
|
||||
|
||||
// Initialize Supabase client with admin key
|
||||
|
@ -100,6 +101,56 @@ export async function createUser(
|
|||
};
|
||||
}
|
||||
|
||||
export async function uploadAvatar(userId: string, email: string, file: File) {
|
||||
try {
|
||||
const supabase = createClientSide();
|
||||
|
||||
// Pastikan mendapatkan session untuk autentikasi
|
||||
const { data: session } = await supabase.auth.getSession();
|
||||
if (!session) throw new Error("User is not authenticated");
|
||||
|
||||
const baseUrl = `${process.env.NEXT_PUBLIC_SUPABASE_STORAGE_URL}/avatars`;
|
||||
|
||||
const fileExt = file.name.split(".").pop();
|
||||
const emailName = email.split("@")[0];
|
||||
const fileName = `AVR-${emailName}.${fileExt}`;
|
||||
const filePath = `${baseUrl}/${fileName}`;
|
||||
|
||||
const { error: uploadError } = await supabase.storage
|
||||
.from("avatars")
|
||||
.upload(fileName, file, {
|
||||
upsert: false,
|
||||
contentType: file.type,
|
||||
});
|
||||
|
||||
if (uploadError) {
|
||||
throw uploadError;
|
||||
}
|
||||
|
||||
await db.users.update({
|
||||
where: {
|
||||
id: userId,
|
||||
},
|
||||
data: {
|
||||
profile: {
|
||||
update: {
|
||||
avatar: filePath,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const {
|
||||
data: { publicUrl },
|
||||
} = supabase.storage.from("avatars").getPublicUrl(fileName);
|
||||
|
||||
return publicUrl;
|
||||
} catch (error) {
|
||||
console.error("Error uploading avatar:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Update an existing user
|
||||
export async function updateUser(
|
||||
userId: string,
|
||||
|
@ -110,7 +161,8 @@ export async function updateUser(
|
|||
const { data, error } = await supabase.auth.admin.updateUserById(userId, {
|
||||
email: params.email,
|
||||
phone: params.phone,
|
||||
password: params.password,
|
||||
password_hash: params.password_hash,
|
||||
ban_duration: params.ban_duration,
|
||||
});
|
||||
|
||||
if (error) {
|
||||
|
@ -118,9 +170,52 @@ export async function updateUser(
|
|||
throw new Error(error.message);
|
||||
}
|
||||
|
||||
const user = await db.users.findUnique({
|
||||
where: {
|
||||
id: userId,
|
||||
},
|
||||
include: {
|
||||
profile: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
throw new Error("User not found");
|
||||
}
|
||||
|
||||
const updateUser = await db.users.update({
|
||||
where: {
|
||||
id: userId,
|
||||
},
|
||||
data: {
|
||||
role: params.role,
|
||||
profile: {
|
||||
update: {
|
||||
avatar: params.profile.avatar || user.profile?.avatar,
|
||||
username: params.profile.username || user.profile?.username,
|
||||
first_name: params.profile.first_name || user.profile?.first_name,
|
||||
last_name: params.profile.last_name || user.profile?.last_name,
|
||||
bio: params.profile.bio || user.profile?.bio,
|
||||
address: params.profile.address,
|
||||
birth_date: params.profile.birth_date || user.profile?.birth_date,
|
||||
},
|
||||
},
|
||||
},
|
||||
include: {
|
||||
profile: true,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
data: {
|
||||
user: data.user,
|
||||
user: {
|
||||
...data.user,
|
||||
role: params.role,
|
||||
profile: {
|
||||
user_id: userId,
|
||||
...updateUser.profile,
|
||||
},
|
||||
},
|
||||
},
|
||||
error: null,
|
||||
};
|
||||
|
|
|
@ -27,11 +27,14 @@ import { Label } from "@/app/_components/ui/label";
|
|||
import { Separator } from "@/app/_components/ui/separator";
|
||||
import { Switch } from "@/app/_components/ui/switch";
|
||||
import { useRef, useState } from "react";
|
||||
import { createClient } from "@/utils/supabase/client";
|
||||
import { ScrollArea } from "@/app/_components/ui/scroll-area";
|
||||
import {
|
||||
updateUser,
|
||||
uploadAvatar,
|
||||
} from "@/app/(protected)/(admin)/dashboard/user-management/action";
|
||||
|
||||
const profileFormSchema = z.object({
|
||||
preferred_name: z.string().nullable().optional(),
|
||||
username: z.string().nullable().optional(),
|
||||
avatar: z.string().nullable().optional(),
|
||||
});
|
||||
|
||||
|
@ -44,46 +47,32 @@ interface ProfileSettingsProps {
|
|||
export function ProfileSettings({ user }: ProfileSettingsProps) {
|
||||
const [isUploading, setIsUploading] = useState(false);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const supabase = createClient();
|
||||
|
||||
// Use profile data with fallbacks
|
||||
const preferredName = user?.profile?.first_name || "";
|
||||
const username = user?.profile?.username || "";
|
||||
const userEmail = user?.email || "";
|
||||
const userAvatar = user?.profile?.avatar || "";
|
||||
|
||||
const form = useForm<ProfileFormValues>({
|
||||
resolver: zodResolver(profileFormSchema),
|
||||
defaultValues: {
|
||||
preferred_name: preferredName || "",
|
||||
username: username || "",
|
||||
avatar: userAvatar || "",
|
||||
},
|
||||
});
|
||||
|
||||
const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (!file || !user?.id) return;
|
||||
|
||||
if (!file || !user?.id || !user?.email) return;
|
||||
|
||||
try {
|
||||
setIsUploading(true);
|
||||
|
||||
// Upload to Supabase Storage
|
||||
const fileExt = file.name.split(".").pop();
|
||||
const fileName = `${user.id}-${Date.now()}.${fileExt}`;
|
||||
const filePath = `avatars/${fileName}`;
|
||||
// Upload avatar to storage
|
||||
const publicUrl = await uploadAvatar(user.id, user.email, file);
|
||||
|
||||
const { error: uploadError, data } = await supabase.storage
|
||||
.from("profiles")
|
||||
.upload(filePath, file, {
|
||||
upsert: true,
|
||||
contentType: file.type,
|
||||
});
|
||||
|
||||
if (uploadError) throw uploadError;
|
||||
|
||||
// Get the public URL
|
||||
const {
|
||||
data: { publicUrl },
|
||||
} = supabase.storage.from("profiles").getPublicUrl(filePath);
|
||||
console.log("publicUrl", publicUrl);
|
||||
|
||||
// Update the form value
|
||||
form.setValue("avatar", publicUrl);
|
||||
|
@ -103,13 +92,12 @@ export function ProfileSettings({ user }: ProfileSettingsProps) {
|
|||
if (!user?.id) return;
|
||||
|
||||
// Update profile in database
|
||||
const { error } = await supabase
|
||||
.from("profiles")
|
||||
.update({
|
||||
first_name: data.preferred_name,
|
||||
avatar: data.avatar,
|
||||
})
|
||||
.eq("user_id", user.id);
|
||||
const { error } = await updateUser(user.id, {
|
||||
profile: {
|
||||
avatar: data.avatar || undefined,
|
||||
username: data.username || undefined,
|
||||
},
|
||||
});
|
||||
|
||||
if (error) throw error;
|
||||
} catch (error) {
|
||||
|
@ -133,10 +121,10 @@ export function ProfileSettings({ user }: ProfileSettingsProps) {
|
|||
<Avatar className="h-16 w-16">
|
||||
<AvatarImage
|
||||
src={form.watch("avatar") || ""}
|
||||
alt={preferredName}
|
||||
alt={username}
|
||||
/>
|
||||
<AvatarFallback>
|
||||
{preferredName?.[0]?.toUpperCase() ||
|
||||
{username?.[0]?.toUpperCase() ||
|
||||
userEmail?.[0]?.toUpperCase()}
|
||||
</AvatarFallback>
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-black/50 rounded-full opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
|
@ -160,7 +148,7 @@ export function ProfileSettings({ user }: ProfileSettingsProps) {
|
|||
<Label>Preferred name</Label>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="preferred_name"
|
||||
name="username"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
|
|
|
@ -3,10 +3,22 @@ import type { NextConfig } from "next";
|
|||
const nextConfig: NextConfig = {
|
||||
images: {
|
||||
remotePatterns: [
|
||||
{
|
||||
protocol: "https",
|
||||
hostname: "images.unsplash.com",
|
||||
},
|
||||
{
|
||||
protocol: "https",
|
||||
hostname: "images.pexels.com",
|
||||
},
|
||||
{
|
||||
protocol: "https",
|
||||
hostname: "images.unsplash.com",
|
||||
},
|
||||
{
|
||||
protocol: "https",
|
||||
hostname: "cppejroeyonsqxulinaj.supabase.co",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
|
|
@ -34,11 +34,13 @@ const timestampSchema = z.union([z.string(), z.date()]).nullable();
|
|||
// email: z.string().email(),
|
||||
// });
|
||||
|
||||
|
||||
export const UserSchema = z.object({
|
||||
id: z.string(),
|
||||
role: z.string().optional(),
|
||||
email: z.string().email().optional(),
|
||||
email_confirmed_at: z.union([z.string(), z.date()]).nullable().optional(),
|
||||
password_hash: z.string().nullable().optional(),
|
||||
invited_at: z.union([z.string(), z.date()]).nullable().optional(),
|
||||
phone: z.string().nullable().optional(),
|
||||
confirmed_at: z.union([z.string(), z.date()]).nullable().optional(),
|
||||
|
@ -50,9 +52,10 @@ export const UserSchema = z.object({
|
|||
banned_until: z.union([z.string(), z.date()]).nullable().optional(),
|
||||
profile: z
|
||||
.object({
|
||||
id: z.string(),
|
||||
id: z.string().optional(),
|
||||
user_id: z.string(),
|
||||
avatar: z.string().nullable().optional(),
|
||||
username: z.string().nullable().optional(),
|
||||
first_name: z.string().nullable().optional(),
|
||||
last_name: z.string().nullable().optional(),
|
||||
bio: z.string().nullable().optional(),
|
||||
|
@ -69,11 +72,12 @@ export const ProfileSchema = z.object({
|
|||
id: z.string(),
|
||||
user_id: z.string(),
|
||||
avatar: z.string().optional(),
|
||||
username: z.string().optional(),
|
||||
first_name: z.string().optional(),
|
||||
last_name: z.string().optional(),
|
||||
bio: z.string(),
|
||||
address: z.string().optional(),
|
||||
birthdate: z.string().optional(),
|
||||
birth_date: z.string().optional(),
|
||||
});
|
||||
|
||||
export type Profile = z.infer<typeof ProfileSchema>;
|
||||
|
@ -91,7 +95,18 @@ export type CreateUserParams = z.infer<typeof CreateUserParamsSchema>;
|
|||
export const UpdateUserParamsSchema = z.object({
|
||||
email: z.string().email().optional(),
|
||||
phone: z.string().optional(),
|
||||
password: z.string().optional(),
|
||||
password_hash: z.string().optional(),
|
||||
ban_duration: z.string().optional(),
|
||||
role: z.enum(["user", "staff", "admin"]).optional(),
|
||||
profile: z.object({
|
||||
avatar: z.string().optional(),
|
||||
username: z.string().optional(),
|
||||
first_name: z.string().optional(),
|
||||
last_name: z.string().optional(),
|
||||
bio: z.string().optional(),
|
||||
address: z.string().optional(),
|
||||
birth_date: z.string().optional(),
|
||||
}),
|
||||
});
|
||||
|
||||
export type UpdateUserParams = z.infer<typeof UpdateUserParamsSchema>;
|
||||
|
|
Loading…
Reference in New Issue