Skip to content
This repository was archived by the owner on Jun 12, 2025. It is now read-only.

Commit 986f4c9

Browse files
authored
Merge pull request #12 from EVOGD-Project/dev
Dev
2 parents d9c0052 + 4abbfd1 commit 986f4c9

22 files changed

Lines changed: 1146 additions & 331 deletions

src/api/api.ts

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
import { API_URL } from '@/constants/constants';
2+
import type { IActivity } from '@/types/IActivity';
3+
import type { IClassroom } from '@/types/IClassroomCard';
4+
5+
const getAuthHeaders = (): Record<string, string> => {
6+
const token = typeof window !== 'undefined' ? localStorage.getItem('token') : null;
7+
return token ? { Authorization: token } : {};
8+
};
9+
10+
export const api = {
11+
classroom: {
12+
getAll: async (): Promise<IClassroom[]> => {
13+
const res = await fetch(`${API_URL}/classrooms`, {
14+
headers: {
15+
...getAuthHeaders()
16+
}
17+
});
18+
19+
if (!res.ok) throw new Error('Failed to fetch classrooms');
20+
return res.json();
21+
},
22+
23+
getById: async (id: string): Promise<IClassroom> => {
24+
const res = await fetch(`${API_URL}/classrooms/${encodeURIComponent(id)}`, {
25+
headers: {
26+
...getAuthHeaders()
27+
}
28+
});
29+
30+
if (!res.ok) throw new Error('Failed to fetch classroom');
31+
return res.json();
32+
},
33+
34+
create: async (data: {
35+
name: string;
36+
description: string;
37+
thumbnailId: number;
38+
}): Promise<{ id: string }> => {
39+
const res = await fetch(`${API_URL}/classrooms`, {
40+
method: 'POST',
41+
headers: {
42+
'Content-Type': 'application/json',
43+
...getAuthHeaders()
44+
},
45+
body: JSON.stringify(data)
46+
});
47+
48+
if (!res.ok) throw new Error('Failed to create classroom');
49+
return res.json();
50+
},
51+
52+
update: async (
53+
id: string,
54+
data: {
55+
name: string;
56+
description: string;
57+
thumbnailId: number;
58+
}
59+
): Promise<{ id: string }> => {
60+
const res = await fetch(`${API_URL}/classrooms/${encodeURIComponent(id)}`, {
61+
method: 'PUT',
62+
headers: {
63+
'Content-Type': 'application/json',
64+
...getAuthHeaders()
65+
},
66+
body: JSON.stringify(data)
67+
});
68+
69+
if (!res.ok) throw new Error('Failed to update classroom');
70+
return res.json();
71+
},
72+
73+
join: async (code: string): Promise<IClassroom> => {
74+
const res = await fetch(`${API_URL}/classrooms/join/${encodeURIComponent(code)}`, {
75+
method: 'POST',
76+
headers: {
77+
...getAuthHeaders()
78+
}
79+
});
80+
81+
if (!res.ok) throw new Error('Failed to join classroom');
82+
return res.json();
83+
}
84+
},
85+
86+
activities: {
87+
getByClassroom: async (classroomId: string): Promise<IActivity[]> => {
88+
const res = await fetch(`${API_URL}/classrooms/${encodeURIComponent(classroomId)}/activities`, {
89+
headers: {
90+
...getAuthHeaders()
91+
}
92+
});
93+
94+
if (!res.ok) throw new Error('Failed to fetch activities');
95+
return res.json();
96+
},
97+
98+
getById: async (classroomId: string, activityId: string): Promise<IActivity> => {
99+
const res = await fetch(
100+
`${API_URL}/classrooms/${encodeURIComponent(classroomId)}/activities/${encodeURIComponent(activityId)}`,
101+
{
102+
headers: {
103+
...getAuthHeaders()
104+
}
105+
}
106+
);
107+
108+
if (!res.ok) throw new Error('Failed to fetch activity');
109+
return res.json();
110+
},
111+
112+
create: async (
113+
classroomId: string,
114+
data: {
115+
title: string;
116+
description: string;
117+
type: 'assignment' | 'material';
118+
dueDate?: string;
119+
content: {
120+
instructions?: string;
121+
resources?: Array<{
122+
type: 'link' | 'file';
123+
name: string;
124+
url: string;
125+
}>;
126+
};
127+
}
128+
): Promise<{ id: string }> => {
129+
const res = await fetch(`${API_URL}/classrooms/${encodeURIComponent(classroomId)}/activities`, {
130+
method: 'POST',
131+
headers: {
132+
'Content-Type': 'application/json',
133+
...getAuthHeaders()
134+
},
135+
body: JSON.stringify(data)
136+
});
137+
138+
if (!res.ok) throw new Error('Failed to create activity');
139+
return res.json();
140+
},
141+
142+
update: async (
143+
classroomId: string,
144+
activityId: string,
145+
data: {
146+
title: string;
147+
description: string;
148+
type: 'assignment' | 'material';
149+
dueDate?: string;
150+
content: {
151+
instructions?: string;
152+
resources?: Array<{
153+
type: 'link' | 'file';
154+
name: string;
155+
url: string;
156+
}>;
157+
};
158+
}
159+
): Promise<IActivity> => {
160+
const res = await fetch(
161+
`${API_URL}/classrooms/${encodeURIComponent(classroomId)}/activities/${encodeURIComponent(activityId)}`,
162+
{
163+
method: 'PUT',
164+
headers: {
165+
'Content-Type': 'application/json',
166+
...getAuthHeaders()
167+
},
168+
body: JSON.stringify(data)
169+
}
170+
);
171+
172+
if (!res.ok) throw new Error('Failed to update activity');
173+
return res.json();
174+
}
175+
}
176+
};

