Поэтому я немного не понимаю, как работают клиентские компоненты. В моем проекте я пытаюсь создать загрузчик изображений, и мне нужно получить идентификатор пользователя из супабазы, функции сервера супабазы, и мне нужно загрузить изображение в хранилище супабазы, используя «userId/имя файла». Поэтому я пытаюсь создать кнопку или использовать и ввести с помощью реквизита onChange. Я не могу добавить функцию в кнопку, поскольку она конфликтует с функцией на стороне сервера. Вот мой код:
import React from 'react';
import { Label } from './ui/label';
import { Input } from './ui/input';
import { createClient } from '@/utils/supabase/server';
import { Button } from './ui/button';
import { useToast } from './ui/use-toast';
import { TestButton } from './button';
const UploadImage = () => {
// const { toast } = useToast();
async function handleUpload (e: React.ChangeEvent<HTMLInputElement>) {
// alert('Uploading image');
const supabase = createClient();
const { data } = await supabase.auth.getUser();
alert(data.user?.id);
let file;
if (e.target.files) {
// alert(`e:${e}`);
file = e.target.files[0];
// alert(`file: ${file.name}`);
}
const userId = await supabase.auth.getUser();
const id = userId.data.user?.id;
alert(userId.data.user);
try {
const { data, error } = await supabase.storage
.from('avatars')
.upload('5ff1e6f3-6974-4398-b329-14d737571129/test', file as File, {
// cacheControl: '3600',
upsert: true,
});
// const { data, error } = await supabase.storage
// .from('avatars')
// .upload(
// '5ff1e6f3-6974-4398-b329-14d737571129/' + file!.name,
// file as File,
// {
// cacheControl: '3600',
// upsert: true,
// }
// );
// alert(data);
if (data) {
alert(data);
alert('successfully uploaded');
// toast({
// title: 'Image Uploaded',
// description: 'Your image was successfully uploaded',
// });
} else if (error) {
// alert(error.message);
}
} catch (e) {
// alert(e);
}
};
return (
<div>
<Label htmlFor = "picture">Picture</Label>
<Input
id = "file_input"
type = "file"
accept = "image/*"
onChange = {e => {
handleUpload(e);
}}
/>
</div>
);
};
export default UploadImage;
TLDR: Как создать клиентский компонент, которому требуется серверная функциональность, или как лучше всего поступить в этой ситуации.
Извините, если это расплывчато, я не до конца понимаю, что происходит, так что терпите. Спасибо
🤔 А знаете ли вы, что...
JavaScript может выполняться как на стороне клиента (в браузере), так и на стороне сервера (с использованием Node.js).
Создайте файл с именем server/upload.ts
и экспортируйте из него метод supabaseUpload
. Это будет действие сервера, которое получает ваш файл через FormData
, внутри функции вы можете безопасно получить пользователя на стороне сервера и загрузить файл.
"use server";
// server/upload.ts
export default async function supabaseUpload(data: FormData): Promise<boolean> {
try {
const file = formData.get("file") as File;
// insert your upload logic here ..
return true;
} catch (error) {
console.info(error);
return false;
}
}
На стороне клиента внутри функции handleUpload
вы создаете новый объект FormData
и вызываете действие сервера для загрузки файла.
const handleUpload = useCallback(async (e: React.ChangeEvent<HTMLInputElement>) => {
try {
const file = e.target.files?.[0];
if (!file) throw new Error("No files selected");
const formData = new FormData();
formData.append("file", file);
const success = await supabaseUpload(formData);
// do whatever with the success value
} catch (error) {
console.error(error);
}
}, []);
Вот как я завершил свой код, на случай, если он кому-нибудь понадобится:
// utils/server/upload.ts
'use server';
import {createClient} from '../supabase/server';
export default async function supabaseUpload(data: FormData): Promise<boolean> {
const supabase = createClient();
const userId = (await createClient().auth.getUser()).data.user?.id
console.info(`userId: ${userId}`)
try {
const file = data.get('file') as File;
const imageExtension = file.name.split('.').pop()
if (file)
await supabase.storage.from('avatars').upload(`${userId}/profile.${imageExtension}`, file as File, {
cacheControl: '3600',
upsert: true,
contentType: `image/${imageExtension}`
});
return true;
} catch (error) {
console.info(error);
return false;
}
}
Здесь функция используется для клиентского компонента
// components/Uploader.tsx
"use client"
import React, { useCallback } from 'react';
import { Label } from './ui/label';
import { Input } from './ui/input';
import { createClient } from '@/utils/supabase/server';
import supabaseUpload from '@/utils/server/upload';
const UploadImage = () => {
const handleUpload = useCallback(
async (e: React.ChangeEvent<HTMLInputElement>) => {
try {
const file = e.target.files?.[0];
if (!file) throw new Error('No files selected');
const formData = new FormData();
formData.append('file', file);
const success = await supabaseUpload(formData);
alert(success)
} catch (e) {
alert(e);
}
},
[]
);
return (
<div>
<Label htmlFor = "picture">Picture</Label>
<Input
id = "file_input"
type = "file"
accept = "image/*"
onChange = {handleUpload}
/>
</div>
);
};
export default UploadImage;