Hoang  Ha

Hoang Ha

1660881900

Xây Dựng ứng Dụng Ngăn Xếp Đầy Đủ Với Next.js Và Supabase

Khi nói đến việc xây dựng và lựa chọn các khuôn khổ cho ứng dụng full-stack tiếp theo của bạn, kết hợp Next.js với Supabase là một trong những lựa chọn tốt nhất để làm việc theo ý kiến ​​của tôi.

Supabase là một giải pháp thay thế Firebase mã nguồn mở với rất nhiều công cụ mạnh mẽ, bao gồm xác thực liền mạch . Là một nhà phát triển, đây là chìa khóa để xây dựng một ứng dụng full-stack thành công.

Cùng với xác thực, Supabase đi kèm với các tính năng khác, chẳng hạn như cơ sở dữ liệu Postgres, đăng ký thời gian thực và lưu trữ đối tượng. Tôi tin rằng Supabase là một trong những dịch vụ phụ trợ dễ dàng nhất để bắt đầu hoặc tích hợp.

Trong bài viết này, chúng ta sẽ tìm hiểu cách xây dựng một ứng dụng đầy đủ bằng cách sử dụng Next.js và Supabase . Chúng ta sẽ nói về cách thiết lập dự án Supabase, định cấu hình giao diện người dùng, triển khai xác thực và các chức năng.

Khái niệm của ứng dụng này là để người dùng theo dõi và tạo các hoạt động tập luyện dựa trên các thông số được chỉ định, chỉnh sửa các hoạt động này nếu có bất kỳ sai sót hoặc thay đổi cần thiết nào và xóa chúng nếu cần. Bắt đầu nào!

Giới thiệu về Next.js và Supabase

Next.js là một trong những cách dễ nhất và phổ biến nhất để xây dựng các ứng dụng React sẵn sàng sản xuất. Trong những năm gần đây, Next.js đã có sự tăng trưởng đáng kể theo cấp số nhân và nhiều công ty đã áp dụng nó để xây dựng các ứng dụng của họ.

Tại sao chúng ta nên sử dụng Supabase?

Supabase là một giải pháp thay thế mã nguồn mở, không máy chủ cho Firebase được xây dựng trên cơ sở dữ liệu PostgreSQL. Nó cung cấp tất cả các dịch vụ phụ trợ cần thiết để tạo một ứng dụng full-stack.

Với tư cách là người dùng, bạn có thể quản lý cơ sở dữ liệu của mình từ giao diện Supabase, từ việc tạo bảng và mối quan hệ để viết các truy vấn SQL và công cụ thời gian thực trên PostgreSQL.

Supabase đi kèm với các tính năng thực sự thú vị giúp việc phát triển ứng dụng toàn ngăn xếp của bạn trở nên dễ dàng hơn. Một số tính năng này là:

  • Bảo mật mức hàng (RLS) - Supabase đi kèm với tính năng PostgreSQL RLS cho phép bạn hạn chế các hàng trong bảng cơ sở dữ liệu của mình. Khi bạn tạo các chính sách, bạn tạo chúng trực tiếp bằng SQL
  • Cơ sở dữ liệu thời gian thực - Supabase có tính năng cập nhật trên cơ sở dữ liệu PostgreSQL có thể được sử dụng để lắng nghe các thay đổi trong thời gian thực
  • Supabase UI - Supabase có thư viện thành phần giao diện người dùng mã nguồn mở để tạo ứng dụng nhanh chóng và hiệu quả
  • Xác thực người dùng - Supabase tạo một auth.usersbảng ngay sau khi bạn tạo cơ sở dữ liệu của mình. Khi bạn tạo một ứng dụng, Supabase cũng sẽ chỉ định người dùng và ID ngay khi bạn đăng ký trên ứng dụng có thể được tham chiếu trong cơ sở dữ liệu. Đối với các phương pháp đăng nhập, có nhiều cách khác nhau để bạn có thể xác thực người dùng như email, mật khẩu, liên kết ma thuật, Google, GitHub, v.v.
  • Các hàm Edge - Các hàm Edge là các hàm TypeScript được phân phối toàn cầu ở rìa, gần với người dùng. Chúng có thể được sử dụng để thực hiện các chức năng như tích hợp với bên thứ ba hoặc lắng nghe WebHooks

Bắt đầu dự án của chúng tôi với Next.js

Để bắt đầu dự án của chúng tôi trong terminal với mẫu Next.js, chúng tôi sẽ chạy lệnh sau:

npx create-next-app nextjs-supabase

nextjs-supabaselà tên thư mục của ứng dụng của chúng tôi, nơi chúng tôi sẽ bao gồm mẫu ứng dụng Next.js.

Chúng tôi sẽ cần cài đặt gói ứng dụng Supabase để kết nối với ứng dụng Next.js của chúng tôi sau này. Chúng ta có thể làm như vậy bằng cách chạy một trong các lệnh sau:

yarn add @supabase/supabase-js

hoặc

npm i @supabase/supabase-js

Khi ứng dụng đã hoàn tất thiết lập, hãy mở thư mục trong trình chỉnh sửa mã yêu thích của bạn. Bây giờ, chúng tôi có thể xóa mẫu cơ bản trong /pages/index.jstệp của mình và thay thế bằng h1tiêu đề “Chào mừng bạn đến với Ứng dụng Workout”.

Sau khi hoàn tất, hãy chạy lệnh yarn devtrong terminal để khởi động ứng dụng của bạn tại http: // localhost: 3000 . Bạn sẽ thấy một trang như thế này:

Màn hình chào mừng

Thiết lập dự án Supabase và tạo bảng cơ sở dữ liệu

Để thiết lập dự án Supabase, hãy truy cập app.supabase.com để đăng nhập vào trang tổng quan ứng dụng bằng tài khoản GitHub của bạn.

Bảng điều khiển siêu dữ liệu

Sau khi đăng nhập, bạn có thể tạo tổ chức của mình và thiết lập một dự án mới trong tổ chức đó bằng cách nhấp vào Tất cả dự án .

Tất cả các dự án và màn hình tổ chức

Nhấp vào Dự án mới và đặt tên và mật khẩu cơ sở dữ liệu cho dự án của bạn. Nhấp vào nút Tạo dự án mới ; sẽ mất vài phút để dự án của bạn bắt đầu và chạy.

Tạo một dự án mới

Khi dự án đã được tạo, bạn sẽ thấy một bảng điều khiển như sau:

Workout Next Supabase Dashboard

Đối với hướng dẫn này, tôi đã tạo một dự án có tênworkout-next-supabase.

Bây giờ, hãy tạo bảng cơ sở dữ liệu của chúng ta bằng cách nhấp vào biểu tượng SQL Editor trên trang tổng quan của chúng tôi và nhấp vào Truy vấn mới . Nhập truy vấn SQL bên dưới vào trình soạn thảo và nhấp vào RUN để thực hiện truy vấn.

CREATE TABLE workouts (
 id bigint generated by default as identity primary key,
 user_id uuid references auth.users not null,
 user_email text,
 title text,
 loads text,
 reps text,
 inserted_at timestamp with time zone default timezone('utc'::text, now()) not null
);

alter table workouts enable row level security;

create policy "Individuals can create workouts." on workouts for
   insert with check (auth.uid() = user_id);

create policy "Individuals can update their own workouts." on workouts for
   update using (auth.uid() = user_id);

create policy "Individuals can delete their own workouts." on workouts for
   delete using (auth.uid() = user_id);

create policy "Workouts are public." on workouts for
   select using (true);

Thực thi truy vấn của bạn

Thao tác này sẽ tạo bảng tập luyện mà chúng tôi sẽ sử dụng để xây dựng ứng dụng CRUD của mình.

Cùng với việc tạo bảng, các quyền ở cấp độ hàng sẽ được bật để đảm bảo rằng chỉ những người dùng được ủy quyền mới có thể tạo, cập nhật hoặc xóa các chi tiết về bài tập của họ.

Để xem bảng tập luyện trông như thế nào, chúng ta có thể nhấp vào biểu tượng Trình chỉnh sửa bảng trên trang tổng quan để xem bảng tập luyện mà chúng ta vừa tạo.

Đối với ứng dụng này, chúng ta sẽ có bảy cột:

  • user_id
  • user_email
  • id
  • title
  • loads
  • reps
  • Date stamp

Trình chỉnh sửa bảng

Khi bảng và cột của chúng ta đã được thiết lập, bước tiếp theo là kết nối cơ sở dữ liệu Supabase với ứng dụng giao diện người dùng Next.js của chúng ta!

Kết nối Next.js với cơ sở dữ liệu Supabase

Để kết nối Supabase với ứng dụng Next.js của chúng tôi, chúng tôi sẽ cần URL dự ánAnon Key . Cả hai điều này có thể được tìm thấy trên bảng điều khiển cơ sở dữ liệu của chúng tôi. Để lấy hai phím này, hãy nhấp vào biểu tượng bánh răng để chuyển đến Cài đặt và sau đó nhấp vào API . Bạn sẽ thấy hai phím này hiển thị như sau:

Thiết lập url Api