src/app/layout.tsx

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use client';
22

33
import Navbar from '@/components/navigation/Navbar';
4+
import AuthProvider from '@/components/providers/AuthProvider';
45
import { theme } from '@/theme/theme';
56
import { ChakraProvider, ColorModeScript, Flex } from '@chakra-ui/react';
67
import { Provider as JotaiProvider } from 'jotai';
@@ -40,10 +41,10 @@ export default function RootLayout({
4041
to { transform: translateX(0); opacity: 1; }
4142
}
4243
.animate-fade-in {
43-
animation: fadeIn 0.5s ease-out forwards;
44+
animation: fadeIn 0.3s ease-out forwards;
4445
}
4546
.animate-slide-in {
46-
animation: slideIn 0.5s ease-out forwards;
47+
animation: slideIn 0.3s ease-out forwards;
4748
}
4849
::-webkit-scrollbar {
4950
width: 8px;
@@ -73,12 +74,14 @@ export default function RootLayout({
7374
<ColorModeScript initialColorMode={theme['config'].initialColorMode} />
7475
<ChakraProvider theme={theme}>
7576
<JotaiProvider>
76-
<Flex w='100%' h='100%' direction='column'>
77-
<Navbar />
78-
<Flex flex='1' overflowY='auto' direction='column' align='stretch'>
79-
{children}
77+
<AuthProvider>
78+
<Flex w='100%' h='100%' direction='column'>
79+
<Navbar />
80+
<Flex flex='1' overflowY='auto' direction='column' align='stretch'>
81+
{children}
82+
</Flex>
8083
</Flex>
81-
</Flex>
84+
</AuthProvider>
8285
</JotaiProvider>
8386
</ChakraProvider>
8487
</body>

src/components/general/ClassroomCard.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
11
'use client';
22

3-
import type { IClassroomCard } from '@/types/IClassroomCard';
3+
import { CDN_URL } from '@/constants/constants';
4+
import type { IClassroom } from '@/types/IClassroomCard';
45
import { Box, Button, Card, CardBody, Flex, Heading, Image, Stack, Text } from '@chakra-ui/react';
56
import { useRouter } from 'next/navigation';
67
import { FiUsers } from 'react-icons/fi';
78

8-
export default function ClassroomCard({ item }: Readonly<{ item: IClassroomCard }>) {
9+
export default function ClassroomCard({ item }: Readonly<{ item: IClassroom }>) {
910
const router = useRouter();
1011

1112
return (
1213
<Card maxW='100%' onClick={() => router.push(`/classes/${item.id}`)} className='animate-fade-in'>
1314
<Box position='relative' overflow='hidden'>
1415
<Image
15-
src={`https://evogd-cdn.tnfangel.com/thumbnails/thumbnail-${item.thumbnailId}.jpg`}
16+
src={`${CDN_URL}/thumbnails/thumbnail-${item.thumbnailId}.jpg`}
1617
alt={'Clase ' + item.name}
1718
width='100%'
1819
height='100px'

src/components/modals/CreateActivityModal.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
'use client';
22

3+
import { api } from '@/api/api';
34
import type { IActivity } from '@/types/IActivity';
45
import {
56
Button,
@@ -64,12 +65,13 @@ export default function CreateActivityModal({
6465

6566
setIsLoading(true);
6667
try {
67-
await new Promise((resolve) => setTimeout(resolve, 1000));
68+
const { id } = await api.activities.create(classroomId, formData);
6869

6970
const newActivity: IActivity = {
70-
id: Math.random().toString(36).substring(7),
71+
id,
7172
...formData,
7273
classroomId,
74+
owner: 'Ángel',
7375
createdAt: new Date().toISOString()
7476
};
7577

src/components/modals/CreateClassModal.tsx

Lines changed: 54 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
'use client';
22

3+
import { api } from '@/api/api';
4+
import type { IClassroom } from '@/types/IClassroomCard';
35
import {
46
Button,
57
FormControl,
@@ -12,26 +14,38 @@ import {
1214
ModalFooter,
1315
ModalHeader,
1416
ModalOverlay,
17+
Select,
1518
Textarea,
1619
VStack,
1720
useToast
1821
} from '@chakra-ui/react';
19-
import { useState } from 'react';
22+
import { useEffect, useState } from 'react';
2023

2124
interface CreateClassModalProps {
2225
isOpen: boolean;
2326
onClose: () => void;
27+
onClassroomCreated?: (classrooms: IClassroom[]) => void;
2428
}
2529

26-
export default function CreateClassModal({ isOpen, onClose }: Readonly<CreateClassModalProps>) {
30+
export default function CreateClassModal({ isOpen, onClose, onClassroomCreated }: Readonly<CreateClassModalProps>) {
2731
const [isLoading, setIsLoading] = useState(false);
2832
const [formData, setFormData] = useState({
2933
name: '',
3034
description: '',
31-
image: ''
35+
thumbnailId: 1
3236
});
3337
const toast = useToast();
3438

39+
useEffect(() => {
40+
if (isOpen) {
41+
setFormData({
42+
name: '',
43+
description: '',
44+
thumbnailId: 1
45+
});
46+
}
47+
}, [isOpen]);
48+
3549
const handleSubmit = async () => {
3650
if (!formData.name.trim() || !formData.description.trim()) {
3751
toast({
@@ -47,7 +61,17 @@ export default function CreateClassModal({ isOpen, onClose }: Readonly<CreateCla
4761

4862
setIsLoading(true);
4963
try {
50-
await new Promise((resolve) => setTimeout(resolve, 1000));
64+
const { id } = await api.classroom.create(formData);
65+
66+
const newClassroom: IClassroom = {
67+
id,
68+
...formData,
69+
code: id.slice(0, 6).toUpperCase(),
70+
owner: 'Ángel'
71+
};
72+
73+
onClassroomCreated?.([newClassroom]);
74+
5175
toast({
5276
title: 'Clase creada',
5377
description: 'La clase se ha creado exitosamente',
@@ -61,8 +85,8 @@ export default function CreateClassModal({ isOpen, onClose }: Readonly<CreateCla
6185
toast({
6286
title: 'Error',
6387
description: 'Ocurrió un error al crear la clase',
64-
position: 'top-right',
6588
status: 'error',
89+
position: 'top-right',
6690
duration: 3000,
6791
isClosable: true
6892
});
@@ -72,17 +96,17 @@ export default function CreateClassModal({ isOpen, onClose }: Readonly<CreateCla
7296
};
7397

7498
return (
75-
<Modal isOpen={isOpen} onClose={onClose} size='xl'>
99+
<Modal isOpen={isOpen} onClose={onClose}>
76100
<ModalOverlay backdropFilter='blur(4px)' />
77101
<ModalContent bg='brand.dark.900' border='1px solid' borderColor='brand.dark.800'>
78102
<ModalHeader>Crear Nueva Clase</ModalHeader>
79103
<ModalCloseButton />
80104
<ModalBody>
81105
<VStack spacing={4}>
82106
<FormControl isRequired>
83-
<FormLabel>Nombre de la Clase</FormLabel>
107+
<FormLabel>Nombre</FormLabel>
84108
<Input
85-
placeholder='Ej: Matemáticas Avanzadas'
109+
placeholder='Ej: Programación Web'
86110
value={formData.name}
87111
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
88112
bg='brand.dark.800'
@@ -113,6 +137,28 @@ export default function CreateClassModal({ isOpen, onClose }: Readonly<CreateCla
113137
rows={4}
114138
/>
115139
</FormControl>
140+
141+
<FormControl>
142+
<FormLabel>Imagen de portada</FormLabel>
143+
<Select
144+
value={formData.thumbnailId}
145+
onChange={(e) => setFormData({ ...formData, thumbnailId: Number(e.target.value) })}
146+
bg='brand.dark.800'
147+
border='1px solid'
148+
borderColor='brand.dark.700'
149+
_hover={{ borderColor: 'brand.primary.500' }}
150+
_focus={{
151+
borderColor: 'brand.primary.500',
152+
boxShadow: '0 0 0 1px var(--chakra-colors-brand-primary-500)'
153+
}}
154+
>
155+
{Array.from({ length: 9 }, (_, i) => (
156+
<option key={i + 1} value={i + 1}>
157+
Imagen {i + 1}
158+
</option>
159+
))}
160+
</Select>
161+
</FormControl>
116162
</VStack>
117163
</ModalBody>
118164

0 commit comments

Comments
 (0)