1660881900
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!
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ọ.
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à:
auth.users
bả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.Để 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-supabase
là 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.js
tệp của mình và thay thế bằng h1
tiê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 dev
trong 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:
Để 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.
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 .
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.
Khi dự án đã được tạo, bạn sẽ thấy một bảng điều khiển như sau:
Đố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);
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
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 Supabase với ứng dụng Next.js của chúng tôi, chúng tôi sẽ cần URL dự án và Anon 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:
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.local
tệ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.local
tệ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.local
vàogitignore
tệ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.js
tệ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 createClient
hàm từ Supabase và tạo một biến được gọi là supabase
. Chúng tôi gọi createClient
hà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!
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 và Đă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 , Logout và Create 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 component
thư mục chứa Navbar.js
và Footer.js
các tệp. Sau đó, bên trong _app.js
, chúng tôi sẽ bọc pages
thành phần của mình bằng Navbar
và các Footer
thà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:
Để 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.js
tệp của mình và tạo một validateUser
hà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.
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:
Bây giờ, chúng ta có thể nhấp vào nút Đăng ký và nhập email.
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:
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!
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.
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.js
tệ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 session
trúc đối tượng mà chúng tôi đã chuyển từ các page
đạo cụ trong _app.js
tệ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:, workouts
một mảng trống và một loading
trạ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 select
phươ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 false
khi 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ị WorkoutCard
thành phần.
Trong WorkoutCard
thà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-fns
thư 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;
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.js
và Create.module.css
các tệp trong thư mục pages
và styles
tươ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 workoutsData
trạ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 createWorkout
sử 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.push
phương pháp này để điều hướng người dùng quay lại trang chủ.
Để cập nhật bài tập, chúng tôi sẽ tạo một thư mục có tên edit
trong pages
thư mục chứa [id].js
tệ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 id
chủ sở hữu được ủy quyền và của thẻ đó. Sau đó, chúng tôi sẽ tạo một updateWorkout
chứ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 id
thẻ của thẻ đó bằng cách sử dụng useRouter
hook. Hàm getWorkout
gọi phiên bản máy khách Supabase để lọc id
thẻ 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 updateWorkout
chứ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”:
Để xóa một bài tập trên mỗi thẻ, chúng tôi sẽ tạo handleDelete
hàm sẽ lấy id
là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 id
hà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 WorkoutCard
thành phần trong index.js
tệ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ủ.
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_URL
và NEXT_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.
Và chúng tôi đã có 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/
1632537859
Not babashka. Node.js babashka!?
Ad-hoc CLJS scripting on Node.js.
Experimental. Please report issues here.
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:
Nbb requires Node.js v12 or newer.
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).
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
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
.
$ 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
.
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.
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.
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"
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]))
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.
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:
:syms
.-x
notation. In nbb, you must use keywords.See the example of what is currently supported.
See the examples directory for small examples.
Also check out these projects built with nbb:
See API documentation.
See this gist on how to convert an nbb script or project to shadow-cljs.
Prequisites:
To build:
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
1660881900
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!
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ọ.
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à:
auth.users
bả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.Để 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-supabase
là 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.js
tệp của mình và thay thế bằng h1
tiê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 dev
trong 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:
Để 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.
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 .
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.
Khi dự án đã được tạo, bạn sẽ thấy một bảng điều khiển như sau:
Đố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);
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
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 Supabase với ứng dụng Next.js của chúng tôi, chúng tôi sẽ cần URL dự án và Anon 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:
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.local
tệ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.local
tệ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.local
vàogitignore
tệ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.js
tệ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 createClient
hàm từ Supabase và tạo một biến được gọi là supabase
. Chúng tôi gọi createClient
hà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!
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 và Đă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 , Logout và Create 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 component
thư mục chứa Navbar.js
và Footer.js
các tệp. Sau đó, bên trong _app.js
, chúng tôi sẽ bọc pages
thành phần của mình bằng Navbar
và các Footer
thà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:
Để 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.js
tệp của mình và tạo một validateUser
hà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.
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:
Bây giờ, chúng ta có thể nhấp vào nút Đăng ký và nhập email.
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:
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!
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.
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.js
tệ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 session
trúc đối tượng mà chúng tôi đã chuyển từ các page
đạo cụ trong _app.js
tệ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:, workouts
một mảng trống và một loading
trạ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 select
phươ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 false
khi 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ị WorkoutCard
thành phần.
Trong WorkoutCard
thà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-fns
thư 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;
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.js
và Create.module.css
các tệp trong thư mục pages
và styles
tươ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 workoutsData
trạ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 createWorkout
sử 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.push
phương pháp này để điều hướng người dùng quay lại trang chủ.
Để cập nhật bài tập, chúng tôi sẽ tạo một thư mục có tên edit
trong pages
thư mục chứa [id].js
tệ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 id
chủ sở hữu được ủy quyền và của thẻ đó. Sau đó, chúng tôi sẽ tạo một updateWorkout
chứ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 id
thẻ của thẻ đó bằng cách sử dụng useRouter
hook. Hàm getWorkout
gọi phiên bản máy khách Supabase để lọc id
thẻ 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 updateWorkout
chứ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”:
Để xóa một bài tập trên mỗi thẻ, chúng tôi sẽ tạo handleDelete
hàm sẽ lấy id
là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 id
hà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 WorkoutCard
thành phần trong index.js
tệ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ủ.
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_URL
và NEXT_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.
Và chúng tôi đã có 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/
1625674200
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
1626971820
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
1625751960
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