Search code examples
reactjsnext.jssupabase

NextJS, Supabase: how to save publicUrl from product form?


need help. I am new to Supabase and don't understand some things.

I want to create simple form where I can upload Image, add name, description and price. I can save Image to bucket, add information into database. Fetch this data.

BUT! I can't imagine how to combine this 2 things. I need grab publicUrl of Image and add it into database to productId.

How to do that? I feel that this must be very simple solution, but I already spend for this 2 days. ChatGPT didn't help me. I don't understand how to solve this.

My form:


import React, { useState } from "react";

import Head from "next/head";
import { useRouter } from "next/router";

import { supabase } from "@/utils/supabaseClient";

import { Button, InputFilled, TextFieldFilled } from "@/core/ui/components";
import { StorageError } from "@supabase/storage-js";
import { useAddress } from "@thirdweb-dev/react";

export default function AddPage() {
  const router = useRouter();

  const [name, setName] = useState("");
  const [description, setDescription] = useState("");
  const [price, setPrice] = useState("");
  const [formError, setFormError] = useState("");
  const [coverUrl, setCoverUrl] = useState(null);

  const address = useAddress();
  const wallet = address;

  async function handleSubmit(e: { preventDefault: () => void }) {
    e.preventDefault();

    const { data, error } = await supabase
      .from("products")
      .insert([{ name, description, price, wallet }]);

    if (data) {
      console.log(data);
      setFormError("");
      router.push("/profile");
    }

    if (!name || !description || !price) {
      setFormError("Please fill in all the fields correctly.");
      return;
    }

    const publicUrl = await uploadImage(coverUrl);

    if (publicUrl !== null) {
      console.log(publicUrl);
    }
  }

  async function uploadImage(e: any) {
    try {
      const file = e.target.files[0];
      const publicURL = supabase.storage.from("cover").getPublicUrl(file.name);
      const {
        data,
        error,
      }: { data: { path: string } | null; error: StorageError | null } =
        await supabase.storage.from("cover").upload(`${file.name}`, file);
      if (error) {
        throw error;
      }
      console.log("File uploaded successfully");
      console.log(publicURL);
    } catch (error: any) {
      console.log("Error uploading file: ", error.message);
    }
  }

  return (
    <>
      <Head>
        <title>New stuff </title>
        <meta
          name="viewport"
          content="width=device-width, initial-scale=1, shrink-to-fit=no, user-scalable=no"
        />
        <meta
          content="New stuff"
          name="New stuff"
        />
      </Head>

      {/*INPUTS*/}
      <form
        onSubmit={handleSubmit}
        className="m-[12px] flex flex-col items-start gap-[12px] lg:w-2/3">
        <div className="flex w-full flex-col items-center justify-center rounded-large border border-dashed border-zinc-700 bg-[#161618]/50 text-white hover:bg-zinc-500/20">
          <div className="flex items-center justify-center">
            <label
              htmlFor="dropzone-file"
              className="flex h-64 w-full cursor-pointer flex-col items-center justify-center rounded-lg">
              <div className="flex flex-col items-center justify-center pt-5 pb-6">
                <svg
                  aria-hidden="true"
                  className="mb-3 h-10 w-10 text-zinc-400"
                  fill="none"
                  stroke="currentColor"
                  viewBox="0 0 24 24"
                  xmlns="http://www.w3.org/2000/svg">
                  <path
                    strokeLinecap="round"
                    strokeLinejoin="round"
                    strokeWidth="2"
                    d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"></path>
                </svg>
                <p className="mb-2 text-title-medium text-zinc-500 dark:text-zinc-400">
                  <span className="font-bold">Click to upload cover</span>
                </p>
                <p className="text-xs text-zinc-500 dark:text-zinc-400">
                  SVG, PNG, JPG or GIF
                </p>
              </div>
              <input
                id="dropzone-file"
                type="file"
                className=""
                onChange={uploadImage}
              />
            </label>
          </div>
        </div>
        <InputFilled
          type="text"
          placeholder="Name"
          id="name"
          value={name}
          onChange={(event) => setName(event.target.value)}
        />
        <TextFieldFilled
          type="text"
          placeholder="Description"
          id="description"
          value={description}
          onChange={(event) => setDescription(event.target.value)}
        />
        <InputFilled
          type="text"
          placeholder="Price, USD"
          id="price"
          value={price}
          onChange={(event) => setPrice(event.target.value)}
        />
        <Button
          className=""
          type="submit"
          onClick={() => router.push("/profile")}
          disabled={!name || !description || !price}>
          Submit
        </Button>
      </form>
    </>
  );
}

Would be happy to get some advices how to fix my issue! I read docs, strive to find solution or example and didn't find. Thanks!


Solution

  • You can store the public URL as a state like this

    const [imagePublicUrl, setImagePublicUrl] = useState(null);
    

    Then store the public URL inside your uploadImage method.

    const publicURL = supabase.storage.from("cover").getPublicUrl(file.name);
    setImagePublicUrl(publicURL)
    

    And store the imagePublicUrl when you save the data to Supabase. This of course assumes you create a column named image_url in your products table.

    const { data, error } = await supabase
          .from("products")
          .insert([{ name, description, price, wallet, image_url: imagePublicUrl }]);