TaskFlow - Todo App with Next.js, Prisma, NextAuth
This commit is contained in:
228
README.md
228
README.md
@@ -1,44 +1,64 @@
|
|||||||
# TaskFlow - Ứng dụng Quản lý Công việc Cá nhân
|
# TaskFlow - Ung dung Quan ly Cong viec Ca nhan
|
||||||
|
|
||||||
Ứng dụng To-do List hiện đại với hệ thống xác thực người dùng, cho phép mỗi user quản lý công việc của riêng mình.
|
Ung dung To-do List hien dai voi he thong xac thuc nguoi dung, cho phep moi user quan ly cong viec cua rieng minh.
|
||||||
|
|
||||||
## 🚀 Tính năng
|
Demo: https://todo-app-topaz-nine-36.vercel.app
|
||||||
|
|
||||||
### Xác thực & Phân quyền
|
## Tinh nang
|
||||||
- ✅ Đăng ký / đăng nhập bằng Email & Password
|
|
||||||
- ✅ Đăng nhập bằng Google OAuth
|
|
||||||
- ✅ Mỗi user chỉ xem và thao tác trên công việc của chính mình
|
|
||||||
- ✅ Đăng xuất
|
|
||||||
|
|
||||||
### Quản lý Công việc (CRUD)
|
### Xac thuc va Phan quyen
|
||||||
- ✅ Tạo công việc mới (tiêu đề, mô tả, trạng thái, deadline)
|
- Dang ky / dang nhap bang Email va Password
|
||||||
- ✅ Sửa công việc
|
- Dang nhap bang Google OAuth
|
||||||
- ✅ Xóa công việc
|
- Moi user chi xem va thao tac tren cong viec cua chinh minh
|
||||||
- ✅ Xem danh sách công việc
|
- Dang xuat
|
||||||
|
|
||||||
### Tìm kiếm – Lọc – Sắp xếp
|
### Quan ly Cong viec (CRUD)
|
||||||
- ✅ Tìm kiếm theo tiêu đề
|
- Tao cong viec moi (tieu de, mo ta, trang thai, deadline)
|
||||||
- ✅ Lọc theo trạng thái (Todo / In Progress / Done)
|
- Sua cong viec
|
||||||
- ✅ Lọc theo deadline (hôm nay, tuần này, quá hạn)
|
- Xoa cong viec
|
||||||
- ✅ Sắp xếp theo deadline, trạng thái, thời gian tạo
|
- Xem danh sach cong viec
|
||||||
|
|
||||||
### Giao diện
|
### Tim kiem - Loc - Sap xep
|
||||||
- ✅ Responsive (desktop + mobile)
|
- Tim kiem theo tieu de
|
||||||
- ✅ Màu sắc rõ ràng cho từng trạng thái
|
- Loc theo trang thai (Todo / In Progress / Done)
|
||||||
- ✅ Hiển thị deadline sắp đến & quá hạn
|
- Loc theo deadline (hom nay, tuan nay, qua han)
|
||||||
- ✅ Loading states, empty states, thông báo
|
- Sap xep theo deadline, trang thai, thoi gian tao
|
||||||
|
|
||||||
## 🛠 Tech Stack
|
### Giao dien
|
||||||
|
- Responsive (desktop + mobile)
|
||||||
|
- Mau sac ro rang cho tung trang thai
|
||||||
|
- Hien thi deadline sap den va qua han
|
||||||
|
- Kanban Board (keo tha task giua cac cot)
|
||||||
|
- Calendar view hien thi task theo ngay
|
||||||
|
- Loading states, empty states, thong bao
|
||||||
|
|
||||||
| Layer | Technology |
|
## Tech Stack
|
||||||
|-------|------------|
|
|
||||||
| Frontend | Next.js 14 (App Router) + TypeScript |
|
|
||||||
| Styling | Tailwind CSS |
|
|
||||||
| Auth | NextAuth.js (Credentials + Google OAuth) |
|
|
||||||
| Database | PostgreSQL + Prisma ORM |
|
|
||||||
| Validation | Zod |
|
|
||||||
|
|
||||||
## 📁 Cấu trúc Project
|
### Frontend
|
||||||
|
- Next.js 14 (App Router)
|
||||||
|
- TypeScript
|
||||||
|
- Tailwind CSS
|
||||||
|
- React Hot Toast (thong bao)
|
||||||
|
- React Icons
|
||||||
|
|
||||||
|
### Backend
|
||||||
|
- Next.js API Routes
|
||||||
|
- NextAuth.js (xac thuc)
|
||||||
|
- Credentials Provider (email/password)
|
||||||
|
- Google OAuth Provider
|
||||||
|
|
||||||
|
### Database
|
||||||
|
- PostgreSQL (Neon - serverless)
|
||||||
|
- Prisma ORM
|
||||||
|
|
||||||
|
### Validation
|
||||||
|
- Zod
|
||||||
|
|
||||||
|
### Deployment
|
||||||
|
- Vercel (hosting)
|
||||||
|
- Neon (PostgreSQL database)
|
||||||
|
|
||||||
|
## Cau truc Project
|
||||||
|
|
||||||
```
|
```
|
||||||
todo-app/
|
todo-app/
|
||||||
@@ -70,47 +90,49 @@ todo-app/
|
|||||||
└── package.json
|
└── package.json
|
||||||
```
|
```
|
||||||
|
|
||||||
## 🔧 Hướng dẫn cài đặt
|
## Huong dan cai dat
|
||||||
|
|
||||||
### 1. Clone và cài đặt dependencies
|
### 1. Clone va cai dat dependencies
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd todo-app
|
cd todo-app
|
||||||
npm install
|
npm install
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Cấu hình biến môi trường
|
### 2. Cau hinh bien moi truong
|
||||||
|
|
||||||
Copy file `.env.example` thành `.env` và cập nhật các giá trị:
|
Copy file .env.example thanh .env va cap nhat cac gia tri:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cp .env.example .env
|
cp .env.example .env
|
||||||
```
|
```
|
||||||
|
|
||||||
```env
|
```env
|
||||||
# Database (PostgreSQL)
|
# Database (PostgreSQL - Neon)
|
||||||
DATABASE_URL="postgresql://user:password@localhost:5432/todoapp?schema=public"
|
DATABASE_URL="postgresql://user:password@host/database?sslmode=require"
|
||||||
|
DIRECT_URL="postgresql://user:password@host/database?sslmode=require"
|
||||||
|
|
||||||
# NextAuth
|
# NextAuth
|
||||||
NEXTAUTH_URL="http://localhost:3000"
|
NEXTAUTH_URL="http://localhost:3000"
|
||||||
NEXTAUTH_SECRET="your-super-secret-key-here"
|
NEXTAUTH_SECRET="your-super-secret-key-here"
|
||||||
|
|
||||||
# Google OAuth (lấy từ Google Cloud Console)
|
# Google OAuth (lay tu Google Cloud Console)
|
||||||
GOOGLE_CLIENT_ID="your-google-client-id"
|
GOOGLE_CLIENT_ID="your-google-client-id"
|
||||||
GOOGLE_CLIENT_SECRET="your-google-client-secret"
|
GOOGLE_CLIENT_SECRET="your-google-client-secret"
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. Thiết lập Google OAuth
|
### 3. Thiet lap Google OAuth
|
||||||
|
|
||||||
1. Vào [Google Cloud Console](https://console.cloud.google.com/)
|
1. Vao Google Cloud Console (https://console.cloud.google.com/)
|
||||||
2. Tạo project mới hoặc chọn project có sẵn
|
2. Tao project moi hoac chon project co san
|
||||||
3. Vào **APIs & Services** > **Credentials**
|
3. Vao APIs & Services > Credentials
|
||||||
4. Tạo **OAuth 2.0 Client IDs**
|
4. Tao OAuth 2.0 Client IDs
|
||||||
5. Thêm Authorized redirect URIs:
|
5. Them Authorized redirect URIs:
|
||||||
- `http://localhost:3000/api/auth/callback/google`
|
- http://localhost:3000/api/auth/callback/google
|
||||||
6. Copy Client ID và Client Secret vào file `.env`
|
- https://your-domain.vercel.app/api/auth/callback/google
|
||||||
|
6. Copy Client ID va Client Secret vao file .env
|
||||||
|
|
||||||
### 4. Khởi tạo Database
|
### 4. Khoi tao Database
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Generate Prisma client
|
# Generate Prisma client
|
||||||
@@ -123,92 +145,76 @@ npm run db:push
|
|||||||
npm run db:seed
|
npm run db:seed
|
||||||
```
|
```
|
||||||
|
|
||||||
### 5. Chạy ứng dụng
|
### 5. Chay ung dung
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run dev
|
npm run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
Mở [http://localhost:3000](http://localhost:3000) để xem kết quả.
|
Mo http://localhost:3000 de xem ket qua.
|
||||||
|
|
||||||
## 📊 API Endpoints
|
## API Endpoints
|
||||||
|
|
||||||
### Authentication
|
### Authentication
|
||||||
|
|
||||||
| Method | Endpoint | Mô tả |
|
| Method | Endpoint | Mo ta |
|
||||||
|--------|----------|-------|
|
|--------|----------|-------|
|
||||||
| POST | `/api/auth/register` | Đăng ký tài khoản mới |
|
| POST | /api/auth/register | Dang ky tai khoan moi |
|
||||||
| POST | `/api/auth/[...nextauth]` | NextAuth handlers |
|
| POST | /api/auth/[...nextauth] | NextAuth handlers |
|
||||||
|
|
||||||
### Tasks (Yêu cầu xác thực)
|
### Tasks (Yeu cau xac thuc)
|
||||||
|
|
||||||
| Method | Endpoint | Mô tả |
|
| Method | Endpoint | Mo ta |
|
||||||
|--------|----------|-------|
|
|--------|----------|-------|
|
||||||
| GET | `/api/tasks` | Lấy danh sách tasks của user |
|
| GET | /api/tasks | Lay danh sach tasks cua user |
|
||||||
| POST | `/api/tasks` | Tạo task mới |
|
| POST | /api/tasks | Tao task moi |
|
||||||
| GET | `/api/tasks/:id` | Lấy chi tiết task |
|
| GET | /api/tasks/:id | Lay chi tiet task |
|
||||||
| PUT | `/api/tasks/:id` | Cập nhật task |
|
| PUT | /api/tasks/:id | Cap nhat task |
|
||||||
| DELETE | `/api/tasks/:id` | Xóa task |
|
| DELETE | /api/tasks/:id | Xoa task |
|
||||||
|
|
||||||
**Query Parameters cho GET /api/tasks:**
|
Query Parameters cho GET /api/tasks:
|
||||||
- `search` - Tìm kiếm theo tiêu đề
|
- search: Tim kiem theo tieu de
|
||||||
- `status` - Lọc theo trạng thái (TODO, IN_PROGRESS, DONE)
|
- status: Loc theo trang thai (TODO, IN_PROGRESS, DONE)
|
||||||
- `deadline` - Lọc theo deadline (today, this_week, overdue)
|
- deadline: Loc theo deadline (today, this_week, overdue)
|
||||||
- `sortBy` - Sắp xếp theo (deadline, status, createdAt)
|
- sortBy: Sap xep theo (deadline, status, createdAt)
|
||||||
- `sortOrder` - Thứ tự (asc, desc)
|
- sortOrder: Thu tu (asc, desc)
|
||||||
|
|
||||||
## 🔐 Bảo mật
|
## Bao mat
|
||||||
|
|
||||||
- **Middleware Protection**: API routes được bảo vệ bằng NextAuth middleware
|
- Middleware Protection: API routes duoc bao ve bang NextAuth middleware
|
||||||
- **User Isolation**: Mỗi task được gắn với `userId`, API chỉ trả về tasks của user hiện tại
|
- User Isolation: Moi task duoc gan voi userId, API chi tra ve tasks cua user hien tai
|
||||||
- **Password Hashing**: Mật khẩu được hash bằng bcrypt
|
- Password Hashing: Mat khau duoc hash bang bcrypt
|
||||||
- **Input Validation**: Tất cả input được validate bằng Zod
|
- Input Validation: Tat ca input duoc validate bang Zod
|
||||||
|
|
||||||
## 🎨 Trạng thái Task
|
## Trang thai Task
|
||||||
|
|
||||||
| Status | Label | Màu |
|
| Status | Label | Mau |
|
||||||
|--------|-------|-----|
|
|--------|-------|-----|
|
||||||
| TODO | Chưa làm | 🟡 Vàng |
|
| TODO | Chua lam | Vang |
|
||||||
| IN_PROGRESS | Đang làm | 🔵 Xanh dương |
|
| IN_PROGRESS | Dang lam | Xanh duong |
|
||||||
| DONE | Hoàn thành | 🟢 Xanh lá |
|
| DONE | Hoan thanh | Xanh la |
|
||||||
| Overdue | Quá hạn | 🔴 Đỏ |
|
| Overdue | Qua han | Do |
|
||||||
|
|
||||||
## 📱 Screenshots
|
## Deploy
|
||||||
|
|
||||||
### Dashboard
|
### Vercel
|
||||||
- Thống kê tổng quan
|
|
||||||
- Bộ lọc và tìm kiếm
|
|
||||||
- Danh sách công việc
|
|
||||||
|
|
||||||
### Task Form
|
1. Push code len Git repository
|
||||||
- Tạo / sửa công việc
|
2. Import project vao Vercel
|
||||||
- Chọn trạng thái
|
3. Cau hinh Environment Variables:
|
||||||
- Đặt deadline
|
- DATABASE_URL
|
||||||
|
- DIRECT_URL
|
||||||
|
- NEXTAUTH_URL (URL production)
|
||||||
|
- NEXTAUTH_SECRET
|
||||||
|
- GOOGLE_CLIENT_ID
|
||||||
|
- GOOGLE_CLIENT_SECRET
|
||||||
|
4. Deploy
|
||||||
|
|
||||||
### Authentication
|
## Demo Account
|
||||||
- Đăng nhập / Đăng ký
|
|
||||||
- Google OAuth
|
|
||||||
|
|
||||||
## 🚀 Deploy
|
- Email: demo@example.com
|
||||||
|
- Password: demo123456
|
||||||
|
|
||||||
### Vercel (Recommended)
|
## License
|
||||||
|
|
||||||
1. Push code lên GitHub
|
MIT
|
||||||
2. Import project vào [Vercel](https://vercel.com)
|
|
||||||
3. Thêm biến môi trường
|
|
||||||
4. Deploy!
|
|
||||||
|
|
||||||
### Docker
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker-compose up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
## 📝 Demo Account
|
|
||||||
|
|
||||||
- **Email**: demo@example.com
|
|
||||||
- **Password**: demo123456
|
|
||||||
|
|
||||||
## 📄 License
|
|
||||||
|
|
||||||
MIT License
|
|
||||||
|
|||||||
@@ -7,9 +7,10 @@ import { updateTaskSchema } from '@/lib/validations'
|
|||||||
// GET /api/tasks/[id] - Lấy chi tiết task
|
// GET /api/tasks/[id] - Lấy chi tiết task
|
||||||
export async function GET(
|
export async function GET(
|
||||||
request: NextRequest,
|
request: NextRequest,
|
||||||
{ params }: { params: { id: string } }
|
{ params }: { params: Promise<{ id: string }> }
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
|
const { id } = await params
|
||||||
const session = await getServerSession(authOptions)
|
const session = await getServerSession(authOptions)
|
||||||
|
|
||||||
if (!session?.user?.id) {
|
if (!session?.user?.id) {
|
||||||
@@ -21,7 +22,7 @@ export async function GET(
|
|||||||
|
|
||||||
const task = await prisma.task.findFirst({
|
const task = await prisma.task.findFirst({
|
||||||
where: {
|
where: {
|
||||||
id: params.id,
|
id: id,
|
||||||
userId: session.user.id, // Chỉ lấy task của user hiện tại
|
userId: session.user.id, // Chỉ lấy task của user hiện tại
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -49,9 +50,10 @@ export async function GET(
|
|||||||
// PUT /api/tasks/[id] - Cập nhật task
|
// PUT /api/tasks/[id] - Cập nhật task
|
||||||
export async function PUT(
|
export async function PUT(
|
||||||
request: NextRequest,
|
request: NextRequest,
|
||||||
{ params }: { params: { id: string } }
|
{ params }: { params: Promise<{ id: string }> }
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
|
const { id } = await params
|
||||||
const session = await getServerSession(authOptions)
|
const session = await getServerSession(authOptions)
|
||||||
|
|
||||||
if (!session?.user?.id) {
|
if (!session?.user?.id) {
|
||||||
@@ -64,7 +66,7 @@ export async function PUT(
|
|||||||
// Check if task belongs to user
|
// Check if task belongs to user
|
||||||
const existingTask = await prisma.task.findFirst({
|
const existingTask = await prisma.task.findFirst({
|
||||||
where: {
|
where: {
|
||||||
id: params.id,
|
id: id,
|
||||||
userId: session.user.id,
|
userId: session.user.id,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -94,7 +96,7 @@ export async function PUT(
|
|||||||
|
|
||||||
// Update task
|
// Update task
|
||||||
const task = await prisma.task.update({
|
const task = await prisma.task.update({
|
||||||
where: { id: params.id },
|
where: { id: id },
|
||||||
data: {
|
data: {
|
||||||
...(title && { title }),
|
...(title && { title }),
|
||||||
...(description !== undefined && { description }),
|
...(description !== undefined && { description }),
|
||||||
@@ -122,9 +124,10 @@ export async function PUT(
|
|||||||
// DELETE /api/tasks/[id] - Xóa task
|
// DELETE /api/tasks/[id] - Xóa task
|
||||||
export async function DELETE(
|
export async function DELETE(
|
||||||
request: NextRequest,
|
request: NextRequest,
|
||||||
{ params }: { params: { id: string } }
|
{ params }: { params: Promise<{ id: string }> }
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
|
const { id } = await params
|
||||||
const session = await getServerSession(authOptions)
|
const session = await getServerSession(authOptions)
|
||||||
|
|
||||||
if (!session?.user?.id) {
|
if (!session?.user?.id) {
|
||||||
@@ -137,7 +140,7 @@ export async function DELETE(
|
|||||||
// Check if task belongs to user
|
// Check if task belongs to user
|
||||||
const existingTask = await prisma.task.findFirst({
|
const existingTask = await prisma.task.findFirst({
|
||||||
where: {
|
where: {
|
||||||
id: params.id,
|
id: id,
|
||||||
userId: session.user.id,
|
userId: session.user.id,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -151,7 +154,7 @@ export async function DELETE(
|
|||||||
|
|
||||||
// Delete task
|
// Delete task
|
||||||
await prisma.task.delete({
|
await prisma.task.delete({
|
||||||
where: { id: params.id },
|
where: { id: id },
|
||||||
})
|
})
|
||||||
|
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
|
|||||||
Reference in New Issue
Block a user