Tất nhiên, chúng tôi không muốn để lộ những giá trị này một cách công khai trên trình duyệt hoặc kho lưu trữ của chúng tôi vì đó là thông tin nhạy cảm. Với lợi thế của chúng tôi, Next.js cung cấp hỗ trợ sẵn có cho các biến môi trường cho phép chúng tôi tạo một .env.localtệp trong thư mục gốc của dự án của chúng tôi. Điều này sẽ tải các biến môi trường của chúng tôi và hiển thị chúng với trình duyệt bằng cách thêm tiền tố vào NEXT_PUBLIC.

Bây giờ, hãy tạo một .env.localtệp trong thư mục gốc của dự án và bao gồm URL và khóa của chúng tôi trong tệp.

.env.local NEXT_PUBLIC_SUPABASE_URL = // dán url dự án của bạn vào đây NEXT_PUBLIC_SUPABASE_ANON_KEY = // dán khóa anon siêu dữ liệu của bạn vào đây

NB, Đừng quên đưa .env.localvào gitignoretệp của bạn để ngăn nó bị đẩy lên repo GitHub (và có sẵn cho mọi người xem) khi triển khai.

Bây giờ chúng ta hãy tạo tệp khách hàng Supabase của chúng ta bằng cách tạo một tệp được gọi supabase.jsở gốc của dự án của chúng ta. Bên trong supabase.jstệp, chúng tôi sẽ viết mã sau:

// supabase.js
import { createClient } from "@supabase/supabase-js";

const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;

export const supabase = createClient(supabaseUrl, supabaseKey);

Ở đây, chúng tôi đang nhập một createClienthàm từ Supabase và tạo một biến được gọi là supabase. Chúng tôi gọi createClienthàm và sau đó chuyển vào các tham số của chúng tôi: URL ( supabaseUrl) và Anon Key ( supabaseKey).

Bây giờ, chúng ta có thể gọi và sử dụng ứng dụng khách Supabase ở bất kỳ đâu trong dự án của mình!

Định cấu hình giao diện người dùng của ứng dụng của chúng tôi

Trước tiên, chúng ta cần định cấu hình ứng dụng của mình theo cách chúng ta muốn. Chúng tôi sẽ có một thanh điều hướng với tên dự án và các tùy chọn Đăng nhậpĐăng ký khi ứng dụng được tải lần đầu tiên. Khi người dùng đăng ký và đăng nhập, chúng tôi sẽ hiển thị thanh điều hướng để có các nút Home , LogoutCreate Workout .

Cũng sẽ có một chân trang trên mỗi trang trên trang web.

Để làm điều này, chúng tôi sẽ tạo một componentthư mục chứa Navbar.jsFooter.jscác tệp. Sau đó, bên trong _app.js, chúng tôi sẽ bọc pagesthành phần của mình bằng Navbarvà các Footerthành phần để chúng được hiển thị trên mọi trang của ứng dụng.

// _app.js
import Footer from "../components/Footer";
import Navbar from "../components/Navbar";
import "../styles/globals.css";

function MyApp({ Component, pageProps }) {
 return (
   <div>
     <Navbar/>
     <Component {...pageProps}/>
     <Footer />
   </div>
 );
}

export default MyApp;

Tôi đã tạo ý chính GitHub ở đây để xem hai thành phần này trông như thế nào cùng với các kiểu tôi đã sử dụng.

Bây giờ, trang chủ của chúng tôi sẽ trông như thế này:

Màn hình chính Adrenargy

Triển khai xác thực người dùng

Để triển khai xác thực người dùng, chúng tôi sẽ khởi tạo trạng thái người dùng trong _app.jstệp của mình và tạo một validateUserhàm để kiểm tra và xác thực người dùng. Sau đó, chúng tôi sẽ đặt trạng thái người dùng thành đối tượng phiên được trả về.

// _app.js

import { useState, useEffect } from "react";
import Footer from "../components/Footer";
import Navbar from "../components/Navbar";
import "../styles/globals.css";
import { supabase } from "../utils/supabase";

function MyApp({ Component, pageProps }) {
 const [session, setSession] = useState(null);

 useEffect(() => {
   setSession(supabase.auth.session());
   supabase.auth.onAuthStateChange((_event, session) => {
     setSession(session);
   });
 }, []);
 return (
   <div>
     <Navbar session={session} />
     <Component {...pageProps} session={session} />
     <Footer />
   </div>
 );
}
export default MyApp;

Khi người dùng tải trang chủ của ứng dụng của chúng tôi, chúng tôi muốn hiển thị một nút để yêu cầu họ đăng nhập hoặc đăng ký. Khi nhấp vào nút Đăng nhập , nút này sẽ chuyển hướng người dùng đến một trang nơi người dùng có thể nhập email và mật khẩu của họ. Nếu họ là người dùng hiện tại và chi tiết đăng nhập hợp lệ, họ sẽ được chuyển hướng đến trang chủ.

Nếu người dùng có thông tin đăng nhập không hợp lệ, một thông báo cảnh báo sẽ hiển thị để cho người dùng biết về sự cố. Thay vào đó, họ sẽ được hiển thị một tùy chọn đăng ký.

Khi người dùng đăng ký, một email xác nhận sẽ được gửi đến email họ đã nhập. họ sẽ cần xác nhận email của mình bằng cách nhấp vào liên kết trong nội dung email.

Xác nhận Email Đăng ký

Bây giờ, khi chúng tôi nhấp vào nút Đăng nhập , chúng tôi sẽ được chuyển hướng đến trang người dùng đến trang này:

Trang đăng nhập

Bây giờ, chúng ta có thể nhấp vào nút Đăng ký và nhập email.

Trang đăng ký

Khi chúng tôi nhấp vào đây, một email sẽ được gửi để xác nhận địa chỉ email. Sau khi xác nhận, nó sẽ đăng nhập cho chúng tôi và chúng tôi sẽ thấy một trang như thế này:

Màn hình chào mừng mà không cần tập luyện

Lưu ý rằng nếu chúng tôi chưa đăng nhập, chúng tôi không thể xem bảng điều khiển hoạt động của mình, thấy nút để tạo bài tập mới hoặc đăng xuất. Đây là xác thực được đề cập ban đầu được cung cấp cho chúng tôi bởi Supabase!

Thực hiện các chức năng tập luyện

Bây giờ, chúng ta sẽ đi sâu vào việc tạo khả năng tạo, sửa đổi và xóa các bài tập của người dùng.

Tìm nạp tất cả các bài tập

Chúng tôi sẽ cần tìm nạp tất cả các bài tập mà chúng tôi sẽ tạo và hiển thị chúng trên trang chủ. Chúng tôi sẽ thực hiện việc này bên trong index.jstệp:

// /pages/index.js
import Head from "next/head";
import Link from "next/link";
import { useEffect, useState } from "react";
import styles from "../styles/Home.module.css";
import { supabase } from "../utils/supabase";
import WorkoutCard from "../components/WorkoutCard";

export default function Home({ session }) {
 const [workouts, setWorkouts] = useState([]);
 const [loading, setLoading] = useState(true);

 useEffect(() => {
   fetchWorkouts();
 }, []);

 const fetchWorkouts = async () => {
   const user = supabase.auth.user();
   try {
     setLoading(true);
     const { data, error } = await supabase
       .from("workouts")
       .select("*")
       .eq("user_id", user?.id);

     if (error) throw error;
     setWorkouts(data);
   } catch (error) {
     alert(error.message);
   } finally {
     setLoading(false);
   }
 };

 if (loading) {
   return <div className={styles.loading}>Fetching Workouts...</div>;
 }
 return (
   <div className={styles.container}>
     <Head>
       <title>Nextjs x Supabase</title>
       <meta name="description" content="Generated by create next app" />
       <link rel="icon" href="/favicon.ico" />
     </Head>

     <div className={styles.home}>
       {!session?.user ? (
         <div>
           <p>
             Welcome to Adrenargy. Kindly log in to your account or sign in for
             a demo
           </p>
         </div>
       ) : (
         <div>
           <p className={styles.workoutHeading}>
             Hello <span className={styles.email}>{session.user.email}</span>,
             Welcome to your dashboard
           </p>
           {workouts?.length === 0 ? (
             <div className={styles.noWorkout}>
               <p>You have no workouts yet</p>
               <Link href="/create">
                 <button className={styles.button}>
                   {" "}
                   Create a New Workout
                 </button>
               </Link>
             </div>
           ) : (
             <div>
               <p className={styles.workoutHeading}>Here are your workouts</p>
               <WorkoutCard data={workouts}/>
             </div>
           )}
         </div>
       )}
     </div>
   </div>
 );
}

Trong thành phần này, chúng tôi đang hủy cấu sessiontrúc đối tượng mà chúng tôi đã chuyển từ các pageđạo cụ trong _app.jstệp và sử dụng đối tượng đó để xác thực người dùng được ủy quyền. Nếu không có người dùng, trang tổng quan sẽ không được hiển thị. Nếu có người dùng đăng nhập, bảng điều khiển các bài tập sẽ xuất hiện. Và nếu chưa có bài tập nào được tạo, một văn bản cho biết “Bạn chưa có bài tập nào” và nút để tạo một bài tập mới sẽ xuất hiện.

Để hiển thị các bài tập đã tạo của chúng tôi, chúng tôi có hai trạng thái:, workoutsmột mảng trống và một loadingtrạng thái nhận giá trị boolean là true. Chúng tôi đang sử dụng useEffectđể tìm nạp dữ liệu tập luyện từ cơ sở dữ liệu khi trang được tải.

