This project is developed using React and Next.js, a robust framework that supports server-side rendering, static site generation, and more. Below is a comprehensive guide to setting up the project and the tools used.
To create a new Next.js application, use the following command:
npx create-next-app@latest
To start the development server, run the following command:
npm run dev
Then, open http://localhost:3000 in your browser to view the application.
Rafty UI is a UI component library used in this project for building user interfaces quickly. It provides a collection of reusable and customizable components that adhere to best practices and design guidelines.
-
Install the Rafty UI as a dependency:
npm install @rafty/ui
-
Install the Rafty Plugin as a development dependency:
npm install -D @rafty/plugin
For the styling to work in @rafty/ui, you need to make few changes in your tailwind.config.js file.
First, install the @rafty/plugin package as devDependency and add in your tailwind.config.js file
import type { Config } from "tailwindcss";
const config: Config = {
darkMode: "class",
content: [
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
"./components/**/*.{js,ts,jsx,tsx,mdx}",
"./app/**/*.{js,ts,jsx,tsx,mdx}",
"./node_modules/@rafty/ui/**/*.js",
],
plugins: [require("@rafty/plugin")],
};
export default config;
Here's an example of creating a Todo App using Rafty UI and Tailwind CSS:
const SAMPLE_DATA = [
{
id: 1,
status: false,
task: "Sample 1",
},
{
id: 2,
status: false,
task: "Sample 2",
},
{
id: 3,
status: false,
task: "Sample 3",
},
{
id: 4,
status: false,
task: "Sample 4",
},
];
import { Button, Checkbox, InputField } from "@rafty/ui";
export default function Home() {
return (
<div className="max-w-5xl mx-auto w-full h-full py-8 flex flex-col gap-5">
<h1 className="text-4xl text-center font-bold">Todo App</h1>
<div className="flex justify-between items-center gap-5">
<InputField placeholder="Enter task name" />
<Button type="submit" colorScheme="primary">
Add Task
</Button>
</div>
<div className="border-secondary-300 rounded-md border max-h-full overflow-y-auto divide-y">
{SAMPLE_DATA.map(({ id, status, task }) => (
<div key={id} className="flex items-center gap-4 p-4">
<Checkbox defaultChecked={status} />
<p className="font-medium">{task}</p>
<div className="flex-1" />
<Button size="sm" colorScheme="error">
Delete
</Button>
</div>
))}
</div>
</div>
);
}
Steps to integrate supabase in your application:
-
Sign Up for Supabase:
- Go to Supabase and sign up for a free account.
-
Create a New Project:
- Once signed in, create a new project and set up your database by following the prompts.
-
Get Your API Keys:
-
In the Supabase dashboard, navigate to the "Settings" > "Database" section.
-
You'll need the Connection String, both Transactional and Session Mode keys to connect your Next.js application to Supabase.
-
To run this project, you must configure environment variables. Add the following variables in your
.env
file:DATABASE_URL="postgresql://postgres.zqfsvdftwmotdadpsdvn:[YOUR-PASSWORD]@aws-0-ap-south-1.pooler.supabase.com:6543/postgres?pgbouncer=true&connection_limit=1" DIRECT_URL="postgresql://postgres.zqfsvdftwmotdadpsdvn:[YOUR-PASSWORD]@aws-0-ap-south-1.pooler.supabase.com:5432/postgres"
-
Steps to setup prisma in your application:
-
Install Prisma CLI:
-
Install the Prisma CLI as a development dependency:
npm install -D prisma
-
Install Prisma client:
npm install @prisma/client
-
-
Initialize Prisma:
-
Initialize Prisma in your project. This will create a prisma directory with a schema.prisma file:
npx prisma init
-
-
Define Your Schema:
-
Open the prisma/schema.prisma file and define your database schema using Prisma's schema language. For example:
generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") directUrl = env("DIRECT_URL") } model Todo { id String @id @default(cuid()) task String status Boolean @default(false) }
-
-
Generate Prisma Client:
-
Run the following command to generate the Prisma client:
npx prisma generate
-
-
Run Migrations:
- Create and run migrations to apply schema changes to your database:
npx prisma migrate dev --name init
- Create and run migrations to apply schema changes to your database:
Code for handling API routes related to todos, including fetching all todos (GET)
, creating a new todo (POST)
, updating a todo (PUT)
, and deleting a todo (DELETE)
.
This route handles fetching all tasks and creating a new task.
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
export async function GET() {
// Get all todos
const todos = await prisma.todo.findMany();
// Return the todos
return Response.json(todos);
}
export async function POST(req: Request) {
// Parse the JSON body
const body = await req.json();
// Create a new todo
const todo = await prisma.todo.create({
data: {
task: body.task,
},
});
// Return the new todo
return Response.json(todo);
}
This route handles updating and deleting a specific task.
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
export async function PUT(
req: Request,
{ params }: { params: { id: string } }
) {
// Parse the JSON body
const body = await req.json();
// Update the todo
await prisma.todo.update({
where: {
id: params.id,
},
data: {
status: body.status,
},
});
// Return an empty response
return Response.json({});
}
export async function DELETE(
req: Request,
{ params }: { params: { id: string } }
) {
// Delete the todo
await prisma.todo.delete({
where: {
id: params.id,
},
});
// Return an empty response
return Response.json({});
}
Steps to install and setup axios:
-
Install Axios :
npm install axios
-
Create an Axios Instance: The
endpoint
utility function is an Axios instance configured with a base URL (/api
). This setup simplifies making API requests by automatically appending the base URL to request paths.import axios from "axios"; export const endpoint = axios.create({ baseURL: "/api", });
"use client";
import { endpoint } from "@/utils";
import type { Todo } from "@prisma/client";
import { Button, Checkbox, InputField, Spinner } from "@rafty/ui";
import { useEffect, useState } from "react";
export default function Home() {
return (
<div className="max-w-5xl mx-auto w-full h-full py-8 flex flex-col gap-5">
<h1 className="text-4xl text-center font-bold">Todo App</h1>
<div className="flex justify-between items-center gap-5">
<InputField placeholder="Enter task name" />
<Button colorScheme="primary">Add Task</Button>
</div>
<Stack />
</div>
);
}
function Stack() {
const [data, setData] = useState<Todo[] | null>(null);
const [isLoading, setLoading] = useState<boolean>(true);
useEffect(() => {
setLoading(true);
endpoint.get<Todo[]>("/todos").then((res) => {
setData(res.data);
setLoading(false);
});
}, []);
if (isLoading)
return (
<div className="w-full h-full flex items-center justify-center gap-1">
<Spinner />
<p>Loading...</p>
</div>
);
if (data)
return (
<div className="border-secondary-300 rounded-md border max-h-full overflow-y-auto divide-y">
{data.length > 0 ? (
data.map(({ id, status, task }) => (
<div key={id} className="flex items-center gap-4 p-4">
<Checkbox defaultChecked={status} />
<p className="font-medium">{task}</p>
<div className="flex-1" />
<Button size="sm" colorScheme="error">
Delete
</Button>
</div>
))
) : (
<p className="py-6 text-center select-none font-medium text-secondary-500">
No Data Found
</p>
)}
</div>
);
}
Steps to setup Tanstack React Query:
-
Install Tanstack React Query
npm install @tanstack/react-query
Details on creating the UI using Rafty and Tailwind CSS, with code snippets for Home
, Layout
, Providers
, Form
, Stack
, and Card
components.
-
Create Query Client and add it to Query Client Provider.
"use client"; import { QueryClientProvider, QueryClient } from "@tanstack/react-query"; const queryClient = new QueryClient(); export default function Home() { return ( <QueryClientProvider client={queryClient}> <div className="max-w-5xl mx-auto w-full h-full py-8 flex flex-col gap-5"> <h1 className="text-4xl text-center font-bold">Todo App</h1> <Form /> <Stack /> </div> </QueryClientProvider> ); }
Create Form to add task:
"use client"; import { endpoint } from "@/utils"; import { Button, InputField } from "@rafty/ui"; import { useMutation, useQueryClient } from "@tanstack/react-query"; import { useState } from "react"; export function Form() { const client = useQueryClient(); const [value, setValue] = useState(""); const { mutateAsync, isPending } = useMutation({ mutationFn: (task: string) => endpoint.post("/todos", { task, }), onSuccess: () => { setValue(""); client.invalidateQueries({ queryKey: ["task"] }); }, onError: (err) => { console.error(err); }, }); return ( <form onSubmit={(e) => { e.preventDefault(); e.stopPropagation(); mutateAsync(value.trim()); }} className="flex justify-between items-center gap-5" > <InputField value={value} onChange={(event) => setValue(event.target.value)} placeholder="Enter task name" /> <Button type="submit" colorScheme="primary" isLoading={isPending} isDisabled={value.trim() === ""} > Add Task </Button> </form> ); }
Create Stack to display list if tasks:
"use client"; import { endpoint } from "@/utils"; import type { Todo } from "@prisma/client"; import { Spinner } from "@rafty/ui"; import { useQuery } from "@tanstack/react-query"; import { TodoCard } from "./TodoCard"; export function Stack() { const { data, isLoading, isError } = useQuery({ queryKey: ["task"], queryFn: () => endpoint.get<Todo[]>("/todos").then((res) => res.data), }); if (isLoading) return ( <div className="w-full h-full flex items-center justify-center gap-1"> <Spinner /> <p>Loading...</p> </div> ); if (isError) return ( <div className="flex w-full h-full items-center justify-center gap-1"> <p className="font-medium text-red-500">Error</p> </div> ); if (data) return ( <div className="border-secondary-300 rounded-md border max-h-full overflow-y-auto divide-y"> {data.length > 0 ? ( data.map((item, index) => ( <TodoCard key={`${index}-${item.task}`} {...item} /> )) ) : ( <p className="py-6 text-center select-none font-medium text-secondary-500"> No Data Found </p> )} </div> ); }
TodoCard component used in Stack component:
"use client"; import { endpoint } from "@/utils"; import type { Todo } from "@prisma/client"; import { Button, Checkbox, classNames } from "@rafty/ui"; import { useMutation, useQueryClient } from "@tanstack/react-query"; export function Card({ status, task, id }: Todo) { const client = useQueryClient(); const { mutateAsync: deleteTask, isPending: isDeleting } = useMutation({ mutationFn: (task_id: string) => endpoint.delete(`/todos/${task_id}`), onSuccess: () => { client.invalidateQueries({ queryKey: ["task"] }); console.log("Task Deleted!"); }, onError: (err) => { console.error(err); }, }); const { mutateAsync: setStatus, isPending: isSettingStatus } = useMutation({ mutationFn: (values: { task_id: string; check: boolean }) => endpoint.put(`/todos/${values.task_id}`, { status: values.check, }), onSuccess: () => { client.invalidateQueries({ queryKey: ["task"] }); console.log("Task Checked!"); }, onError: (err) => { console.error(err); }, }); return ( <div className="flex items-center gap-4 p-4"> <Checkbox checked={status} onCheckedChange={(check) => setStatus({ check: check as boolean, task_id: id, }) } isDisabled={isSettingStatus} /> <p className={classNames(isSettingStatus && "opacity-50", "font-medium")} > {task} </p> <div className="flex-1" /> <Button size="sm" colorScheme="error" onClick={() => deleteTask(id)} isLoading={isDeleting} loadingText="Deleting" isDisabled={isSettingStatus} > Delete </Button> </div> ); }