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
2,086 changes: 770 additions & 1,316 deletions package-lock.json

Large diffs are not rendered by default.

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

const api = axios.create({
baseURL: 'http://localhost:3000',
baseURL: 'https://the-locals.deno.dev/',
});

api.interceptors.request.use(
Expand Down
26 changes: 17 additions & 9 deletions src/api/services/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,31 @@ import api from '../api';
import { Event } from 'models/event.model';

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

const response = await fetch(`http://localhost:3000/events?${query}`);

if (!response.ok) {
throw new Error('Failed to fetch events');
console.log('getting events by mode...');
try {
const response = await api.get('/events', {
params: { mode: modes.join(',') },
});
console.log('=== RESPONSE DATA FROM BACKEND ===');
console.log(response.data);
return response.data;
} catch (error) {
throw new Error('Failed to fetch events: ' + error);
}
return response.json();
};

export const getEventById = async (id: string) => {
const response = await fetch(`http://localhost:3000/events/${id}`);
const response = await fetch(`https://the-locals.deno.dev/events/${id}`);
const events = await response.json();

if (!response.ok) {
throw new Error('Failed to fetch events');
}
return response.json();
if (response) {
console.log('=== RETURNED FROM GET EVENTS BY MODE ===');
console.log(events);
}
return events;
};

export interface UpdateEventResponse {
Expand Down
28 changes: 27 additions & 1 deletion src/api/services/users.ts
Original file line number Diff line number Diff line change
@@ -1 +1,27 @@
//users
import api from '../api';

type UserEvent = {
eventId: string;
active: boolean;
};

export const saveUserEvents = async (userEvent: UserEvent) => {
try {
await api.post('users/saveUserEvents', userEvent);
console.log('Saved sucessfully');
return {};
} catch (error) {
console.error('Error saveing event: ' + error);
}
};

export const getUserEventByMode = async (modes: string[]) => {
try {
const response = await api.get('users/userEvents', {
params: { mode: modes.join(',') },
});
return response.data;
} catch (error) {
throw new Error('Failed to fetch events: ' + error);
}
};
2 changes: 1 addition & 1 deletion src/api/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useContext, useEffect, useState } from 'react';
import { TokenContext } from '../config/TokenContext';

function GetFullURL(url: string) {
return 'http://localhost:3000/' + url;
return 'https://the-locals.deno.dev/' + url;
}

export function GetRouterAPI(url: string) {
Expand Down
65 changes: 62 additions & 3 deletions src/components/layouts/EventPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,67 @@ import NavBar from '../major/nav-bar';
import SideBar from '../major/side-bar';
import EventsContainer from '../major/eventsContainer.tsx';
import { useEventFilters } from '../../hooks/useEventFilters.ts';
import { useEvents } from '../../hooks/useEvents.ts';
import { saveUserEvents } from '../../api/services/users.ts';
import { useAuth } from '../../auth/useAuth.tsx';
import { useLocation, useNavigate } from 'react-router';
import { useEffect, useState } from 'react';
import { getEventByMode } from '../../api/services/events.ts';
import { filterEvents } from '../../utils/filterEvents.ts';
import { FullEvent } from 'models/event.model.ts';

// Create hook folder and extract the filter and event filter into it. along with usetheme.
export default function EventLayout() {
const { filters, updateFilters } = useEventFilters();
const { filteredEvents } = useEvents(filters);
const [allEvents, setAllEvents] = useState<FullEvent[]>([]);
const [savedEventIds, setSavedEventIds] = useState<string[]>([]);
const user = useAuth();
const navigate = useNavigate();
const location = useLocation();

useEffect(() => {
const fetchEvents = async () => {
try {
const { events, savedEventIds } = await getEventByMode(
filters.selectedModes,
);
const result = await getEventByMode(filters.selectedModes);
console.log('=== FULL RESULT FROM getEventByMode ===');
console.log(result);
console.log('=== EVENTS PROPERTY ===');
console.log(result?.events);
console.log('=== SAVED EVENT IDS ===');
console.log(result?.savedEventIds);

setAllEvents(events);
setSavedEventIds(savedEventIds);
} catch (error) {
console.error('Error fetching events: ' + error);
}
};
fetchEvents();
}, [filters.selectedModes, user.user]);

const filteredEvents = allEvents ? filterEvents(allEvents, filters) : [];

const displayedEvents =
location.pathname === '/savedevents'
? filteredEvents.filter((x) => savedEventIds.includes(String(x._id)))
: filteredEvents;

const handleSaveToggle = (eventId: string) => {
if (!user.user) {
navigate('/login');
return;
}
setSavedEventIds((prev) => {
const isSaved = prev.includes(eventId);
const updated = isSaved
? prev.filter((id) => id !== eventId)
: [...prev, eventId];
saveUserEvents({ eventId, active: !isSaved });
return updated;
});
};

return (
<div className="flex flex-col min-h-screen">
Expand All @@ -20,7 +75,11 @@ export default function EventLayout() {
<SideBar filters={filters} updateFilters={updateFilters} />
</div>
<div className="flex flex-1">
<EventsContainer events={filteredEvents} />
<EventsContainer
events={displayedEvents}
savedEventIds={savedEventIds}
handleSaveToggle={handleSaveToggle}
/>
</div>
</div>
</div>
Expand Down
52 changes: 37 additions & 15 deletions src/components/major/events.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,14 @@ import { FullEvent } from 'models/event.model.ts';

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

export default function Events(info: FullEvent) {
import SaveEventButton from '../minor/saveEventButton';

type Props = FullEvent & {
isSaved: boolean;
handleSaveToggle: (id: string) => void;
};

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

Expand All @@ -18,8 +25,8 @@ export default function Events(info: FullEvent) {

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

navigate(0); // refreshes the current page were in, as we dont want to show deleted event
}
Expand All @@ -28,18 +35,28 @@ export default function Events(info: FullEvent) {
};

return (
<div className="p-4 m-4 border solid white rounded w-[50%]">
<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
className="p-4 m-4 border solid white rounded w-[50%]"
data-id={props._id}
>
<div>
<div className="flex">
<div className="w-full flex row gap-4 items-top">
<h1 className="text-xl font-bold">{props.name}</h1>
<SaveEventButton
eventId={String(props._id)}
isSaved={props.isSaved}
handleSaveToggle={props.handleSaveToggle}
/>
</div>
<p>{props.location}</p>
<p>{props.distance}km away</p>
<p>{new Date(props.date).toLocaleDateString()}</p>
</div>
{admin && (
<div className="flex flex-col gap-2">
<Link
to={'/events/edit/' + info._id}
to={'/events/edit/' + props._id}
className={`p-2 rounded-md outline-2 outline-primary hover:bg-input-bg`}
>
<Wrench />
Expand All @@ -54,9 +71,14 @@ export default function Events(info: FullEvent) {
</div>
)}
</div>
<p>{info.description}</p>
<p>Price: {info.price}</p>
<p>More info can be found here - {info.url}</p>
<p>{props.description}</p>
<p>Price: {props.price}</p>
<p>
More info can be found here -{' '}
<a href={props.url} target="_blank">
{props.name}
</a>
</p>

{open && (
<div
Expand All @@ -65,7 +87,7 @@ export default function Events(info: FullEvent) {
>
<div className="popup p-4">
<h4 className="mb-5">
Are you sure you want to delete <b>{info.name}</b>?
Are you sure you want to delete <b>{props.name}</b>?
</h4>

<div className="flex justify-center items-center gap-5">
Expand Down
17 changes: 14 additions & 3 deletions src/components/major/eventsContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,24 @@ import { FullEvent } from 'models/event.model';

type Props = {
events: FullEvent[];
savedEventIds: string[];
handleSaveToggle: (id: string) => void;
};

const EventsContainer = ({ events }: Props) => {
const EventsContainer = ({
events,
savedEventIds,
handleSaveToggle,
}: Props) => {
return (
<div className="flex flex-col grow items-center mt-12">
{events.map((event: FullEvent) => (
<Events {...event} />
{events.map((event) => (
<Events
key={String(event._id)}
isSaved={savedEventIds.includes(String(event._id))}
handleSaveToggle={handleSaveToggle}
{...event}
/>
))}
</div>
);
Expand Down
2 changes: 1 addition & 1 deletion src/components/major/nav-bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export default function NavBar() {
};

return (
<div className="border-b-3 input">
<div className="border-b-3 input bg-[var(--color-background)]">
{/* Desktop navbar */}
<div className="hidden md:flex justify-between items-center">
<ThemeButton />
Expand Down
2 changes: 1 addition & 1 deletion src/components/major/side-bar/filters/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export const Filters = ({
icon={HandCoins}
label="Max Price (£)"
value={price}
max={50}
max={100}
suffix="£"
onChange={(val) => updateFilters({ price: val })}
/>
Expand Down
23 changes: 23 additions & 0 deletions src/components/minor/saveEventButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Heart } from 'lucide-react';

type Props = {
eventId: string;
isSaved: boolean;
handleSaveToggle: (id: string) => void;
};

export default function SaveEventButton({
eventId,
isSaved,
handleSaveToggle,
}: Props) {
console.log(isSaved);
return (
<button
onClick={() => handleSaveToggle(eventId)}
className="flex items-top"
>
<Heart color={isSaved ? 'red' : 'grey'} />
</button>
);
}
2 changes: 1 addition & 1 deletion src/hooks/useEventFilters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { FiltersState } from '../components/major/side-bar/types';

export const useEventFilters = () => {
const [filters, setFilters] = useState<FiltersState>({
selectedModes: ['music'],
selectedModes: [],
search: '',
price: 50,
distance: 20,
Expand Down
Loading