Hàm fetchWorkoutsđược sử dụng để gọi cá thể Supabase để trả về tất cả dữ liệu từ các bảng tập luyện trong cơ sở dữ liệu của chúng tôi bằng selectphương pháp này. Các . eq()phương pháp lọc được sử dụng để lọc ra và chỉ trả về dữ liệu có id người dùng phù hợp với người dùng đăng nhập hiện tại. Sau đó, setWorkoutsđược đặt thành dữ liệu được gửi từ cơ sở dữ liệu và setLoadingđược đặt trở lại sau falsekhi chúng tôi tìm nạp dữ liệu của mình.

Nếu dữ liệu vẫn đang được tìm nạp, trang sẽ hiển thị “Tìm nạp các bài tập…” và nếu yêu cầu được thực hiện đến cơ sở dữ liệu của chúng tôi trả về mảng các bài tập của chúng tôi, chúng tôi muốn ánh xạ qua mảng và hiển thị WorkoutCardthành phần.

Trong WorkoutCardthành phần, chúng tôi đang hiển thị tiêu đề bài tập, tải, số lần tập và ngày giờ nó được tạo. Thời gian được tạo đang được định dạng bằng date-fnsthư viện mà bạn có thể xem tại đây . Chúng ta sẽ xem các thẻ của chúng ta trông như thế nào khi chúng ta bắt đầu tạo chúng trong phần tiếp theo.

// Workoutcard.js

import Link from "next/link";
import styles from "../styles/WorkoutCard.module.css";
import { BsTrash } from "react-icons/bs";
import { FiEdit } from "react-icons/fi";
import { formatDistanceToNow } from "date-fns/";

const WorkoutCard = ({ data }) => {
 return (
   <div className={styles.workoutContainer}>
     {data?.map((item) => (
       <div key={item.id} className={styles.container}>
         <p className={styles.title}>
           {" "}
           Title: {""}
           {item.title}
         </p>
         <p className={styles.load}>
           {" "}
           Load(kg): {"  "}
           {item.loads}
         </p>
         <p className={styles.reps}>Reps:{item.reps}</p>
         <p className={styles.time}>
           created:{" "}
           {formatDistanceToNow(new Date(item.inserted_at), {
             addSuffix: true,
           })}
         </p>
       </div>
     ))}
   </div>
 );
};

export default WorkoutCard;

Tạo một bài tập mới

Bây giờ chúng tôi đã đăng nhập, trang tổng quan của chúng tôi mới và sạch sẽ. Để triển khai khả năng tạo một bài tập mới, chúng tôi sẽ thêm create.jsCreate.module.csscác tệp trong thư mục pagesstylestương ứng, đồng thời triển khai một số logic và kiểu dáng.

// /pages/create.js

import { supabase } from "../utils/supabase";
import { useState } from "react";
import styles from "../styles/Create.module.css";
import { useRouter } from "next/router";

const Create = () => {
 const initialState = {
   title: "",
   loads: "",
   reps: "",
 };

 const router = useRouter();
 const [workoutData, setWorkoutData] = useState(initialState);

 const { title, loads, reps } = workoutData;

 const handleChange = (e) => {
   setWorkoutData({ ...workoutData, [e.target.name]: e.target.value });
 };

 const createWorkout = async () => {
   try {
     const user = supabase.auth.user();

     const { data, error } = await supabase
       .from("workouts")
       .insert([
         {
           title,
           loads,
           reps,
           user_id: user?.id,
         },
       ])
       .single();
     if (error) throw error;
     alert("Workout created successfully");
     setWorkoutData(initialState);
     router.push("/");
   } catch (error) {
     alert(error.message);
   }
 };

 return (
   <>
     <div className={styles.container}>
       <div className={styles.form}>
         <p className={styles.title}>Create a New Workout</p>
         <label className={styles.label}>Title:</label>
         <input
           type="text"
           name="title"
           value={title}
           onChange={handleChange}
           className={styles.input}
           placeholder="Enter a title"
         />
         <label className={styles.label}>Load (kg):</label>
         <input
           type="text"
           name="loads"
           value={loads}
           onChange={handleChange}
           className={styles.input}
           placeholder="Enter weight load"
         />
         <label className={styles.label}>Reps:</label>
         <input
           type="text"
           name="reps"
           value={reps}
           onChange={handleChange}
           className={styles.input}
           placeholder="Enter number of reps"
         />

         <button className={styles.button} onClick={createWorkout}>
           Create Workout
         </button>
       </div>
     </div>
   </>
 );
};

export default Create;

Ở đây, phạm vi giao diện người dùng cơ bản là chúng ta sẽ có một biểu mẫu để tạo một bài tập mới. Biểu mẫu sẽ bao gồm ba trường (tiêu đề, tải và đại diện) như chúng tôi đã chỉ định khi tạo cơ sở dữ liệu của mình.

Một đối tượng trạng thái ban đầu được định nghĩa để xử lý tất cả các trường này đã được chuyển đến workoutsDatatrạng thái. Hàm onChangeđược sử dụng để xử lý các thay đổi của trường đầu vào.

Hàm createWorkoutsử dụng cá thể máy khách Supabase để tạo một bài tập mới bằng cách sử dụng các trường trạng thái ban đầu mà chúng tôi đã xác định và chèn nó vào bảng cơ sở dữ liệu.

Cuối cùng, chúng tôi có một bánh mì nướng thông báo cho chúng tôi biết khi bài tập mới của chúng tôi đã được tạo.

Sau đó, chúng tôi đặt dữ liệu biểu mẫu trở lại trạng thái chuỗi trống ban đầu khi bài tập của chúng tôi đã được tạo. Sau đó, chúng tôi đang sử dụng router.pushphương pháp này để điều hướng người dùng quay lại trang chủ.

Tạo dự án mới

Đã tạo thành công bài tập

Bảng điều khiển với bài tập Dumbell Press

Cập nhật bài tập

Để cập nhật bài tập, chúng tôi sẽ tạo một thư mục có tên edittrong pagesthư mục chứa [id].jstệp của chúng tôi. Chúng tôi sẽ tạo một biểu tượng liên kết chỉnh sửa trên thẻ thành phần tập luyện của chúng tôi liên kết đến trang này. Khi các thẻ được hiển thị trên trang chủ, chúng ta có thể nhấp vào biểu tượng chỉnh sửa này và nó sẽ đưa chúng ta đến trang chỉnh sửa của thẻ cụ thể đó.

Sau đó, chúng tôi sẽ tìm nạp thông tin chi tiết của thẻ tập luyện cần thiết để được cập nhật từ bảng tập luyện của chúng tôi bởi idchủ sở hữu được ủy quyền và của thẻ đó. Sau đó, chúng tôi sẽ tạo một updateWorkoutchức năng để cập nhật chi tiết thẻ tập luyện của chúng tôi:

// /pages/edit/[id].js
import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import styles from "../../styles/Edit.module.css";
import { supabase } from "../../utils/supabase";

const Edit = () => {
 const [workout, setWorkout] = useState("");
 const router = useRouter();

 const { id } = router.query;
 useEffect(() => {
   const user = supabase.auth.user();
   const getWorkout = async () => {
     const { data } = await supabase
       .from("workouts")
       .select("*")
       .eq("user_id", user?.id)
       .filter("id", "eq", id)
       .single();
     setWorkout(data);
   };
   getWorkout();
 }, [id]);

 const handleOnChange = (e) => {
   setWorkout({
     ...workout,
     [e.target.name]: e.target.value,
   });
 };

 const { title, loads, reps } = workout;
 const updateWorkout = async () => {
   const user = supabase.auth.user();
   const { data } = await supabase
     .from("workouts")
     .update({
       title,
       loads,
       reps,
     })
     .eq("id", id)
     .eq("user_id", user?.id);

   alert("Workout updated successfully");

   router.push("/");
 };
 return (
   <div className={styles.container}>
     <div className={styles.formContainer}>
       <h1 className={styles.title}>Edit Workout</h1>
       <label className={styles.label}> Title:</label>
       <input
         type="text"
         name="title"
         value={workout.title}
         onChange={handleOnChange}
         className={styles.updateInput}
       />
       <label className={styles.label}> Load (kg):</label>
       <input
         type="text"
         name="loads"
         value={workout.loads}
         onChange={handleOnChange}
         className={styles.updateInput}
       />
       <label className={styles.label}> Reps:</label>
       <input
         type="text"
         name="reps"
         value={workout.reps}
         onChange={handleOnChange}
         className={styles.updateInput}
       />

       <button onClick={updateWorkout} className={styles.updateButton}>
         Update Workout
       </button>
     </div>
   </div>
 );
};

export default Edit;

Đầu tiên, chúng tôi tạo một trạng thái để lưu trữ các chi tiết thẻ tập luyện sẽ được tìm nạp từ bảng của chúng tôi. Sau đó, chúng tôi trích xuất idthẻ của thẻ đó bằng cách sử dụng useRouterhook. Hàm getWorkoutgọi phiên bản máy khách Supabase để lọc idthẻ tập luyện đó và trả về dữ liệu (tiêu đề, số lần tải và số lần thực hiện).

