Skip to content

Commit fcdc2d9

Browse files
committed
Add redirects to dashboard and enhance task category access control
1 parent 1eb389f commit fcdc2d9

File tree

8 files changed

+98
-33
lines changed

8 files changed

+98
-33
lines changed

next.config.mjs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,15 @@ import { withPayload } from '@payloadcms/next/withPayload'
44
const nextConfig = {
55
// Your Next.js config here
66
output: 'standalone',
7+
redirects: async () => {
8+
return [
9+
{
10+
source: '/',
11+
destination: '/dashboard',
12+
permanent: false,
13+
},
14+
]
15+
},
716
}
817

918
export default withPayload(nextConfig)

src/app/(my-app)/dashboard/page.tsx

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,17 @@
11
'use client'
22

33
import { useState, useEffect } from 'react'
4-
import { Button } from '@/components/ui/button'
5-
import { Input } from '@/components/ui/input'
64
import { Sidebar } from '@/components/Sidebar'
75
import { TaskList } from '@/components/TaskList'
86
import { AddTaskForm } from '@/components/AddTaskForm'
97
import { OnboardingTips } from '@/components/OnboardingTips'
108
import { addTask, getTasks } from '@/lib/client/api'
119
import type { Category, Task } from '@/lib/types'
1210
import { ProtectedRoute } from '@/components/ProtectedRoute'
13-
import { useAuth } from '@/context/AuthContext'
1411

