Как использовать функцию в клиентском компоненте в nextjs?

Поэтому я немного не понимаю, как работают клиентские компоненты. В моем проекте я пытаюсь создать загрузчик изображений, и мне нужно получить идентификатор пользователя из супабазы, функции сервера супабазы, и мне нужно загрузить изображение в хранилище супабазы, используя «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).


622
2

Ответы:

Решено

Создайте файл с именем 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;