Khi chi tiết thẻ tập luyện đã được trả lại, chúng tôi có thể tạo updateWorkoutchức năng của mình để sửa đổi các chi tiết bằng cách sử dụng .update()chức năng. Sau khi người dùng cập nhật bài tập và nhấp vào nút Cập nhật bài tập , một thông báo cảnh báo sẽ được gửi và người dùng sẽ được chuyển hướng trở lại trang chủ.

Hãy xem nó hoạt động như thế nào.

Nhấp vào biểu tượng chỉnh sửa để chuyển đến trang chỉnh sửa. Chúng tôi sẽ đổi tên tiêu đề từ “Dumbell Press” thành “Arm Curl”:

Chỉnh sửa Workout Dumbell Press

Chỉnh sửa bài tập thành công

Chỉnh sửa bài tập với máy cuộn cánh tay

Xóa bài tập

Để xóa một bài tập trên mỗi thẻ, chúng tôi sẽ tạo handleDeletehàm sẽ lấy idlàm đối số. Chúng tôi sẽ gọi phiên bản Supabase để xóa thẻ tập luyện bằng cách sử dụng

.delete()hàm số. Điều này .eq('id', id)chỉ định idhàng sẽ bị xóa trên bảng.

 const handleDelete = async (id) => {
   try {


     const user = supabase.auth.user();
     const { data, error } = await supabase
       .from("workouts")
       .delete()
       .eq("id", id)
       .eq("user_id", user?.id);
     fetchWorkouts();
     alert("Workout deleted successfully");
   } catch (error) {
     alert(error.message);
   }
 };

eq('user_id', user?.id)Được sử dụng để kiểm tra xem thẻ đang bị xóa có thuộc về người dùng cụ thể đó hay không . Hàm sẽ được chuyển đến WorkoutCardthành phần trong index.jstệp và bị hủy để sử dụng trong chính thành phần đó như sau:

const WorkoutCard = ({ data, handleDelete }) => {
 return (
   <div className={styles.workoutContainer}>
     {data?.map((item) => (
       <div key={item.id} className={styles.container}>
         <p className={styles.title}>
           {" "}
           Title: {""}
           {item.title}
         </p>
         <p className={styles.load}>
           {" "}
           Load(kg): {"  "}
           {item.loads}
         </p>
         <p className={styles.reps}>Reps:{item.reps}</p>
         <p className={styles.time}>
           created:{" "}
           {formatDistanceToNow(new Date(item.inserted_at), {
             addSuffix: true,
           })}
         </p>

         <div className={styles.buttons}>
           <Link href={`/edit/${item.id}`}>
             <a className={styles.edit}>
               <FiEdit />
             </a>
           </Link>
           <button
             onClick={() => handleDelete(item.id)}
             className={styles.delete}
           >
             <BsTrash />
           </button>
         </div>
       </div>
     ))}
   </div>
 );
};

Bánh mì nướng cảnh báo sẽ được hiển thị khi thẻ đã được xóa thành công và người dùng sẽ được chuyển hướng đến trang chủ.

Triển khai cho Vercel

Bây giờ, chúng tôi phải triển khai ứng dụng của mình cho Vercel để bất kỳ ai trên Internet đều có thể sử dụng nó!

Để triển khai Vercel, trước tiên bạn phải đẩy mã của mình vào kho lưu trữ, đăng nhập vào bảng điều khiển Vercel, nhấp vào Tạo dự án mới và nhấp vào kho lưu trữ mà bạn vừa đẩy mã của mình vào.

Nhập các biến môi trường mà chúng tôi đã tạo trước đó cùng với các giá trị của chúng ( NEXT_PUBLIC_SUPABASE_URLNEXT_PUBLIC_SUPABASE_ANON_KEY) trong trường Biến môi trường và nhấp vào Triển khai để triển khai ứng dụng của bạn vào phiên bản sản xuất.

Triển khai tới Vercel

Và chúng tôi đã có nó!

Sự kết luận

Cảm ơn bạn đã đọc! Tôi hy vọng hướng dẫn này cung cấp cho bạn kiến ​​thức bắt buộc cần thiết để tạo ứng dụng toàn ngăn xếp bằng Next.js và Supabase.

Bạn có thể tùy chỉnh kiểu cho trường hợp sử dụng của mình, vì hướng dẫn này chủ yếu tập trung vào logic của việc tạo một ứng dụng đầy đủ.

Nguồn: https://blog.logrocket.com/build-full-stack-app-next-js-supabase/

  #nextjs #supabase #fullstack 

What is GEEK

Buddha Community

Xây Dựng ứng Dụng Ngăn Xếp Đầy Đủ Với Next.js Và Supabase

NBB: Ad-hoc CLJS Scripting on Node.js

Nbb

Not babashka. Node.js babashka!?

Ad-hoc CLJS scripting on Node.js.

Status

Experimental. Please report issues here.

Goals and features

Nbb's main goal is to make it easy to get started with ad hoc CLJS scripting on Node.js.

Additional goals and features are:

  • Fast startup without relying on a custom version of Node.js.
  • Small artifact (current size is around 1.2MB).
  • First class macros.
  • Support building small TUI apps using Reagent.
  • Complement babashka with libraries from the Node.js ecosystem.

Requirements

Nbb requires Node.js v12 or newer.

How does this tool work?

CLJS code is evaluated through SCI, the same interpreter that powers babashka. Because SCI works with advanced compilation, the bundle size, especially when combined with other dependencies, is smaller than what you get with self-hosted CLJS. That makes startup faster. The trade-off is that execution is less performant and that only a subset of CLJS is available (e.g. no deftype, yet).

Usage

Install nbb from NPM:

$ npm install nbb -g

Omit -g for a local install.

Try out an expression:

$ nbb -e '(+ 1 2 3)'
6

And then install some other NPM libraries to use in the script. E.g.:

$ npm install csv-parse shelljs zx

Create a script which uses the NPM libraries:

(ns script
  (:require ["csv-parse/lib/sync$default" :as csv-parse]
            ["fs" :as fs]
            ["path" :as path]
            ["shelljs$default" :as sh]
            ["term-size$default" :as term-size]
            ["zx$default" :as zx]
            ["zx$fs" :as zxfs]
            [nbb.core :refer [*file*]]))

(prn (path/resolve "."))

(prn (term-size))

(println (count (str (fs/readFileSync *file*))))

(prn (sh/ls "."))

(prn (csv-parse "foo,bar"))

(prn (zxfs/existsSync *file*))

