bug rls (not solved)

This commit is contained in:
vergiLgood1 2025-03-05 22:51:46 +07:00
parent b2784cc108
commit bc03c18a05
6 changed files with 166 additions and 40 deletions

View File

@ -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>

View File

@ -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,
};

View File

@ -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>

View File

@ -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",
},
],
},
};

View File

@ -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>;