Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 7 additions & 10 deletions deno.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 5 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-router": "^7.6.0",
"tailwindcss": "^4.1.6",
"zod": "^3.25.48"
"tailwindcss": "^4.1.6"
},
"devDependencies": {
"@eslint/js": "^9.22.0",
Expand Down
2 changes: 1 addition & 1 deletion shared
51 changes: 51 additions & 0 deletions src/api/services/events.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
import axios from 'axios';
import api from '../api';

import { Event } from 'models/event.model';

export const getEventByMode = async (modes: string[]) => {
const query = new URLSearchParams({ mode: modes.join(',') }).toString();

Expand All @@ -8,3 +13,49 @@ export const getEventByMode = async (modes: string[]) => {
}
return response.json();
};

export const getEventById = async (id: string) => {
const response = await fetch(`http://localhost:3000/events/${id}`);

if (!response.ok) {
throw new Error('Failed to fetch events');
}
return response.json();
};

export interface UpdateEventResponse {
message: string;
}

export const updateEventById = async (
id: string,
event: Event,
): Promise<UpdateEventResponse | null> => {
try {
const { data } = await api.put<UpdateEventResponse>('/events/' + id, event);
return data;
} catch (error) {
if (axios.isAxiosError(error) && error.response?.status === 401) {
return null;
}
throw error;
}
};

export interface DeleteEventResponse {
message: string;
}

export const deleteEventById = async (
id: string,
): Promise<DeleteEventResponse | null> => {
try {
const { data } = await api.delete<DeleteEventResponse>('/events/' + id);
return data;
} catch (error) {
if (axios.isAxiosError(error) && error.response?.status === 401) {
return null;
}
throw error;
}
};
77 changes: 71 additions & 6 deletions src/components/major/events.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,81 @@
//component for events data
import { Event } from 'models/event.model.ts';
import { useState } from 'react';
import { Link, useNavigate } from 'react-router';
import { Wrench, Trash2 } from 'lucide-react';

import { useAuth } from '../../auth/useAuth';
import { deleteEventById } from '../../api/services/events.ts';
import { FullEvent } from 'models/event.model.ts';

import DirectButton from '../minor/DirectButton';

export default function Events(info: FullEvent) {
const { user } = useAuth();
const admin = user && user.role === 'admin';

const [open, setOpen] = useState(false);
const navigate = useNavigate();

const handleDelete = async () => {
// TODO handle response from API for fails
if (info._id !== undefined) {
await deleteEventById(info._id.toString());

navigate(0); // refreshes the current page were in, as we dont want to show deleted event
}

setOpen(false);
};

export default function Events(info: Event) {
return (
<div className="p-4 m-4 border solid white rounded w-[50%]">
<h1 className="text-xl font-bold">{info.name}</h1>
<p>{info.location}</p>
<p>{info.distance}km away</p>
<p>{new Date(info.date).toLocaleDateString()}</p>
<div className="flex">
<div className="w-full">
<h1 className="text-xl font-bold">{info.name}</h1>
<p>{info.location}</p>
<p>{info.distance}km away</p>
<p>{new Date(info.date).toLocaleDateString()}</p>
</div>
{admin && (
<div className="flex flex-col gap-2">
<Link
to={'/events/edit/' + info._id}
className={`p-2 rounded-md outline-2 outline-primary hover:bg-input-bg`}
>
<Wrench />
</Link>

<button
onClick={() => setOpen(true)}
className="p-2 rounded-md outline-2 outline-primary hover:bg-input-bg"
>
<Trash2 />
</button>
</div>
)}
</div>
<p>{info.description}</p>
<p>Price: {info.price}</p>
<p>More info can be found here - {info.url}</p>

{open && (
<div
onClick={() => setOpen(false)}
className="fixed inset-0 bg-black/50 flex justify-center items-center"
>
<div className="popup p-4">
<h4 className="mb-5">
Are you sure you want to delete <b>{info.name}</b>?
</h4>

<div className="flex justify-center items-center gap-5">
<DirectButton onClick={handleDelete} text="Yes" />

<DirectButton onClick={() => setOpen(false)} text="No" />
</div>
</div>
</div>
)}
</div>
);
}
17 changes: 4 additions & 13 deletions src/components/major/eventsContainer.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,15 @@
import Events from './events';
import { Event } from 'models/event.model';
import { FullEvent } from 'models/event.model';

type Props = {
events: Event[];
events: FullEvent[];
};

const EventsContainer = ({ events }: Props) => {
return (
<div className="flex flex-col grow items-center mt-12">
{events.map((event) => (
<Events
mode={event.mode}
name={event.name}
date={event.date}
price={event.price}
description={event.description}
distance={event.distance}
location={event.location}
url={event.url}
/>
{events.map((event: FullEvent) => (
<Events {...event} />
))}
</div>
);
Expand Down
7 changes: 5 additions & 2 deletions src/components/minor/FormInput.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
type InputType = 'text' | 'email' | 'password';
type InputType = 'text' | 'email' | 'number' | 'password';

interface FormInputProps {
name: string;
label: string;
def?: string | number;
type?: InputType;
error: boolean;
error?: boolean;
}

export default function FormInput({
name,
label,
def = '',
type = 'text',
error = false,
}: FormInputProps) {
Expand All @@ -20,6 +22,7 @@ export default function FormInput({
type={type}
id={name}
name={name}
defaultValue={def}
required
className={`bg-[var(--color-input-bg)] text-[var(--color-text)] outline-none pl-1 border ${
error
Expand Down
5 changes: 5 additions & 0 deletions src/config/router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import UserHome from '../pages/UserHome';
import UserProfile from '../pages/UserProfile';
import Error from '../pages/Error';
import SavedEvents from '../pages/SavedEvents';
import EditEvent from '../pages/EditEvent';

const router = createBrowserRouter([
{
Expand All @@ -33,6 +34,10 @@ const router = createBrowserRouter([
path: '/savedevents/:mode?',
element: <SavedEvents />,
},
{
path: '/events/edit/:id',
element: <EditEvent />,
},
{
path: '/*',
element: <Error />,
Expand Down
21 changes: 21 additions & 0 deletions src/hooks/useEventById.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { useState, useEffect } from 'react';
import { getEventById } from '../api/services/events';
import { Event } from 'models/event.model';

export const useEventById = (id: string) => {
const [event, setEvent] = useState<Event>();

useEffect(() => {
const getEvent = async () => {
try {
const data = await getEventById(id);
setEvent(data);
} catch (error) {
new Error('Error setting event by id: ' + error);
}
};
getEvent();
}, [id]);

return { event, setEvent };
};
6 changes: 3 additions & 3 deletions src/hooks/useEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import { useState, useEffect } from 'react';
import { FiltersState } from '../components/major/side-bar/types';
import { getEventByMode } from '../api/services/events';
import { filterEvents } from '../utils/filterEvents';
import { Event } from 'models/event.model';
import { FullEvent } from 'models/event.model';

export const useEvents = (filters: FiltersState) => {
const [rawEvents, setRawEvents] = useState<Event[]>([]);
const [filteredEvents, setFilteredEvents] = useState<Event[]>([]);
const [rawEvents, setRawEvents] = useState<FullEvent[]>([]);
const [filteredEvents, setFilteredEvents] = useState<FullEvent[]>([]);

useEffect(() => {
const getEvents = async () => {
Expand Down
Loading