Skip to content

Commit ba1abd4

Browse files
authored
Adds popups for successful crud operations (#12)
* fix: fix bug in comparator function * feat(ui): add notifications to edit train log form * feat(ui): implement admin views for training logs, animals, users * fix(crud): fix bug in comparator func * chore: add react-toast to deps * feat(ui): add detail to empty data view * fix: inconsistent padding on form headers * feat(ui): add detailed validation messages to form
1 parent ea75afe commit ba1abd4

26 files changed

Lines changed: 468 additions & 186 deletions

File tree

‎package-lock.json‎

Lines changed: 34 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
"next": "16.0.1",
1717
"next-auth": "^4.24.13",
1818
"react": "19.2.0",
19-
"react-dom": "19.2.0"
19+
"react-dom": "19.2.0",
20+
"react-loading-skeleton": "^3.5.0",
21+
"react-toastify": "^11.0.5"
2022
},
2123
"devDependencies": {
2224
"@playwright/test": "^1.56.1",

‎server/mongodb/actions/findAllTrainingLogs.ts‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ import { TrainingLog, TrainingLogDocument } from '../models/TrainingLog';
44
const findAllTrainingLogs = async (): Promise<TrainingLogDocument[]> => {
55
try {
66
await connectDB();
7-
const logs = await TrainingLog.find({}).exec();
7+
const logs = (await TrainingLog.find({}).exec()).sort((a, b) =>
8+
a.date < b.date ? -1 : b.date > a.date ? 1 : 0
9+
);
10+
console.log('Sorted training logs ', logs);
811
return logs;
912
} catch (err) {
1013
console.error(`[ERROR]: Error encountered while creating user: ${err}`);
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import connectDB from '..';
2+
import { User, UserDocument } from '../models/User';
3+
4+
const findAllUsers = async (): Promise<UserDocument[]> => {
5+
try {
6+
await connectDB();
7+
const users = await User.find({}).exec();
8+
return users;
9+
} catch (err) {
10+
console.error(`[ERROR]: Error while searching for users: ${err}`);
11+
return [];
12+
}
13+
};
14+
15+
export default findAllUsers;

‎src/app/api/animal/route.ts‎

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
import { NextRequest } from 'next/server';
22
import createAnimal from '../../../../server/mongodb/actions/createAnimal';
3-
import updateAnimal from '../../../../server/mongodb/actions/updateAnimal';
4-
import { getServerSession } from 'next-auth';
53
import findAnimals from '../../../../server/mongodb/actions/findAllAnimals';
64
import findAnimalsByOwner from '../../../../server/mongodb/actions/findAnimalsByOwner';
75
import { auth } from '@/lib/auth';
@@ -50,8 +48,8 @@ export const POST = async (req: NextRequest): Promise<Response> => {
5048
data.profilePicture = '/images/appLogo.png';
5149

5250
const animals = await findAnimalsByOwner({ id: userId });
53-
54-
if (animals?.filter((a) => a.name == data.name)) {
51+
const animalsMatchingName = animals?.filter((a) => a.name == data.name);
52+
if (animalsMatchingName && animalsMatchingName.length > 0) {
5553
return new Response(
5654
JSON.stringify({
5755
error: 'An animal with this name already exists',

‎src/app/api/user/[id]/route.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export const GET = async (
2121
}
2222

2323
const user = await findUserById(id);
24-
console.log('Found user ', user);
24+
2525
if (user) {
2626
return new Response(JSON.stringify({ data: user || undefined }), {
2727
status: 200,

‎src/app/api/user/route.ts‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { auth } from '@/lib/auth';
2+
import { NextRequest } from 'next/server';
3+
import findAllUsers from '../../../../server/mongodb/actions/findAllUsers';
4+
5+
export const GET = async (req: NextRequest): Promise<Response> => {
6+
const session = await auth();
7+
const isAdmin = (session?.user as any).admin;
8+
9+
if (!isAdmin) {
10+
return new Response(
11+
JSON.stringify({
12+
error: 'user is not authorized to query all users',
13+
}),
14+
{ status: 403 }
15+
);
16+
}
17+
18+
const users = await findAllUsers();
19+
return new Response(JSON.stringify({ data: users }), {
20+
status: 200,
21+
});
22+
};

‎src/app/dashboard/admin/animals/page.tsx‎

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,35 @@
1+
'use client';
12
import Link from 'next/link';
23
import Image from 'next/image';
4+
import { AnimalDocument } from '../../../../../server/mongodb/models/Animal';
5+
import { useEffect, useState } from 'react';
6+
import AnimalCard from '@/components/AnimalCard';
37

48
export default function AdminAnimalsPage() {
9+
const [isLoading, setIsLoading] = useState(true);
10+
const [animals, setAnimals] = useState<AnimalDocument[]>([]);
11+
12+
useEffect(() => {
13+
const fetchAnimals = async () => {
14+
try {
15+
const response = await fetch('/api/animal?all=true', {
16+
method: 'GET',
17+
credentials: 'include',
18+
});
19+
const respBody = await response.json();
20+
const animals: AnimalDocument[] = respBody?.data;
21+
22+
if (animals != undefined) {
23+
setAnimals(animals);
24+
}
25+
setIsLoading(false);
26+
} catch (error) {
27+
setIsLoading(false);
28+
}
29+
};
30+
31+
fetchAnimals();
32+
}, [isLoading]);
533
return (
634
<main className="min-h-full w-full bg-white dark:bg-black flex flex-col">
735
<div className="p-8 flex flex-row justify-between items-center">
@@ -24,7 +52,19 @@ export default function AdminAnimalsPage() {
2452
</div>
2553

2654
<hr />
27-
<div className="mt-8 mx-4 grid xl:grid-cols-3 gap-1"></div>
55+
<div className="mt-8 mx-4 grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4">
56+
{animals.map((a, i) => {
57+
return (
58+
<div
59+
className="flex items-center justify-center "
60+
key={i}
61+
>
62+
<AnimalCard data={a} />
63+
</div>
64+
);
65+
})}
66+
{!isLoading && animals.length == 0 && <p>No animals yet!</p>}
67+
</div>
2868
</main>
2969
);
3070
}

‎src/app/dashboard/admin/layout.tsx‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,16 @@ export default function RootLayout({
99
}: Readonly<{
1010
children: React.ReactNode;
1111
}>) {
12-
const { data: session} = useSession();
12+
const { data: session } = useSession();
1313
const isAdmin = session?.user && (session?.user as any).admin;
1414

1515
return isAdmin ? (
16-
<div>{children}</div>
16+
<div className="h-full">{children}</div>
1717
) : (
18-
<div className='h-full flex flex-row justify-center items-center'>
19-
<p className='rounded-2xl bg-red-300 p-4'>You are not authorized to view this page</p>
18+
<div className="h-full flex flex-row justify-center items-center">
19+
<p className="rounded-2xl bg-red-300 p-4">
20+
You are not authorized to view this page
21+
</p>
2022
</div>
2123
);
2224
}
Lines changed: 55 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,63 @@
1-
import LoginForm from '@/components/LoginForm';
1+
'use client';
22
import TrainingLogCard, { mockData } from '@/components/TrainingLogCard';
3-
import Link from 'next/link';
4-
import Image from 'next/image';
3+
import { useEffect, useState } from 'react';
4+
import { TrainingLogDocument } from '../../../../../server/mongodb/models/TrainingLog';
5+
import Skeleton from 'react-loading-skeleton';
56

67
export default function AdminTrainingLogsPage() {
8+
const [isLoading, setIsLoading] = useState(true);
9+
const [logs, setLogs] = useState<TrainingLogDocument[]>([]);
10+
11+
useEffect(() => {
12+
const fetchTrainingLogs = async () => {
13+
try {
14+
const response = await fetch('/api/training-log?all=true', {
15+
method: 'GET',
16+
credentials: 'include',
17+
});
18+
const respBody = await response.json();
19+
const logs: TrainingLogDocument[] = respBody?.data;
20+
21+
if (logs != undefined) {
22+
setLogs(logs);
23+
}
24+
setIsLoading(false);
25+
} catch (error) {
26+
setIsLoading(false);
27+
}
28+
};
29+
30+
fetchTrainingLogs();
31+
}, [isLoading]);
32+
733
return (
8-
<main className="min-h-full w-full bg-white dark:bg-black flex flex-col">
9-
<div className='p-8 flex flex-row justify-between items-center'>
34+
<main className="min-h-full w-full bg-white dark:bg-black flex flex-col">
35+
<div className="p-8 flex flex-row justify-between items-center">
1036
<h1 className="text-left text-2xl font-bold text-neutral-600">
11-
All training logs
37+
All training Logs
1238
</h1>
13-
</div>
14-
15-
<hr />
16-
<div className="mt-8 mx-4 overflow-y-scroll flex-1 flex flex-col gap-4 justify-start items-center">
17-
<TrainingLogCard data={mockData} />
18-
<TrainingLogCard data={mockData} />
19-
<TrainingLogCard data={mockData} />
20-
</div>
21-
</main>
39+
</div>
40+
41+
<hr />
42+
<div className="mt-8 mx-4 overflow-y-scroll flex-1 flex flex-col gap-4 justify-start items-center">
43+
{isLoading ? (
44+
<Skeleton count={10} />
45+
) : (
46+
logs.map((l, i) => (
47+
<TrainingLogCard
48+
key={i}
49+
data={{
50+
...l,
51+
_id: l._id.toString(),
52+
user: l.user as any,
53+
animal: l.animal as any,
54+
date: l.date as any,
55+
}}
56+
/>
57+
))
58+
)}
59+
{!isLoading && logs.length == 0 && <p>No training logs yet!</p>}
60+
</div>
61+
</main>
2262
);
2363
}

0 commit comments

Comments
 (0)