(zx/$ #js ["ls"])

Call the script:

$ nbb script.cljs
"/private/tmp/test-script"
#js {:columns 216, :rows 47}
510
#js ["node_modules" "package-lock.json" "package.json" "script.cljs"]
#js [#js ["foo" "bar"]]
true
$ ls
node_modules
package-lock.json
package.json
script.cljs

Macros

Nbb has first class support for macros: you can define them right inside your .cljs file, like you are used to from JVM Clojure. Consider the plet macro to make working with promises more palatable:

(defmacro plet
  [bindings & body]
  (let [binding-pairs (reverse (partition 2 bindings))
        body (cons 'do body)]
    (reduce (fn [body [sym expr]]
              (let [expr (list '.resolve 'js/Promise expr)]
                (list '.then expr (list 'clojure.core/fn (vector sym)
                                        body))))
            body
            binding-pairs)))

Using this macro we can look async code more like sync code. Consider this puppeteer example:

(-> (.launch puppeteer)
      (.then (fn [browser]
               (-> (.newPage browser)
                   (.then (fn [page]
                            (-> (.goto page "https://clojure.org")
                                (.then #(.screenshot page #js{:path "screenshot.png"}))
                                (.catch #(js/console.log %))
                                (.then #(.close browser)))))))))

Using plet this becomes:

(plet [browser (.launch puppeteer)
       page (.newPage browser)
       _ (.goto page "https://clojure.org")
       _ (-> (.screenshot page #js{:path "screenshot.png"})
             (.catch #(js/console.log %)))]
      (.close browser))

See the puppeteer example for the full code.

Since v0.0.36, nbb includes promesa which is a library to deal with promises. The above plet macro is similar to promesa.core/let.

Startup time

$ time nbb -e '(+ 1 2 3)'
6
nbb -e '(+ 1 2 3)'   0.17s  user 0.02s system 109% cpu 0.168 total

The baseline startup time for a script is about 170ms seconds on my laptop. When invoked via npx this adds another 300ms or so, so for faster startup, either use a globally installed nbb or use $(npm bin)/nbb script.cljs to bypass npx.

Dependencies

NPM dependencies

Nbb does not depend on any NPM dependencies. All NPM libraries loaded by a script are resolved relative to that script. When using the Reagent module, React is resolved in the same way as any other NPM library.

Classpath

To load .cljs files from local paths or dependencies, you can use the --classpath argument. The current dir is added to the classpath automatically. So if there is a file foo/bar.cljs relative to your current dir, then you can load it via (:require [foo.bar :as fb]). Note that nbb uses the same naming conventions for namespaces and directories as other Clojure tools: foo-bar in the namespace name becomes foo_bar in the directory name.

To load dependencies from the Clojure ecosystem, you can use the Clojure CLI or babashka to download them and produce a classpath:

$ classpath="$(clojure -A:nbb -Spath -Sdeps '{:aliases {:nbb {:replace-deps {com.github.seancorfield/honeysql {:git/tag "v2.0.0-rc5" :git/sha "01c3a55"}}}}}')"

and then feed it to the --classpath argument:

$ nbb --classpath "$classpath" -e "(require '[honey.sql :as sql]) (sql/format {:select :foo :from :bar :where [:= :baz 2]})"
["SELECT foo FROM bar WHERE baz = ?" 2]

Currently nbb only reads from directories, not jar files, so you are encouraged to use git libs. Support for .jar files will be added later.

Current file

The name of the file that is currently being executed is available via nbb.core/*file* or on the metadata of vars:

(ns foo
  (:require [nbb.core :refer [*file*]]))

(prn *file*) ;; "/private/tmp/foo.cljs"

(defn f [])
(prn (:file (meta #'f))) ;; "/private/tmp/foo.cljs"

Reagent

Nbb includes reagent.core which will be lazily loaded when required. You can use this together with ink to create a TUI application:

$ npm install ink

ink-demo.cljs:

(ns ink-demo
  (:require ["ink" :refer [render Text]]
            [reagent.core :as r]))

(defonce state (r/atom 0))

(doseq [n (range 1 11)]
  (js/setTimeout #(swap! state inc) (* n 500)))

(defn hello []
  [:> Text {:color "green"} "Hello, world! " @state])

(render (r/as-element [hello]))

Promesa

Working with callbacks and promises can become tedious. Since nbb v0.0.36 the promesa.core namespace is included with the let and do! macros. An example:

(ns prom
  (:require [promesa.core :as p]))

(defn sleep [ms]
  (js/Promise.
   (fn [resolve _]
     (js/setTimeout resolve ms))))

(defn do-stuff
  []
  (p/do!
   (println "Doing stuff which takes a while")
   (sleep 1000)
   1))

(p/let [a (do-stuff)
        b (inc a)
        c (do-stuff)
        d (+ b c)]
  (prn d))
$ nbb prom.cljs
Doing stuff which takes a while
Doing stuff which takes a while
3

Also see API docs.

Js-interop

Since nbb v0.0.75 applied-science/js-interop is available:

(ns example
  (:require [applied-science.js-interop :as j]))

(def o (j/lit {:a 1 :b 2 :c {:d 1}}))

(prn (j/select-keys o [:a :b])) ;; #js {:a 1, :b 2}
(prn (j/get-in o [:c :d])) ;; 1

Most of this library is supported in nbb, except the following:

  • destructuring using :syms
  • property access using .-x notation. In nbb, you must use keywords.

See the example of what is currently supported.

Examples

See the examples directory for small examples.

Also check out these projects built with nbb:

API

See API documentation.

Migrating to shadow-cljs

See this gist on how to convert an nbb script or project to shadow-cljs.

Build

Prequisites:

  • babashka >= 0.4.0
  • Clojure CLI >= 1.10.3.933
  • Node.js 16.5.0 (lower version may work, but this is the one I used to build)

To build:

  • Clone and cd into this repo
  • bb release

Run bb tasks for more project-related tasks.

Download Details:
Author: borkdude
Download Link: Download The Source Code
Official Website: https://github.com/borkdude/nbb 
License: EPL-1.0

#node #javascript

Hoang  Ha

Hoang Ha

1660881900

Xây Dựng ứng Dụng Ngăn Xếp Đầy Đủ Với Next.js Và Supabase

Khi nói đến việc xây dựng và lựa chọn các khuôn khổ cho ứng dụng full-stack tiếp theo của bạn, kết hợp Next.js với Supabase là một trong những lựa chọn tốt nhất để làm việc theo ý kiến ​​của tôi.

Supabase là một giải pháp thay thế Firebase mã nguồn mở với rất nhiều công cụ mạnh mẽ, bao gồm xác thực liền mạch . Là một nhà phát triển, đây là chìa khóa để xây dựng một ứng dụng full-stack thành công.

Cùng với xác thực, Supabase đi kèm với các tính năng khác, chẳng hạn như cơ sở dữ liệu Postgres, đăng ký thời gian thực và lưu trữ đối tượng. Tôi tin rằng Supabase là một trong những dịch vụ phụ trợ dễ dàng nhất để bắt đầu hoặc tích hợp.

Trong bài viết này, chúng ta sẽ tìm hiểu cách xây dựng một ứng dụng đầy đủ bằng cách sử dụng Next.js và Supabase . Chúng ta sẽ nói về cách thiết lập dự án Supabase, định cấu hình giao diện người dùng, triển khai xác thực và các chức năng.

Khái niệm của ứng dụng này là để người dùng theo dõi và tạo các hoạt động tập luyện dựa trên các thông số được chỉ định, chỉnh sửa các hoạt động này nếu có bất kỳ sai sót hoặc thay đổi cần thiết nào và xóa chúng nếu cần. Bắt đầu nào!

Giới thiệu về Next.js và Supabase

Next.js là một trong những cách dễ nhất và phổ biến nhất để xây dựng các ứng dụng React sẵn sàng sản xuất. Trong những năm gần đây, Next.js đã có sự tăng trưởng đáng kể theo cấp số nhân và nhiều công ty đã áp dụng nó để xây dựng các ứng dụng của họ.

Tại sao chúng ta nên sử dụng Supabase?

Supabase là một giải pháp thay thế mã nguồn mở, không máy chủ cho Firebase được xây dựng trên cơ sở dữ liệu PostgreSQL. Nó cung cấp tất cả các dịch vụ phụ trợ cần thiết để tạo một ứng dụng full-stack.

Với tư cách là người dùng, bạn có thể quản lý cơ sở dữ liệu của mình từ giao diện Supabase, từ việc tạo bảng và mối quan hệ để viết các truy vấn SQL và công cụ thời gian thực trên PostgreSQL.

Supabase đi kèm với các tính năng thực sự thú vị giúp việc phát triển ứng dụng toàn ngăn xếp của bạn trở nên dễ dàng hơn. Một số tính năng này là:

  • Bảo mật mức hàng (RLS) - Supabase đi kèm với tính năng PostgreSQL RLS cho phép bạn hạn chế các hàng trong bảng cơ sở dữ liệu của mình. Khi bạn tạo các chính sách, bạn tạo chúng trực tiếp bằng SQL
  • Cơ sở dữ liệu thời gian thực - Supabase có tính năng cập nhật trên cơ sở dữ liệu PostgreSQL có thể được sử dụng để lắng nghe các thay đổi trong thời gian thực
  • Supabase UI - Supabase có thư viện thành phần giao diện người dùng mã nguồn mở để tạo ứng dụng nhanh chóng và hiệu quả
  • Xác thực người dùng - Supabase tạo một auth.usersbảng ngay sau khi bạn tạo cơ sở dữ liệu của mình. Khi bạn tạo một ứng dụng, Supabase cũng sẽ chỉ định người dùng và ID ngay khi bạn đăng ký trên ứng dụng có thể được tham chiếu trong cơ sở dữ liệu. Đối với các phương pháp đăng nhập, có nhiều cách khác nhau để bạn có thể xác thực người dùng như email, mật khẩu, liên kết ma thuật, Google, GitHub, v.v.
  • Các hàm Edge - Các hàm Edge là các hàm TypeScript được phân phối toàn cầu ở rìa, gần với người dùng. Chúng có thể được sử dụng để thực hiện các chức năng như tích hợp với bên thứ ba hoặc lắng nghe WebHooks

Bắt đầu dự án của chúng tôi với Next.js

Để bắt đầu dự án của chúng tôi trong terminal với mẫu Next.js, chúng tôi sẽ chạy lệnh sau:

npx create-next-app nextjs-supabase

nextjs-supabaselà tên thư mục của ứng dụng của chúng tôi, nơi chúng tôi sẽ bao gồm mẫu ứng dụng Next.js.

Chúng tôi sẽ cần cài đặt gói ứng dụng Supabase để kết nối với ứng dụng Next.js của chúng tôi sau này. Chúng ta có thể làm như vậy bằng cách chạy một trong các lệnh sau:

yarn add @supabase/supabase-js

hoặc

npm i @supabase/supabase-js

Khi ứng dụng đã hoàn tất thiết lập, hãy mở thư mục trong trình chỉnh sửa mã yêu thích của bạn. Bây giờ, chúng tôi có thể xóa mẫu cơ bản trong /pages/index.jstệp của mình và thay thế bằng h1tiêu đề “Chào mừng bạn đến với Ứng dụng Workout”.

Sau khi hoàn tất, hãy chạy lệnh yarn devtrong terminal để khởi động ứng dụng của bạn tại http: // localhost: 3000 . Bạn sẽ thấy một trang như thế này:

Màn hình chào mừng

Thiết lập dự án Supabase và tạo bảng cơ sở dữ liệu

Để thiết lập dự án Supabase, hãy truy cập app.supabase.com để đăng nhập vào trang tổng quan ứng dụng bằng tài khoản GitHub của bạn.

Bảng điều khiển siêu dữ liệu

Sau khi đăng nhập, bạn có thể tạo tổ chức của mình và thiết lập một dự án mới trong tổ chức đó bằng cách nhấp vào Tất cả dự án .

Tất cả các dự án và màn hình tổ chức

Nhấp vào Dự án mới và đặt tên và mật khẩu cơ sở dữ liệu cho dự án của bạn. Nhấp vào nút Tạo dự án mới ; sẽ mất vài phút để dự án của bạn bắt đầu và chạy.

Tạo một dự án mới

Khi dự án đã được tạo, bạn sẽ thấy một bảng điều khiển như sau:

Workout Next Supabase Dashboard

Đối với hướng dẫn này, tôi đã tạo một dự án có tênworkout-next-supabase.

Bây giờ, hãy tạo bảng cơ sở dữ liệu của chúng ta bằng cách nhấp vào biểu tượng SQL Editor trên trang tổng quan của chúng tôi và nhấp vào Truy vấn mới . Nhập truy vấn SQL bên dưới vào trình soạn thảo và nhấp vào RUN để thực hiện truy vấn.

CREATE TABLE workouts (
 id bigint generated by default as identity primary key,
 user_id uuid references auth.users not null,
 user_email text,
 title text,
 loads text,
 reps text,
 inserted_at timestamp with time zone default timezone('utc'::text, now()) not null
);

alter table workouts enable row level security;

create policy "Individuals can create workouts." on workouts for
   insert with check (auth.uid() = user_id);

create policy "Individuals can update their own workouts." on workouts for
   update using (auth.uid() = user_id);

create policy "Individuals can delete their own workouts." on workouts for
   delete using (auth.uid() = user_id);

create policy "Workouts are public." on workouts for
   select using (true);

Thực thi truy vấn của bạn

Thao tác này sẽ tạo bảng tập luyện mà chúng tôi sẽ sử dụng để xây dựng ứng dụng CRUD của mình.

Cùng với việc tạo bảng, các quyền ở cấp độ hàng sẽ được bật để đảm bảo rằng chỉ những người dùng được ủy quyền mới có thể tạo, cập nhật hoặc xóa các chi tiết về bài tập của họ.

Để xem bảng tập luyện trông như thế nào, chúng ta có thể nhấp vào biểu tượng Trình chỉnh sửa bảng trên trang tổng quan để xem bảng tập luyện mà chúng ta vừa tạo.

Đối với ứng dụng này, chúng ta sẽ có bảy cột:

  • user_id
  • user_email
  • id
  • title
  • loads
  • reps
  • Date stamp

Trình chỉnh sửa bảng

Khi bảng và cột của chúng ta đã được thiết lập, bước tiếp theo là kết nối cơ sở dữ liệu Supabase với ứng dụng giao diện người dùng Next.js của chúng ta!

Kết nối Next.js với cơ sở dữ liệu Supabase

Để kết nối Supabase với ứng dụng Next.js của chúng tôi, chúng tôi sẽ cần URL dự ánAnon Key . Cả hai điều này có thể được tìm thấy trên bảng điều khiển cơ sở dữ liệu của chúng tôi. Để lấy hai phím này, hãy nhấp vào biểu tượng bánh răng để chuyển đến Cài đặt và sau đó nhấp vào API . Bạn sẽ thấy hai phím này hiển thị như sau:

Thiết lập url Api

Tất nhiên, chúng tôi không muốn để lộ những giá trị này một cách công khai trên trình duyệt hoặc kho lưu trữ của chúng tôi vì đó là thông tin nhạy cảm. Với lợi thế của chúng tôi, Next.js cung cấp hỗ trợ sẵn có cho các biến môi trường cho phép chúng tôi tạo một .env.localtệp trong thư mục gốc của dự án của chúng tôi. Điều này sẽ tải các biến môi trường của chúng tôi và hiển thị chúng với trình duyệt bằng cách thêm tiền tố vào NEXT_PUBLIC.

Bây giờ, hãy tạo một .env.localtệp trong thư mục gốc của dự án và bao gồm URL và khóa của chúng tôi trong tệp.

.env.local NEXT_PUBLIC_SUPABASE_URL = // dán url dự án của bạn vào đây NEXT_PUBLIC_SUPABASE_ANON_KEY = // dán khóa anon siêu dữ liệu của bạn vào đây

NB, Đừng quên đưa .env.localvào gitignoretệp của bạn để ngăn nó bị đẩy lên repo GitHub (và có sẵn cho mọi người xem) khi triển khai.

Bây giờ chúng ta hãy tạo tệp khách hàng Supabase của chúng ta bằng cách tạo một tệp được gọi supabase.jsở gốc của dự án của chúng ta. Bên trong supabase.jstệp, chúng tôi sẽ viết mã sau:

// supabase.js
import { createClient } from "@supabase/supabase-js";

const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;

export const supabase = createClient(supabaseUrl, supabaseKey);

Ở đây, chúng tôi đang nhập một createClienthàm từ Supabase và tạo một biến được gọi là supabase. Chúng tôi gọi createClienthàm và sau đó chuyển vào các tham số của chúng tôi: URL ( supabaseUrl) và Anon Key ( supabaseKey).

Bây giờ, chúng ta có thể gọi và sử dụng ứng dụng khách Supabase ở bất kỳ đâu trong dự án của mình!

Định cấu hình giao diện người dùng của ứng dụng của chúng tôi

Trước tiên, chúng ta cần định cấu hình ứng dụng của mình theo cách chúng ta muốn. Chúng tôi sẽ có một thanh điều hướng với tên dự án và các tùy chọn Đăng nhậpĐăng ký khi ứng dụng được tải lần đầu tiên. Khi người dùng đăng ký và đăng nhập, chúng tôi sẽ hiển thị thanh điều hướng để có các nút Home , LogoutCreate Workout .

Cũng sẽ có một chân trang trên mỗi trang trên trang web.

Để làm điều này, chúng tôi sẽ tạo một componentthư mục chứa Navbar.jsFooter.jscác tệp. Sau đó, bên trong _app.js, chúng tôi sẽ bọc pagesthành phần của mình bằng Navbarvà các Footerthành phần để chúng được hiển thị trên mọi trang của ứng dụng.

// _app.js
import Footer from "../components/Footer";
import Navbar from "../components/Navbar";
import "../styles/globals.css";

function MyApp({ Component, pageProps }) {
 return (
   <div>
     <Navbar/>
     <Component {...pageProps}/>
     <Footer />
   </div>
 );
}

export default MyApp;

Tôi đã tạo ý chính GitHub ở đây để xem hai thành phần này trông như thế nào cùng với các kiểu tôi đã sử dụng.

Bây giờ, trang chủ của chúng tôi sẽ trông như thế này:

Màn hình chính Adrenargy

Triển khai xác thực người dùng

Để triển khai xác thực người dùng, chúng tôi sẽ khởi tạo trạng thái người dùng trong _app.jstệp của mình và tạo một validateUserhàm để kiểm tra và xác thực người dùng. Sau đó, chúng tôi sẽ đặt trạng thái người dùng thành đối tượng phiên được trả về.

// _app.js

import { useState, useEffect } from "react";
import Footer from "../components/Footer";
import Navbar from "../components/Navbar";
import "../styles/globals.css";
import { supabase } from "../utils/supabase";

function MyApp({ Component, pageProps }) {
 const [session, setSession] = useState(null);

 useEffect(() => {
   setSession(supabase.auth.session());
   supabase.auth.onAuthStateChange((_event, session) => {
     setSession(session);
   });
 }, []);
 return (
   <div>
     <Navbar session={session} />
     <Component {...pageProps} session={session} />
     <Footer />
   </div>
 );
}
export default MyApp;

Khi người dùng tải trang chủ của ứng dụng của chúng tôi, chúng tôi muốn hiển thị một nút để yêu cầu họ đăng nhập hoặc đăng ký. Khi nhấp vào nút Đăng nhập , nút này sẽ chuyển hướng người dùng đến một trang nơi người dùng có thể nhập email và mật khẩu của họ. Nếu họ là người dùng hiện tại và chi tiết đăng nhập hợp lệ, họ sẽ được chuyển hướng đến trang chủ.

Nếu người dùng có thông tin đăng nhập không hợp lệ, một thông báo cảnh báo sẽ hiển thị để cho người dùng biết về sự cố. Thay vào đó, họ sẽ được hiển thị một tùy chọn đăng ký.

Khi người dùng đăng ký, một email xác nhận sẽ được gửi đến email họ đã nhập. họ sẽ cần xác nhận email của mình bằng cách nhấp vào liên kết trong nội dung email.

Xác nhận Email Đăng ký

Bây giờ, khi chúng tôi nhấp vào nút Đăng nhập , chúng tôi sẽ được chuyển hướng đến trang người dùng đến trang này:

Trang đăng nhập

Bây giờ, chúng ta có thể nhấp vào nút Đăng ký và nhập email.

Trang đăng ký

Khi chúng tôi nhấp vào đây, một email sẽ được gửi để xác nhận địa chỉ email. Sau khi xác nhận, nó sẽ đăng nhập cho chúng tôi và chúng tôi sẽ thấy một trang như thế này:

Màn hình chào mừng mà không cần tập luyện

Lưu ý rằng nếu chúng tôi chưa đăng nhập, chúng tôi không thể xem bảng điều khiển hoạt động của mình, thấy nút để tạo bài tập mới hoặc đăng xuất. Đây là xác thực được đề cập ban đầu được cung cấp cho chúng tôi bởi Supabase!

Thực hiện các chức năng tập luyện

Bây giờ, chúng ta sẽ đi sâu vào việc tạo khả năng tạo, sửa đổi và xóa các bài tập của người dùng.

Tìm nạp tất cả các bài tập

Chúng tôi sẽ cần tìm nạp tất cả các bài tập mà chúng tôi sẽ tạo và hiển thị chúng trên trang chủ. Chúng tôi sẽ thực hiện việc này bên trong index.jstệp:

// /pages/index.js
import Head from "next/head";
import Link from "next/link";
import { useEffect, useState } from "react";
import styles from "../styles/Home.module.css";
import { supabase } from "../utils/supabase";
import WorkoutCard from "../components/WorkoutCard";

export default function Home({ session }) {
 const [workouts, setWorkouts] = useState([]);
 const [loading, setLoading] = useState(true);

 useEffect(() => {
   fetchWorkouts();
 }, []);

 const fetchWorkouts = async () => {
   const user = supabase.auth.user();
   try {
     setLoading(true);
     const { data, error } = await supabase
       .from("workouts")
       .select("*")
       .eq("user_id", user?.id);

     if (error) throw error;
     setWorkouts(data);
   } catch (error) {
     alert(error.message);
   } finally {
     setLoading(false);
   }
 };

 if (loading) {
   return <div className={styles.loading}>Fetching Workouts...</div>;
 }
 return (
   <div className={styles.container}>
     <Head>
       <title>Nextjs x Supabase</title>
       <meta name="description" content="Generated by create next app" />
       <link rel="icon" href="/favicon.ico" />
     </Head>

     <div className={styles.home}>
       {!session?.user ? (
         <div>
           <p>
             Welcome to Adrenargy. Kindly log in to your account or sign in for
             a demo
           </p>
         </div>
       ) : (
         <div>
           <p className={styles.workoutHeading}>
             Hello <span className={styles.email}>{session.user.email}</span>,
             Welcome to your dashboard
           </p>
           {workouts?.length === 0 ? (
             <div className={styles.noWorkout}>
               <p>You have no workouts yet</p>
               <Link href="/create">
                 <button className={styles.button}>
                   {" "}
                   Create a New Workout
                 </button>
               </Link>
             </div>
           ) : (
             <div>
               <p className={styles.workoutHeading}>Here are your workouts</p>
               <WorkoutCard data={workouts}/>
             </div>
           )}
         </div>
       )}
     </div>
   </div>
 );
}

Trong thành phần này, chúng tôi đang hủy cấu sessiontrúc đối tượng mà chúng tôi đã chuyển từ các pageđạo cụ trong _app.jstệp và sử dụng đối tượng đó để xác thực người dùng được ủy quyền. Nếu không có người dùng, trang tổng quan sẽ không được hiển thị. Nếu có người dùng đăng nhập, bảng điều khiển các bài tập sẽ xuất hiện. Và nếu chưa có bài tập nào được tạo, một văn bản cho biết “Bạn chưa có bài tập nào” và nút để tạo một bài tập mới sẽ xuất hiện.

Để hiển thị các bài tập đã tạo của chúng tôi, chúng tôi có hai trạng thái:, workoutsmột mảng trống và một loadingtrạng thái nhận giá trị boolean là true. Chúng tôi đang sử dụng useEffectđể tìm nạp dữ liệu tập luyện từ cơ sở dữ liệu khi trang được tải.

Hàm fetchWorkoutsđược sử dụng để gọi cá thể Supabase để trả về tất cả dữ liệu từ các bảng tập luyện trong cơ sở dữ liệu của chúng tôi bằng selectphương pháp này. Các . eq()phương pháp lọc được sử dụng để lọc ra và chỉ trả về dữ liệu có id người dùng phù hợp với người dùng đăng nhập hiện tại. Sau đó, setWorkoutsđược đặt thành dữ liệu được gửi từ cơ sở dữ liệu và setLoadingđược đặt trở lại sau falsekhi chúng tôi tìm nạp dữ liệu của mình.

Nếu dữ liệu vẫn đang được tìm nạp, trang sẽ hiển thị “Tìm nạp các bài tập…” và nếu yêu cầu được thực hiện đến cơ sở dữ liệu của chúng tôi trả về mảng các bài tập của chúng tôi, chúng tôi muốn ánh xạ qua mảng và hiển thị WorkoutCardthành phần.

Trong WorkoutCardthành phần, chúng tôi đang hiển thị tiêu đề bài tập, tải, số lần tập và ngày giờ nó được tạo. Thời gian được tạo đang được định dạng bằng date-fnsthư viện mà bạn có thể xem tại đây . Chúng ta sẽ xem các thẻ của chúng ta trông như thế nào khi chúng ta bắt đầu tạo chúng trong phần tiếp theo.

// Workoutcard.js

import Link from "next/link";
import styles from "../styles/WorkoutCard.module.css";
import { BsTrash } from "react-icons/bs";
import { FiEdit } from "react-icons/fi";
import { formatDistanceToNow } from "date-fns/";

const WorkoutCard = ({ data }) => {
 return (
   <div className={styles.workoutContainer}>
     {data?.map((item) => (
       <div key={item.id} className={styles.container}>
         <p className={styles.title}>
           {" "}
           Title: {""}
           {item.title}
         </p>
         <p className={styles.load}>
           {" "}
           Load(kg): {"  "}
           {item.loads}
         </p>
         <p className={styles.reps}>Reps:{item.reps}</p>
         <p className={styles.time}>
           created:{" "}
           {formatDistanceToNow(new Date(item.inserted_at), {
             addSuffix: true,
           })}
         </p>
       </div>
     ))}
   </div>
 );
};

export default WorkoutCard;

Tạo một bài tập mới

Bây giờ chúng tôi đã đăng nhập, trang tổng quan của chúng tôi mới và sạch sẽ. Để triển khai khả năng tạo một bài tập mới, chúng tôi sẽ thêm create.jsCreate.module.csscác tệp trong thư mục pagesstylestương ứng, đồng thời triển khai một số logic và kiểu dáng.

// /pages/create.js

import { supabase } from "../utils/supabase";
import { useState } from "react";
import styles from "../styles/Create.module.css";
import { useRouter } from "next/router";

const Create = () => {
 const initialState = {
   title: "",
   loads: "",
   reps: "",
 };

 const router = useRouter();
 const [workoutData, setWorkoutData] = useState(initialState);

 const { title, loads, reps } = workoutData;

 const handleChange = (e) => {
   setWorkoutData({ ...workoutData, [e.target.name]: e.target.value });
 };

 const createWorkout = async () => {
   try {
     const user = supabase.auth.user();

     const { data, error } = await supabase
       .from("workouts")
       .insert([
         {
           title,
           loads,
           reps,
           user_id: user?.id,
         },
       ])
       .single();
     if (error) throw error;
     alert("Workout created successfully");
     setWorkoutData(initialState);
     router.push("/");
   } catch (error) {
     alert(error.message);
   }
 };

 return (
   <>
     <div className={styles.container}>
       <div className={styles.form}>
         <p className={styles.title}>Create a New Workout</p>
         <label className={styles.label}>Title:</label>
         <input
           type="text"
           name="title"
           value={title}
           onChange={handleChange}
           className={styles.input}
           placeholder="Enter a title"
         />
         <label className={styles.label}>Load (kg):</label>
         <input
           type="text"
           name="loads"
           value={loads}
           onChange={handleChange}
           className={styles.input}
           placeholder="Enter weight load"
         />
         <label className={styles.label}>Reps:</label>
         <input
           type="text"
           name="reps"
           value={reps}
           onChange={handleChange}
           className={styles.input}
           placeholder="Enter number of reps"
         />

         <button className={styles.button} onClick={createWorkout}>
           Create Workout
         </button>
       </div>
     </div>
   </>
 );
};

export default Create;

Ở đây, phạm vi giao diện người dùng cơ bản là chúng ta sẽ có một biểu mẫu để tạo một bài tập mới. Biểu mẫu sẽ bao gồm ba trường (tiêu đề, tải và đại diện) như chúng tôi đã chỉ định khi tạo cơ sở dữ liệu của mình.

Một đối tượng trạng thái ban đầu được định nghĩa để xử lý tất cả các trường này đã được chuyển đến workoutsDatatrạng thái. Hàm onChangeđược sử dụng để xử lý các thay đổi của trường đầu vào.

Hàm createWorkoutsử dụng cá thể máy khách Supabase để tạo một bài tập mới bằng cách sử dụng các trường trạng thái ban đầu mà chúng tôi đã xác định và chèn nó vào bảng cơ sở dữ liệu.

Cuối cùng, chúng tôi có một bánh mì nướng thông báo cho chúng tôi biết khi bài tập mới của chúng tôi đã được tạo.

Sau đó, chúng tôi đặt dữ liệu biểu mẫu trở lại trạng thái chuỗi trống ban đầu khi bài tập của chúng tôi đã được tạo. Sau đó, chúng tôi đang sử dụng router.pushphương pháp này để điều hướng người dùng quay lại trang chủ.

Tạo dự án mới

Đã tạo thành công bài tập

Bảng điều khiển với bài tập Dumbell Press

Cập nhật bài tập

Để cập nhật bài tập, chúng tôi sẽ tạo một thư mục có tên edittrong pagesthư mục chứa [id].jstệp của chúng tôi. Chúng tôi sẽ tạo một biểu tượng liên kết chỉnh sửa trên thẻ thành phần tập luyện của chúng tôi liên kết đến trang này. Khi các thẻ được hiển thị trên trang chủ, chúng ta có thể nhấp vào biểu tượng chỉnh sửa này và nó sẽ đưa chúng ta đến trang chỉnh sửa của thẻ cụ thể đó.

Sau đó, chúng tôi sẽ tìm nạp thông tin chi tiết của thẻ tập luyện cần thiết để được cập nhật từ bảng tập luyện của chúng tôi bởi idchủ sở hữu được ủy quyền và của thẻ đó. Sau đó, chúng tôi sẽ tạo một updateWorkoutchức năng để cập nhật chi tiết thẻ tập luyện của chúng tôi:

// /pages/edit/[id].js
import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import styles from "../../styles/Edit.module.css";
import { supabase } from "../../utils/supabase";

const Edit = () => {
 const [workout, setWorkout] = useState("");
 const router = useRouter();

 const { id } = router.query;
 useEffect(() => {
   const user = supabase.auth.user();
   const getWorkout = async () => {
     const { data } = await supabase
       .from("workouts")
       .select("*")
       .eq("user_id", user?.id)
       .filter("id", "eq", id)
       .single();
     setWorkout(data);
   };
   getWorkout();
 }, [id]);

 const handleOnChange = (e) => {
   setWorkout({
     ...workout,
     [e.target.name]: e.target.value,
   });
 };

 const { title, loads, reps } = workout;
 const updateWorkout = async () => {
   const user = supabase.auth.user();
   const { data } = await supabase
     .from("workouts")
     .update({
       title,
       loads,
       reps,
     })
     .eq("id", id)
     .eq("user_id", user?.id);

   alert("Workout updated successfully");

   router.push("/");
 };
 return (
   <div className={styles.container}>
     <div className={styles.formContainer}>
       <h1 className={styles.title}>Edit Workout</h1>
       <label className={styles.label}> Title:</label>
       <input
         type="text"
         name="title"
         value={workout.title}
         onChange={handleOnChange}
         className={styles.updateInput}
       />
       <label className={styles.label}> Load (kg):</label>
       <input
         type="text"
         name="loads"
         value={workout.loads}
         onChange={handleOnChange}
         className={styles.updateInput}
       />
       <label className={styles.label}> Reps:</label>
       <input
         type="text"
         name="reps"
         value={workout.reps}
         onChange={handleOnChange}
         className={styles.updateInput}
       />

       <button onClick={updateWorkout} className={styles.updateButton}>
         Update Workout
       </button>
     </div>
   </div>
 );
};

export default Edit;

Đầu tiên, chúng tôi tạo một trạng thái để lưu trữ các chi tiết thẻ tập luyện sẽ được tìm nạp từ bảng của chúng tôi. Sau đó, chúng tôi trích xuất idthẻ của thẻ đó bằng cách sử dụng useRouterhook. Hàm getWorkoutgọi phiên bản máy khách Supabase để lọc idthẻ tập luyện đó và trả về dữ liệu (tiêu đề, số lần tải và số lần thực hiện).

Khi chi tiết thẻ tập luyện đã được trả lại, chúng tôi có thể tạo updateWorkoutchức năng của mình để sửa đổi các chi tiết bằng cách sử dụng .update()chức năng. Sau khi người dùng cập nhật bài tập và nhấp vào nút Cập nhật bài tập , một thông báo cảnh báo sẽ được gửi và người dùng sẽ được chuyển hướng trở lại trang chủ.

Hãy xem nó hoạt động như thế nào.

Nhấp vào biểu tượng chỉnh sửa để chuyển đến trang chỉnh sửa. Chúng tôi sẽ đổi tên tiêu đề từ “Dumbell Press” thành “Arm Curl”:

Chỉnh sửa Workout Dumbell Press

Chỉnh sửa bài tập thành công

Chỉnh sửa bài tập với máy cuộn cánh tay

Xóa bài tập

Để xóa một bài tập trên mỗi thẻ, chúng tôi sẽ tạo handleDeletehàm sẽ lấy idlàm đối số. Chúng tôi sẽ gọi phiên bản Supabase để xóa thẻ tập luyện bằng cách sử dụng

.delete()hàm số. Điều này .eq('id', id)chỉ định idhàng sẽ bị xóa trên bảng.

 const handleDelete = async (id) => {
   try {


     const user = supabase.auth.user();
     const { data, error } = await supabase
       .from("workouts")
       .delete()
       .eq("id", id)
       .eq("user_id", user?.id);
     fetchWorkouts();
     alert("Workout deleted successfully");
   } catch (error) {
     alert(error.message);
   }
 };

eq('user_id', user?.id)Được sử dụng để kiểm tra xem thẻ đang bị xóa có thuộc về người dùng cụ thể đó hay không . Hàm sẽ được chuyển đến WorkoutCardthành phần trong index.jstệp và bị hủy để sử dụng trong chính thành phần đó như sau:

const WorkoutCard = ({ data, handleDelete }) => {
 return (
   <div className={styles.workoutContainer}>
     {data?.map((item) => (
       <div key={item.id} className={styles.container}>
         <p className={styles.title}>
           {" "}
           Title: {""}
           {item.title}
         </p>
         <p className={styles.load}>
           {" "}
           Load(kg): {"  "}
           {item.loads}
         </p>
         <p className={styles.reps}>Reps:{item.reps}</p>
         <p className={styles.time}>
           created:{" "}
           {formatDistanceToNow(new Date(item.inserted_at), {
             addSuffix: true,
           })}
         </p>

         <div className={styles.buttons}>
           <Link href={`/edit/${item.id}`}>
             <a className={styles.edit}>
               <FiEdit />
             </a>
           </Link>
           <button
             onClick={() => handleDelete(item.id)}
             className={styles.delete}
           >
             <BsTrash />
           </button>
         </div>
       </div>
     ))}
   </div>
 );
};

Bánh mì nướng cảnh báo sẽ được hiển thị khi thẻ đã được xóa thành công và người dùng sẽ được chuyển hướng đến trang chủ.

Triển khai cho Vercel

Bây giờ, chúng tôi phải triển khai ứng dụng của mình cho Vercel để bất kỳ ai trên Internet đều có thể sử dụng nó!

Để triển khai Vercel, trước tiên bạn phải đẩy mã của mình vào kho lưu trữ, đăng nhập vào bảng điều khiển Vercel, nhấp vào Tạo dự án mới và nhấp vào kho lưu trữ mà bạn vừa đẩy mã của mình vào.

Nhập các biến môi trường mà chúng tôi đã tạo trước đó cùng với các giá trị của chúng ( NEXT_PUBLIC_SUPABASE_URLNEXT_PUBLIC_SUPABASE_ANON_KEY) trong trường Biến môi trường và nhấp vào Triển khai để triển khai ứng dụng của bạn vào phiên bản sản xuất.

Triển khai tới Vercel

Và chúng tôi đã có nó!

Sự kết luận

Cảm ơn bạn đã đọc! Tôi hy vọng hướng dẫn này cung cấp cho bạn kiến ​​thức bắt buộc cần thiết để tạo ứng dụng toàn ngăn xếp bằng Next.js và Supabase.

Bạn có thể tùy chỉnh kiểu cho trường hợp sử dụng của mình, vì hướng dẫn này chủ yếu tập trung vào logic của việc tạo một ứng dụng đầy đủ.

Nguồn: https://blog.logrocket.com/build-full-stack-app-next-js-supabase/

  #nextjs #supabase #fullstack 

Eva  Murphy

Eva Murphy

1625674200

Google analytics Setup with Next JS, React JS using Router Events - 14

In this video, we are going to implement Google Analytics to our Next JS application. Tracking page views of an application is very important.

Google analytics will allow us to track analytics information.

Frontend: https://github.com/amitavroy/video-reviews
API: https://github.com/amitavdevzone/video-review-api
App link: https://video-reviews.vercel.app

You can find me on:
Twitter: https://twitter.com/amitavroy7​
Discord: https://discord.gg/Em4nuvQk

#next js #js #react js #react #next #google analytics

Landen  Brown

Landen Brown

1626971820

Magic Link Authentication and Route Controls with Supabase and Next.js

In this video, we’ll build out a Next.js app that enables navigation, authentication, authorization, redirects (client and server-side), and a profile view with Supabase magic link authentication.

Code:
https://github.com/dabit3/supabase-nextjs-auth

0:00 - Intro
2:00 - Project setup
7:25 - Creating the sign in page
10:45 - Creating the profile page
17:33 - Creating the API route
18:25 - Updating _app.js to acc navigation and auth state
27:49 - Creating the protected page
30:11 - Running the project
32:00 - Conclusion

#next #next.js #supabase #magic link

Eva  Murphy

Eva Murphy

1625751960

Laravel API and React Next JS frontend development - 28

In this video, I wanted to touch upon the functionality of adding Chapters inside a Course. The idea was to not think much and start the development and pick up things as they come.

There are places where I get stuck and trying to find answers to it up doing what every developer does - Google and get help. I hope this will help you understand the flow and also how developers debug while doing development.

App url: https://video-reviews.vercel.app
Github code links below:
Next JS App: https://github.com/amitavroy/video-reviews
Laravel API: https://github.com/amitavdevzone/video-review-api

You can find me on:
Twitter: https://twitter.com/amitavroy7​
Discord: https://discord.gg/Em4nuvQk

#next js #api #react next js #next #frontend #development