From c800501726f413a197c99a78a051c1002621626c Mon Sep 17 00:00:00 2001 From: rdev-worker Date: Sat, 31 Jan 2026 09:25:08 +0000 Subject: [PATCH] build: Build a full-stack task management application with the following str... --- backend/Dockerfile | 6 +- backend/go.mod | 2 +- backend/main.go | 33 ++++-- docker-compose.yml | 4 +- frontend/next.config.js | 2 +- frontend/package.json | 1 + frontend/src/app/page.tsx | 158 ++++++++++++++++++++----- frontend/src/components/ui/badge.tsx | 41 +++++++ frontend/src/components/ui/select.tsx | 160 ++++++++++++++++++++++++++ frontend/src/lib/api.ts | 10 +- 10 files changed, 370 insertions(+), 47 deletions(-) create mode 100644 frontend/src/components/ui/badge.tsx create mode 100644 frontend/src/components/ui/select.tsx diff --git a/backend/Dockerfile b/backend/Dockerfile index 0813bae..1e36ac1 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -3,9 +3,9 @@ FROM golang:1.22-alpine AS builder WORKDIR /app -# Copy go mod files +# Copy go mod files and download dependencies COPY go.mod go.sum* ./ -RUN go mod download +RUN go mod download || go mod tidy # Copy source code COPY . . @@ -25,7 +25,7 @@ RUN apk --no-cache add ca-certificates COPY --from=builder /app/main . # Expose port -EXPOSE 8081 +EXPOSE 8080 # Run the binary CMD ["./main"] diff --git a/backend/go.mod b/backend/go.mod index 35c0320..3478898 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -1,4 +1,4 @@ -module github.com/jordan/fs3/backend +module backend go 1.22 diff --git a/backend/main.go b/backend/main.go index 57d6da9..51ca6e6 100644 --- a/backend/main.go +++ b/backend/main.go @@ -17,8 +17,10 @@ type Task struct { ID int `json:"id"` Title string `json:"title"` Description string `json:"description"` - Completed bool `json:"completed"` + Status string `json:"status"` + Priority string `json:"priority"` CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` } type TaskStore struct { @@ -53,16 +55,26 @@ func (s *TaskStore) Get(id int) (Task, bool) { return task, ok } -func (s *TaskStore) Create(title, description string) Task { +func (s *TaskStore) Create(title, description, status, priority string) Task { s.mu.Lock() defer s.mu.Unlock() + if status == "" { + status = "pending" + } + if priority == "" { + priority = "medium" + } + + now := time.Now() task := Task{ ID: s.nextID, Title: title, Description: description, - Completed: false, - CreatedAt: time.Now(), + Status: status, + Priority: priority, + CreatedAt: now, + UpdatedAt: now, } s.tasks[s.nextID] = task s.nextID++ @@ -96,9 +108,10 @@ func main() { store := NewTaskStore() // Add some sample tasks - store.Create("Welcome to Task Manager", "This is your first task. Click on it to see details!") - store.Create("Add a new task", "Use the Add Task button to create new tasks") - store.Create("Stay organized", "Keep track of all your tasks in one place") + store.Create("Set up development environment", "Install all necessary tools and dependencies for the project", "completed", "high") + store.Create("Design database schema", "Create the initial database schema for the application", "in-progress", "high") + store.Create("Write unit tests", "Add comprehensive unit tests for all core functionality", "pending", "medium") + store.Create("Deploy to production", "Set up CI/CD pipeline and deploy to cloud", "pending", "low") r := chi.NewRouter() @@ -135,6 +148,8 @@ func main() { var input struct { Title string `json:"title"` Description string `json:"description"` + Status string `json:"status"` + Priority string `json:"priority"` } if err := json.NewDecoder(r.Body).Decode(&input); err != nil { @@ -147,7 +162,7 @@ func main() { return } - task := store.Create(input.Title, input.Description) + task := store.Create(input.Title, input.Description, input.Status, input.Priority) jsonResponse(w, http.StatusCreated, APIResponse{Success: true, Data: task}) }) @@ -190,7 +205,7 @@ func main() { jsonResponse(w, http.StatusOK, APIResponse{Success: true, Data: "OK"}) }) - port := ":8081" + port := ":8080" log.Printf("Backend server starting on %s", port) if err := http.ListenAndServe(port, r); err != nil { log.Fatal(err) diff --git a/docker-compose.yml b/docker-compose.yml index bc5858e..1c79f14 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,7 +6,7 @@ services: ports: - "8080:3000" environment: - - BACKEND_URL=http://backend:8081 + - BACKEND_URL=http://backend:8080 depends_on: - backend restart: unless-stopped @@ -16,5 +16,5 @@ services: context: ./backend dockerfile: Dockerfile ports: - - "8081:8081" + - "8081:8080" restart: unless-stopped diff --git a/frontend/next.config.js b/frontend/next.config.js index cab1c43..be86f12 100644 --- a/frontend/next.config.js +++ b/frontend/next.config.js @@ -7,7 +7,7 @@ const nextConfig = { source: '/api/:path*', destination: process.env.BACKEND_URL ? `${process.env.BACKEND_URL}/api/:path*` - : 'http://backend:8081/api/:path*', + : 'http://backend:8080/api/:path*', }, ]; }, diff --git a/frontend/package.json b/frontend/package.json index 8661a77..dfeedcc 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -17,6 +17,7 @@ "tailwind-merge": "^2.3.0", "lucide-react": "^0.378.0", "@radix-ui/react-dialog": "^1.0.5", + "@radix-ui/react-select": "^2.0.0", "@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-toast": "^1.1.5" }, diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index 477194c..9f5c485 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -1,7 +1,15 @@ "use client"; import { useState, useEffect } from "react"; -import { Plus, Trash2, Eye, CheckCircle2, Circle } from "lucide-react"; +import { + Plus, + Trash2, + Eye, + CheckCircle2, + Circle, + Clock, + AlertCircle, +} from "lucide-react"; import { Button } from "@/components/ui/button"; import { Card, @@ -21,8 +29,49 @@ import { DialogTitle, DialogTrigger, } from "@/components/ui/dialog"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Badge } from "@/components/ui/badge"; import { getTasks, createTask, deleteTask, getTask, Task } from "@/lib/api"; +function getStatusIcon(status: string) { + switch (status) { + case "completed": + return ; + case "in-progress": + return ; + default: + return ; + } +} + +function getStatusBadge(status: string) { + switch (status) { + case "completed": + return Completed; + case "in-progress": + return In Progress; + default: + return Pending; + } +} + +function getPriorityBadge(priority: string) { + switch (priority) { + case "high": + return High; + case "medium": + return Medium; + default: + return Low; + } +} + export default function Home() { const [tasks, setTasks] = useState([]); const [loading, setLoading] = useState(true); @@ -31,6 +80,8 @@ export default function Home() { // Add task form state const [newTitle, setNewTitle] = useState(""); const [newDescription, setNewDescription] = useState(""); + const [newStatus, setNewStatus] = useState("pending"); + const [newPriority, setNewPriority] = useState("medium"); const [addDialogOpen, setAddDialogOpen] = useState(false); const [adding, setAdding] = useState(false); @@ -65,10 +116,17 @@ export default function Home() { try { setAdding(true); - const task = await createTask(newTitle.trim(), newDescription.trim()); + const task = await createTask( + newTitle.trim(), + newDescription.trim(), + newStatus, + newPriority + ); setTasks((prev) => [...prev, task]); setNewTitle(""); setNewDescription(""); + setNewStatus("pending"); + setNewPriority("medium"); setAddDialogOpen(false); } catch (err) { setError(err instanceof Error ? err.message : "Failed to add task"); @@ -159,6 +217,43 @@ export default function Home() { rows={3} /> +
+
+ + +
+
+ + +
+