1512
export default function DashboardPage() {
1613
const [selectedCategory, setSelectedCategory] = useState<Category | null>(null)
1714
const [tasks, setTasks] = useState<Task[]>([])
18-
const [hashtag, setHashtag] = useState('')
19-
const { user, logout } = useAuth()
2015

2116
useEffect(() => {
2217
fetchTasks()
@@ -44,13 +39,6 @@ export default function DashboardPage() {
4439
}
4540
}
4641

47-
const filteredTasks = tasks
48-
.filter((task) => selectedCategory?.id === 'all' || task.categoryId === selectedCategory?.id)
49-
.filter(
50-
(task) =>
51-
!hashtag || task.hashtags.some((h) => h.toLowerCase().includes(hashtag.toLowerCase())),
52-
)
53-
5442
return (
5543
<ProtectedRoute>
5644
<div className="flex h-screen bg-gray-100">
@@ -63,22 +51,12 @@ export default function DashboardPage() {
6351
<main className="flex-1 p-8 overflow-auto">
6452
<div className="max-w-4xl mx-auto">
6553
<div className="mb-8 flex items-center justify-between">
66-
<h1 className="text-2xl font-bold">{selectedCategory?.name || 'All Tasks'}</h1>
54+
<h1 className="text-2xl font-bold">{selectedCategory?.name ?? 'All Tasks'}</h1>
6755
<div className="flex items-center space-x-4">
6856
<AddTaskForm onAddTask={handleAddTask} selectedCategory={selectedCategory} />
6957
</div>
7058
</div>
7159

72-
<div className="mb-4">
73-
<Input
74-
type="text"
75-
placeholder="Filter by hashtag"
76-
value={hashtag}
77-
onChange={(e) => setHashtag(e.target.value)}
78-
className="w-full"
79-
/>
80-
</div>
81-
8260
<TaskList tasks={tasks} onTasksChange={fetchTasks} />
8361
</div>
8462
</main>

src/collections/Categories.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,27 @@ const Categories: CollectionConfig = {
55
admin: {
66
useAsTitle: 'name',
77
},
8+
access: {
9+
read: ({ req }) => {
10+
if (!req.user) {
11+
return false
12+
}
13+
14+
if (req.user.role === 'user') {
15+
return {
16+
user: {
17+
equals: req.user.id,
18+
},
19+
}
20+
}
21+
22+
if (req.user.role === 'admin') {
23+
return true
24+
}
25+
26+
return false
27+
},
28+
},
829
fields: [
930
{
1031
name: 'name',

src/collections/Tasks.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { CollectionConfig } from 'payload'
2+
import mongoose from 'mongoose'
23

34
const Tasks: CollectionConfig = {
45
slug: 'tasks',
@@ -49,6 +50,37 @@ const Tasks: CollectionConfig = {
4950
},
5051
],
5152
},
53+
endpoints: [
54+
{
55+
path: '/categories-count',
56+
method: 'get',
57+
handler: async (req) => {
58+
if (!req.user) {
59+
throw new Error('You must be logged in to view tasks')
60+
}
61+
62+
const taskCollection = req.payload.db.collections.tasks
63+
64+
// aggregate the count of tasks for each category using mongodb
65+
const result = await taskCollection.aggregate([
66+
{
67+
$match: {
68+
user: new mongoose.Types.ObjectId(req.user.id),
69+
category: { $ne: null },
70+
},
71+
},
72+
{
73+
$group: {
74+
_id: '$category',
75+
count: { $sum: 1 },
76+
},
77+
},
78+
])
79+
80+
return Response.json({ stats: result.map((r) => ({ category: r._id, count: r.count })) })
81+
},
82+
},
83+
],
5284
fields: [
5385
{
5486
name: 'title',

src/components/Sidebar.tsx

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,35 +5,52 @@ import { Search, Plus, Check, X, Pencil, Trash } from 'lucide-react'
55
import { Input } from '@/components/ui/input'
66
import { Button } from '@/components/ui/button'
77
import { UserProfile } from './UserProfile'
8-
import { getCategories, addCategory, updateCategory, deleteCategory } from '@/lib/client/api'
8+
import {
9+
getCategories,
10+
addCategory,
11+
updateCategory,
12+
deleteCategory,
13+
getTaskCategoryCount,
14+
} from '@/lib/client/api'
915
import type { Category, Task } from '@/lib/types'
10-
import { useAuth } from '@/context/AuthContext'
1116

1217
interface SidebarProps {
1318
onSelectCategory: (category: Category) => void
1419
selectedCategory: Category | null
1520
tasks: Task[]
1621
}
1722

18-
export function Sidebar({ onSelectCategory, selectedCategory, tasks }: SidebarProps) {
23+
export function Sidebar({ onSelectCategory, selectedCategory, tasks }: Readonly<SidebarProps>) {
1924
const [categories, setCategories] = useState<Category[]>([])
2025
const [searchTerm, setSearchTerm] = useState('')
2126
const [newCategory, setNewCategory] = useState('')
22-
const [editingId, setEditingId] = useState<string | null>(null)
2327
const [editingName, setEditingName] = useState('')
2428
const [isLoading, setIsLoading] = useState(true)
2529
const [editingCategoryId, setEditingCategoryId] = useState<string | null>(null)
26-
const { user } = useAuth()
30+
const [stats, setStats] = useState<{ category: string; count: number }[]>([])
2731

2832
useEffect(() => {
2933
fetchCategories()
34+
fetchTasksCount()
3035
}, [])
3136

37+
useEffect(() => {
38+
fetchTasksCount()
39+
}, [tasks])
40+
41+
const fetchTasksCount = async () => {
42+
const fetchedTasks = await getTaskCategoryCount()
43+
setStats(fetchedTasks.stats)
44+
}
45+
3246
const fetchCategories = async () => {
3347
setIsLoading(true)
3448
try {
3549
const fetchedCategories = await getCategories()
3650
setCategories(fetchedCategories)
51+
if (fetchedCategories.length > 0) {
52+
onSelectCategory(fetchedCategories[0])
53+
}
3754
} catch (error) {
3855
console.error('Failed to fetch categories:', error)
3956
} finally {
@@ -76,11 +93,11 @@ export function Sidebar({ onSelectCategory, selectedCategory, tasks }: SidebarPr
7693
if (categoryId === 'all') {
7794
return tasks.length
7895
}
79-
return tasks.filter((task) => task.categoryId === categoryId).length
96+
return stats.find((stat) => stat.category === categoryId)?.count ?? 0
8097
}
8198

8299
return (
83-
<div className="w-64 border-r h-screen flex flex-col bg-white">
100+
<div className="w-80 border-r h-screen flex flex-col bg-white">
84101
<UserProfile />
85102

86103
<div className="p-4">

src/context/AuthContext.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
4444
setIsLoading(true)
4545
try {
4646
const loggedInUser = await loginUser({ email, password })
47-
setUser(loggedInUser)
47+
setUser(loggedInUser.user)
4848
router.push('/dashboard')
4949
} catch (error) {
5050
console.error('Login error:', error)

src/lib/client/api.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,15 @@ export async function deleteCategory(id: string): Promise<void> {
102102
}
103103

104104
// Task-related functions
105+
export async function getTaskCategoryCount(): Promise<{
106+
stats: { category: string; count: number }[]
107+
}> {
108+
const response = await fetch(`${API_URL}/tasks/categories-count`, {
109+
credentials: 'include',
110+
})
111+
return handleResponse(response)
112+
}
113+
105114
export async function getTasks(category: string): Promise<Task[]> {
106115
const response = await fetch(`${API_URL}/tasks?where[category][equals]=${category}`, {
107116
credentials: 'include',

src/lib/types.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,10 @@ export interface Task {
2121
title: string
2222
description?: string
2323
completed: boolean
24-
categoryId?: string
24+
category?: string
2525
hashtags: string[]
2626
dueDate?: string
2727
reminder?: string
2828
steps: Step[]
2929
notes: Note[]
3030
}
31-

0 commit comments

Comments
 (0)