mirror of
https://github.com/jakobkordez/ham-reserve.git
synced 2025-05-31 08:49:06 +00:00
Frontend call changes
This commit is contained in:
parent
a6814b1746
commit
2d97016eae
@ -5,6 +5,7 @@ import { Event } from './interfaces/event.interface';
|
||||
import { CreateEventDto } from './interfaces/create-event-dto.interface';
|
||||
import { Reservation } from './interfaces/reservation.interface';
|
||||
import { CreateReservationDto } from './interfaces/create-reservation-dto';
|
||||
import { useAuthState } from './state/auth-state';
|
||||
|
||||
const baseURL = '/api';
|
||||
|
||||
@ -25,180 +26,225 @@ interface LoginResponse {
|
||||
refreshToken: string;
|
||||
}
|
||||
|
||||
async function getAuthHeader() {
|
||||
const accessToken = await useAuthState.getState().getAccessToken();
|
||||
if (!accessToken) throw new Error('Not authenticated');
|
||||
return { Authorization: `Bearer ${accessToken}` };
|
||||
}
|
||||
|
||||
export const apiFunctions = {
|
||||
// Auth
|
||||
login: async (username: string, password: string) => {
|
||||
return await api.post<LoginResponse>('/auth/login', { username, password });
|
||||
return (
|
||||
await api.post<LoginResponse>('/auth/login', { username, password })
|
||||
).data;
|
||||
},
|
||||
refresh: async (refreshToken: string) => {
|
||||
return await api.get<LoginResponse>('/auth/refresh', {
|
||||
headers: { Authorization: `Bearer ${refreshToken}` },
|
||||
});
|
||||
return (
|
||||
await api.get<LoginResponse>('/auth/refresh', {
|
||||
headers: { Authorization: `Bearer ${refreshToken}` },
|
||||
})
|
||||
).data;
|
||||
},
|
||||
logout: async (accessToken: string) => {
|
||||
return await api.get('/auth/logout', {
|
||||
headers: { Authorization: `Bearer ${accessToken}` },
|
||||
});
|
||||
logout: async () => {
|
||||
return (
|
||||
await api.get('/auth/logout', {
|
||||
headers: await getAuthHeader(),
|
||||
})
|
||||
).data;
|
||||
},
|
||||
|
||||
// Users
|
||||
createUser: async (accessToken: string, user: CreateUserDto) => {
|
||||
return await api.post<User>('/users', user, {
|
||||
headers: { Authorization: `Bearer ${accessToken}` },
|
||||
});
|
||||
createUser: async (user: CreateUserDto) => {
|
||||
return (
|
||||
await api.post<User>('/users', user, {
|
||||
headers: await getAuthHeader(),
|
||||
})
|
||||
).data;
|
||||
},
|
||||
getAllUsers: async (accessToken: string) => {
|
||||
return await api.get<User[]>('/users', {
|
||||
headers: { Authorization: `Bearer ${accessToken}` },
|
||||
});
|
||||
getAllUsers: async () => {
|
||||
return (
|
||||
await api.get<User[]>('/users', {
|
||||
headers: await getAuthHeader(),
|
||||
})
|
||||
).data;
|
||||
},
|
||||
getMe: async (accessToken: string) => {
|
||||
return await api.get<User>('/users/me', {
|
||||
headers: { Authorization: `Bearer ${accessToken}` },
|
||||
});
|
||||
getMe: async () => {
|
||||
return (
|
||||
await api.get<User>('/users/me', {
|
||||
headers: await getAuthHeader(),
|
||||
})
|
||||
).data;
|
||||
},
|
||||
findByUsername: async (accessToken: string, username: string) => {
|
||||
return await api.get<User>(`/users/search/${username}`, {
|
||||
headers: { Authorization: `Bearer ${accessToken}` },
|
||||
});
|
||||
findByUsername: async (username: string) => {
|
||||
return (
|
||||
await api.get<User>(`/users/search/${username}`, {
|
||||
headers: await getAuthHeader(),
|
||||
})
|
||||
).data;
|
||||
},
|
||||
getUser: async (accessToken: string, id: string) => {
|
||||
return await api.get<User>(`/users/${id}`, {
|
||||
headers: { Authorization: `Bearer ${accessToken}` },
|
||||
});
|
||||
getUser: async (id: string) => {
|
||||
return (
|
||||
await api.get<User>(`/users/${id}`, {
|
||||
headers: await getAuthHeader(),
|
||||
})
|
||||
).data;
|
||||
},
|
||||
getManyUsers: async (accessToken: string, ids: string[]) => {
|
||||
return await api.post<User[]>(`/users/many`, ids, {
|
||||
headers: { Authorization: `Bearer ${accessToken}` },
|
||||
});
|
||||
getManyUsers: async (ids: string[]) => {
|
||||
return (
|
||||
await api.post<User[]>(`/users/many`, ids, {
|
||||
headers: await getAuthHeader(),
|
||||
})
|
||||
).data;
|
||||
},
|
||||
updateUser: async (accessToken: string, id: string, user: User) => {
|
||||
return await api.patch<User>(`/users/${id}`, user, {
|
||||
headers: { Authorization: `Bearer ${accessToken}` },
|
||||
});
|
||||
updateUser: async (id: string, user: User) => {
|
||||
return (
|
||||
await api.patch<User>(`/users/${id}`, user, {
|
||||
headers: await getAuthHeader(),
|
||||
})
|
||||
).data;
|
||||
},
|
||||
deleteUser: async (accessToken: string, id: string) => {
|
||||
return await api.delete<User>(`/users/${id}`, {
|
||||
headers: { Authorization: `Bearer ${accessToken}` },
|
||||
});
|
||||
deleteUser: async (id: string) => {
|
||||
return (
|
||||
await api.delete<User>(`/users/${id}`, {
|
||||
headers: await getAuthHeader(),
|
||||
})
|
||||
).data;
|
||||
},
|
||||
|
||||
// Events
|
||||
createEvent: async (accessToken: string, event: CreateEventDto) => {
|
||||
return await api.post<Event>('/events', event, {
|
||||
headers: { Authorization: `Bearer ${accessToken}` },
|
||||
});
|
||||
createEvent: async (event: CreateEventDto) => {
|
||||
return (
|
||||
await api.post<Event>('/events', event, {
|
||||
headers: await getAuthHeader(),
|
||||
})
|
||||
).data;
|
||||
},
|
||||
getAllEvents: async (accessToken: string) => {
|
||||
return await api.get<Event[]>('/events/all', {
|
||||
headers: { Authorization: `Bearer ${accessToken}` },
|
||||
});
|
||||
getAllEvents: async () => {
|
||||
return (
|
||||
await api.get<Event[]>('/events/all', {
|
||||
headers: await getAuthHeader(),
|
||||
})
|
||||
).data;
|
||||
},
|
||||
getPrivateEvents: async (accessToken: string) => {
|
||||
return await api.get<Event[]>('/events/private', {
|
||||
headers: { Authorization: `Bearer ${accessToken}` },
|
||||
});
|
||||
getPrivateEvents: async () => {
|
||||
return (
|
||||
await api.get<Event[]>('/events/private', {
|
||||
headers: await getAuthHeader(),
|
||||
})
|
||||
).data;
|
||||
},
|
||||
getCurrentEvents: async () => {
|
||||
return await api.get<Event[]>('/events');
|
||||
return (await api.get<Event[]>('/events')).data;
|
||||
},
|
||||
getEvent: async (id: string) => {
|
||||
return await api.get<Event>(`/events/${id}`);
|
||||
return (await api.get<Event>(`/events/${id}`)).data;
|
||||
},
|
||||
updateEvent: async (accessToken: string, id: string, event: Event) => {
|
||||
return await api.patch<Event>(`/events/${id}`, event, {
|
||||
headers: { Authorization: `Bearer ${accessToken}` },
|
||||
});
|
||||
updateEvent: async (id: string, event: Event) => {
|
||||
return (
|
||||
await api.patch<Event>(`/events/${id}`, event, {
|
||||
headers: await getAuthHeader(),
|
||||
})
|
||||
).data;
|
||||
},
|
||||
grantEventAccess: async (accessToken: string, id: string, userId: string) => {
|
||||
return await api.get<Event>(`/events/${id}/grant/${userId}`, {
|
||||
headers: { Authorization: `Bearer ${accessToken}` },
|
||||
});
|
||||
grantEventAccess: async (id: string, userId: string) => {
|
||||
return (
|
||||
await api.get<Event>(`/events/${id}/grant/${userId}`, {
|
||||
headers: await getAuthHeader(),
|
||||
})
|
||||
).data;
|
||||
},
|
||||
revokeEventAccess: async (
|
||||
accessToken: string,
|
||||
id: string,
|
||||
userId: string,
|
||||
) => {
|
||||
return await api.get<Event>(`/events/${id}/revoke/${userId}`, {
|
||||
headers: { Authorization: `Bearer ${accessToken}` },
|
||||
});
|
||||
revokeEventAccess: async (id: string, userId: string) => {
|
||||
return (
|
||||
await api.get<Event>(`/events/${id}/revoke/${userId}`, {
|
||||
headers: await getAuthHeader(),
|
||||
})
|
||||
).data;
|
||||
},
|
||||
deleteEvent: async (accessToken: string, id: string) => {
|
||||
return await api.delete<Event>(`/events/${id}`, {
|
||||
headers: { Authorization: `Bearer ${accessToken}` },
|
||||
});
|
||||
deleteEvent: async (id: string) => {
|
||||
return (
|
||||
await api.delete<Event>(`/events/${id}`, {
|
||||
headers: await getAuthHeader(),
|
||||
})
|
||||
).data;
|
||||
},
|
||||
|
||||
// Reservations
|
||||
getReservation: async (accessToken: string, id: string) => {
|
||||
return await api.get<Reservation>(`/reservations/${id}`, {
|
||||
headers: { Authorization: `Bearer ${accessToken}` },
|
||||
});
|
||||
getReservation: async (id: string) => {
|
||||
return (
|
||||
await api.get<Reservation>(`/reservations/${id}`, {
|
||||
headers: await getAuthHeader(),
|
||||
})
|
||||
).data;
|
||||
},
|
||||
updateReservation: async (
|
||||
accessToken: string,
|
||||
id: string,
|
||||
reservation: Reservation,
|
||||
) => {
|
||||
return await api.patch<Reservation>(`/reservations/${id}`, reservation, {
|
||||
headers: { Authorization: `Bearer ${accessToken}` },
|
||||
});
|
||||
updateReservation: async (id: string, reservation: Reservation) => {
|
||||
return (
|
||||
await api.patch<Reservation>(`/reservations/${id}`, reservation, {
|
||||
headers: await getAuthHeader(),
|
||||
})
|
||||
).data;
|
||||
},
|
||||
deleteReservation: async (accessToken: string, id: string) => {
|
||||
return await api.delete<Reservation>(`/reservations/${id}`, {
|
||||
headers: { Authorization: `Bearer ${accessToken}` },
|
||||
});
|
||||
deleteReservation: async (id: string) => {
|
||||
return (
|
||||
await api.delete<Reservation>(`/reservations/${id}`, {
|
||||
headers: await getAuthHeader(),
|
||||
})
|
||||
).data;
|
||||
},
|
||||
createReservation: async (
|
||||
accessToken: string,
|
||||
eventId: string,
|
||||
reservation: CreateReservationDto,
|
||||
) => {
|
||||
return await api.post<Reservation>(
|
||||
`/events/${eventId}/reservations`,
|
||||
reservation,
|
||||
{ headers: { Authorization: `Bearer ${accessToken}` } },
|
||||
);
|
||||
return (
|
||||
await api.post<Reservation>(
|
||||
`/events/${eventId}/reservations`,
|
||||
reservation,
|
||||
{ headers: await getAuthHeader() },
|
||||
)
|
||||
).data;
|
||||
},
|
||||
getReservationsForEvent: async (eventId: string) => {
|
||||
return await api.get<Reservation[]>(`/events/${eventId}/reservations`);
|
||||
return (await api.get<Reservation[]>(`/events/${eventId}/reservations`))
|
||||
.data;
|
||||
},
|
||||
getReservationsForSelf: async (
|
||||
accessToken: string,
|
||||
filter?: {
|
||||
event?: string;
|
||||
start?: string;
|
||||
end?: string;
|
||||
},
|
||||
) => {
|
||||
return await api.get<Reservation[]>('/users/me/reservations', {
|
||||
headers: { Authorization: `Bearer ${accessToken}` },
|
||||
params: {
|
||||
event: filter?.event,
|
||||
start: filter?.start,
|
||||
end: filter?.end,
|
||||
},
|
||||
});
|
||||
getReservationsForSelf: async (filter?: {
|
||||
event?: string;
|
||||
start?: string;
|
||||
end?: string;
|
||||
}) => {
|
||||
return (
|
||||
await api.get<Reservation[]>('/users/me/reservations', {
|
||||
headers: await getAuthHeader(),
|
||||
params: {
|
||||
event: filter?.event,
|
||||
start: filter?.start,
|
||||
end: filter?.end,
|
||||
},
|
||||
})
|
||||
).data;
|
||||
},
|
||||
getReservationsForUser: async (accessToken: string, userId: string) => {
|
||||
return await api.get<Reservation[]>(`/users/${userId}/reservations`, {
|
||||
headers: { Authorization: `Bearer ${accessToken}` },
|
||||
});
|
||||
getReservationsForUser: async (userId: string) => {
|
||||
return (
|
||||
await api.get<Reservation[]>(`/users/${userId}/reservations`, {
|
||||
headers: await getAuthHeader(),
|
||||
})
|
||||
).data;
|
||||
},
|
||||
uploadLog: async (accessToken: string, reservationId: string, file: File) => {
|
||||
uploadLog: async (reservationId: string, file: File) => {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
return await api.post<Reservation>(
|
||||
`/reservations/${reservationId}/log`,
|
||||
formData,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
'Content-Type': 'multipart/form-data',
|
||||
return (
|
||||
await api.post<Reservation>(
|
||||
`/reservations/${reservationId}/log`,
|
||||
formData,
|
||||
{
|
||||
headers: {
|
||||
...(await getAuthHeader()),
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
)
|
||||
).data;
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -5,7 +5,6 @@ import { PrivateTag } from '@/components/private-tag';
|
||||
import { ProgressBar } from '@/components/progress-bar';
|
||||
import { Event } from '@/interfaces/event.interface';
|
||||
import { User } from '@/interfaces/user.interface';
|
||||
import { useAuthState } from '@/state/auth-state';
|
||||
import { faMinus, faPlus } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import Link from 'next/link';
|
||||
@ -62,27 +61,17 @@ export function EventComponent({ event }: EventComponentProps) {
|
||||
function AccessComponent({ event }: EventComponentProps) {
|
||||
const [usernameInput, setUsernameInput] = useState('');
|
||||
const [users, setUsers] = useState<User[]>();
|
||||
const getAccessToken = useAuthState((state) => state.getAccessToken);
|
||||
|
||||
useEffect(() => {
|
||||
getAccessToken().then((token) => {
|
||||
if (!token) return;
|
||||
apiFunctions.getManyUsers(token, event.access).then((res) => {
|
||||
setUsers(res.data);
|
||||
});
|
||||
});
|
||||
}, [getAccessToken, event.access]);
|
||||
apiFunctions.getManyUsers(event.access).then(setUsers).catch(console.error);
|
||||
}, [event.access]);
|
||||
|
||||
async function grantAccess() {
|
||||
const token = await getAccessToken();
|
||||
if (!token) return;
|
||||
const user = await apiFunctions.findByUsername(
|
||||
token,
|
||||
usernameInput.toUpperCase(),
|
||||
);
|
||||
if (!user.data) return;
|
||||
try {
|
||||
await apiFunctions.grantEventAccess(token, event._id, user.data._id);
|
||||
const user = await apiFunctions.findByUsername(
|
||||
usernameInput.toUpperCase(),
|
||||
);
|
||||
await apiFunctions.grantEventAccess(event._id, user._id);
|
||||
window.location.reload();
|
||||
setUsernameInput('');
|
||||
} catch (err) {
|
||||
@ -91,10 +80,8 @@ function AccessComponent({ event }: EventComponentProps) {
|
||||
}
|
||||
|
||||
async function revokeAccess(userId: string) {
|
||||
const token = await getAccessToken();
|
||||
if (!token) return;
|
||||
try {
|
||||
await apiFunctions.revokeEventAccess(token, event._id, userId);
|
||||
await apiFunctions.revokeEventAccess(event._id, userId);
|
||||
window.location.reload();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
@ -17,9 +17,7 @@ export default function AdminEventPage({ params: { id } }: EventPageProps) {
|
||||
useEffect(() => {
|
||||
apiFunctions
|
||||
.getEvent(id)
|
||||
.then((res) => {
|
||||
setEvent(res.data);
|
||||
})
|
||||
.then(setEvent)
|
||||
.catch((e) => {
|
||||
console.log(e);
|
||||
setEvent(null);
|
||||
|
@ -1,7 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { apiFunctions } from '@/api';
|
||||
import { useAuthState } from '@/state/auth-state';
|
||||
import { faTriangleExclamation } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { useRouter } from 'next/navigation';
|
||||
@ -15,35 +14,27 @@ export function CreateEventForm() {
|
||||
const [isPrivate, setIsPrivate] = useState(false);
|
||||
const [error, setError] = useState<string>();
|
||||
|
||||
const getAccessToken = useAuthState((s) => s.getAccessToken);
|
||||
const router = useRouter();
|
||||
|
||||
function submit() {
|
||||
setError(undefined);
|
||||
getAccessToken().then((accessToken) => {
|
||||
if (!accessToken) {
|
||||
setError('Session expired');
|
||||
throw new Error('No access token');
|
||||
}
|
||||
apiFunctions
|
||||
.createEvent(accessToken, {
|
||||
callsign: callsign.toUpperCase(),
|
||||
description,
|
||||
fromDateTime: fromDateTime?.toISOString(),
|
||||
toDateTime: toDateTime?.toISOString(),
|
||||
isPrivate,
|
||||
})
|
||||
.then((res) => {
|
||||
console.log(res);
|
||||
router.push('/admin/events');
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
const msg = err.response.data.message;
|
||||
if (msg instanceof Array) setError(msg.join(', '));
|
||||
else setError(msg);
|
||||
});
|
||||
});
|
||||
apiFunctions
|
||||
.createEvent({
|
||||
callsign: callsign.toUpperCase(),
|
||||
description,
|
||||
fromDateTime: fromDateTime?.toISOString(),
|
||||
toDateTime: toDateTime?.toISOString(),
|
||||
isPrivate,
|
||||
})
|
||||
.then(() => {
|
||||
router.push('/admin/events');
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
const msg = err.response.data.message;
|
||||
if (msg instanceof Array) setError(msg.join(', '));
|
||||
else setError(msg);
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -3,20 +3,15 @@
|
||||
import { apiFunctions } from '@/api';
|
||||
import { PrivateTag } from '@/components/private-tag';
|
||||
import { Event } from '@/interfaces/event.interface';
|
||||
import { useAuthState } from '@/state/auth-state';
|
||||
import Link from 'next/link';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
export function EventsList() {
|
||||
const [getAccessToken] = useAuthState((state) => [state.getAccessToken]);
|
||||
const [events, setEvents] = useState<Event[]>();
|
||||
|
||||
useEffect(() => {
|
||||
getAccessToken().then((token) => {
|
||||
if (!token) return;
|
||||
apiFunctions.getAllEvents(token).then((res) => setEvents(res.data));
|
||||
});
|
||||
}, [getAccessToken]);
|
||||
apiFunctions.getAllEvents().then(setEvents).catch(console.error);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
|
@ -1,7 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { Role } from '@/enums/role.enum';
|
||||
import { useAuthState } from '@/state/auth-state';
|
||||
import { useUserState } from '@/state/user-state';
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { useEffect } from 'react';
|
||||
@ -23,7 +23,7 @@ export default function AdminPageLayout({
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const pathname = usePathname();
|
||||
const getUser = useAuthState((s) => s.getUser);
|
||||
const getUser = useUserState((s) => s.getUser);
|
||||
|
||||
useEffect(() => {
|
||||
getUser().then((u) => {
|
||||
|
@ -1,3 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { redirect } from 'next/navigation';
|
||||
|
||||
export default function AdminPage() {
|
||||
return <></>;
|
||||
redirect('/admin/events');
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { apiFunctions } from '@/api';
|
||||
import { useAuthState } from '@/state/auth-state';
|
||||
import { faTriangleExclamation } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { useRouter } from 'next/navigation';
|
||||
@ -17,34 +16,25 @@ export function CreateUserForm() {
|
||||
const [phone, setPhone] = useState('');
|
||||
const [error, setError] = useState<string>();
|
||||
|
||||
const getAccessToken = useAuthState((s) => s.getAccessToken);
|
||||
|
||||
function submit() {
|
||||
setError(undefined);
|
||||
getAccessToken().then((accessToken) => {
|
||||
if (!accessToken) {
|
||||
setError('Session expired');
|
||||
throw new Error('No access token');
|
||||
}
|
||||
apiFunctions
|
||||
.createUser(accessToken, {
|
||||
username: username.toUpperCase(),
|
||||
password,
|
||||
name,
|
||||
email: email || undefined,
|
||||
phone: phone || undefined,
|
||||
})
|
||||
.then((res) => {
|
||||
console.log(res);
|
||||
router.push('/admin/users');
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
const msg = err.response.data.message;
|
||||
if (msg instanceof Array) setError(msg.join(', '));
|
||||
else setError(msg);
|
||||
});
|
||||
});
|
||||
apiFunctions
|
||||
.createUser({
|
||||
username: username.toUpperCase(),
|
||||
password,
|
||||
name,
|
||||
email: email || undefined,
|
||||
phone: phone || undefined,
|
||||
})
|
||||
.then(() => {
|
||||
router.push('/admin/users');
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
const msg = err.response.data.message;
|
||||
if (msg instanceof Array) setError(msg.join(', '));
|
||||
else setError(msg);
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -3,34 +3,33 @@
|
||||
import { apiFunctions } from '@/api';
|
||||
import { Role } from '@/enums/role.enum';
|
||||
import { User } from '@/interfaces/user.interface';
|
||||
import { useAuthState } from '@/state/auth-state';
|
||||
import { faCrown, faTrash } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { DeleteUserDialog } from './delete-user-dialog';
|
||||
|
||||
export function UsersList() {
|
||||
const [getAccessToken, getMe] = useAuthState((s) => [
|
||||
s.getAccessToken,
|
||||
s.getUser,
|
||||
]);
|
||||
|
||||
const [users, setUsers] = useState<User[]>();
|
||||
const [me, setMe] = useState<User>();
|
||||
const [deleteUser, setDeleteUser] = useState<User>();
|
||||
|
||||
useEffect(() => {
|
||||
getAccessToken().then(async (token) => {
|
||||
if (!token) return;
|
||||
const users = await apiFunctions.getAllUsers(token);
|
||||
async function getUsers() {
|
||||
try {
|
||||
const [users, me] = await Promise.all([
|
||||
apiFunctions.getAllUsers(),
|
||||
apiFunctions.getMe(),
|
||||
]);
|
||||
|
||||
const me = await getMe();
|
||||
if (!me) return;
|
||||
|
||||
setUsers(users.data);
|
||||
setUsers(users);
|
||||
setMe(me);
|
||||
});
|
||||
}, [getAccessToken, getMe]);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getUsers();
|
||||
}, []);
|
||||
|
||||
if (!users || !me) return <div>Loading...</div>;
|
||||
|
||||
@ -87,11 +86,13 @@ export function UsersList() {
|
||||
user={deleteUser}
|
||||
onCancel={() => setDeleteUser(undefined)}
|
||||
onConfirm={async () => {
|
||||
const token = await getAccessToken();
|
||||
if (!token) return;
|
||||
apiFunctions.deleteUser(token, deleteUser!._id);
|
||||
setDeleteUser(undefined);
|
||||
window.location.reload();
|
||||
apiFunctions
|
||||
.deleteUser(deleteUser!._id)
|
||||
.then(() => {
|
||||
setDeleteUser(undefined);
|
||||
getUsers();
|
||||
})
|
||||
.catch(console.error);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
|
@ -9,8 +9,8 @@ import { FreeDatesComponent } from './free-dates-component';
|
||||
import { MyReservations } from './my-reservations';
|
||||
import { User } from '@/interfaces/user.interface';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useAuthState } from '@/state/auth-state';
|
||||
import Link from 'next/link';
|
||||
import { useUserState } from '@/state/user-state';
|
||||
|
||||
interface EventComponentProps {
|
||||
event: Event;
|
||||
@ -18,10 +18,10 @@ interface EventComponentProps {
|
||||
|
||||
export function EventComponent({ event }: EventComponentProps) {
|
||||
const [user, setUser] = useState<User | null>();
|
||||
const getUser = useAuthState((state) => state.getUser);
|
||||
const getUser = useUserState((state) => state.getUser);
|
||||
|
||||
useEffect(() => {
|
||||
getUser().then((user) => setUser(user));
|
||||
getUser().then(setUser);
|
||||
}, [getUser]);
|
||||
|
||||
return (
|
||||
|
@ -2,7 +2,7 @@
|
||||
import { apiFunctions } from '@/api';
|
||||
import { Event } from '@/interfaces/event.interface';
|
||||
import { Reservation } from '@/interfaces/reservation.interface';
|
||||
import { dayInMs, dayInWeeks } from '@/util/date.util';
|
||||
import { dayInMs, dayInWeeks, getNextNDays } from '@/util/date.util';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Band } from '@/enums/band.enum';
|
||||
|
||||
@ -12,19 +12,14 @@ export function FreeDatesComponent({ event }: { event: Event }) {
|
||||
useEffect(() => {
|
||||
apiFunctions
|
||||
.getReservationsForEvent(event._id)
|
||||
.then((res) => setReservations(res.data))
|
||||
.then(setReservations)
|
||||
.catch(console.error);
|
||||
}, [event._id]);
|
||||
|
||||
const bands = Object.values(Band).map((val) => val.toString());
|
||||
const dates = new Array(7)
|
||||
.fill(null)
|
||||
.map((_, i) => new Date(Math.floor(Date.now() / dayInMs + i) * dayInMs))
|
||||
.filter(
|
||||
(date) =>
|
||||
(!event.fromDateTime || date >= event.fromDateTime) &&
|
||||
(!event.toDateTime || date <= event.toDateTime),
|
||||
);
|
||||
const dates = getNextNDays(7, event);
|
||||
|
||||
if (!dates.length) return <div>Dogodka je konec</div>;
|
||||
|
||||
const freeTable = bands.map(() => dates.map(() => true));
|
||||
|
||||
|
@ -4,7 +4,6 @@ import { apiFunctions } from '@/api';
|
||||
import { ReservationsTable } from '@/components/reservations-table';
|
||||
import { Event } from '@/interfaces/event.interface';
|
||||
import { Reservation } from '@/interfaces/reservation.interface';
|
||||
import { useAuthState } from '@/state/auth-state';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
interface MyReservationsProps {
|
||||
@ -13,23 +12,19 @@ interface MyReservationsProps {
|
||||
|
||||
export function MyReservations({ event }: MyReservationsProps) {
|
||||
const [reservations, setReservations] = useState<Reservation[]>();
|
||||
const getAccessToken = useAuthState((state) => state.getAccessToken);
|
||||
|
||||
useEffect(() => {
|
||||
getAccessToken().then((token) => {
|
||||
if (!token) return;
|
||||
apiFunctions
|
||||
.getReservationsForSelf(token, { event: event._id })
|
||||
.then((res) => {
|
||||
setReservations(
|
||||
res.data.sort((a, b) => b.forDate.valueOf() - a.forDate.valueOf()),
|
||||
);
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log(e);
|
||||
});
|
||||
});
|
||||
}, [event._id, getAccessToken]);
|
||||
apiFunctions
|
||||
.getReservationsForSelf({ event: event._id })
|
||||
.then((res) => {
|
||||
setReservations(
|
||||
res.sort((a, b) => b.forDate.valueOf() - a.forDate.valueOf()),
|
||||
);
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log(e);
|
||||
});
|
||||
}, [event._id]);
|
||||
|
||||
return <ReservationsTable reservations={reservations || []} />;
|
||||
}
|
||||
|
@ -18,9 +18,7 @@ export default function EventPage({ params: { id } }: EventPageProps) {
|
||||
useEffect(() => {
|
||||
apiFunctions
|
||||
.getEvent(id)
|
||||
.then((res) => {
|
||||
setEvent(res.data);
|
||||
})
|
||||
.then(setEvent)
|
||||
.catch((e) => {
|
||||
console.log(e);
|
||||
setEvent(null);
|
||||
|
@ -5,8 +5,8 @@ import { Band } from '@/enums/band.enum';
|
||||
import { Mode } from '@/enums/mode.enum';
|
||||
import { Event } from '@/interfaces/event.interface';
|
||||
import { User } from '@/interfaces/user.interface';
|
||||
import { useAuthState } from '@/state/auth-state';
|
||||
import { dayInMs } from '@/util/date.util';
|
||||
import { useUserState } from '@/state/user-state';
|
||||
import { getNextNDays } from '@/util/date.util';
|
||||
import { faTriangleExclamation } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
@ -19,10 +19,7 @@ export function ReserveComponent({ event }: ReserveComponentProps) {
|
||||
const dateRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const [user, setUser] = useState<User | null>();
|
||||
const [getUser, getAccessToken] = useAuthState((state) => [
|
||||
state.getUser,
|
||||
state.getAccessToken,
|
||||
]);
|
||||
const getUser = useUserState((state) => state.getUser);
|
||||
|
||||
const [date, setDate] = useState<Date>();
|
||||
const [bands, setBands] = useState<Set<Band>>(new Set());
|
||||
@ -30,7 +27,7 @@ export function ReserveComponent({ event }: ReserveComponentProps) {
|
||||
const [error, setError] = useState<string>();
|
||||
|
||||
useEffect(() => {
|
||||
getUser().then((user) => setUser(user));
|
||||
getUser().then(setUser);
|
||||
}, [getUser]);
|
||||
|
||||
if (!user) return <></>;
|
||||
@ -40,10 +37,8 @@ export function ReserveComponent({ event }: ReserveComponentProps) {
|
||||
|
||||
async function submit() {
|
||||
setError(undefined);
|
||||
const token = await getAccessToken();
|
||||
if (!token) return;
|
||||
apiFunctions
|
||||
.createReservation(token, event._id, {
|
||||
.createReservation(event._id, {
|
||||
forDate: date!.toISOString(),
|
||||
bands: Array.from(bands),
|
||||
modes: Array.from(modes),
|
||||
@ -59,14 +54,7 @@ export function ReserveComponent({ event }: ReserveComponentProps) {
|
||||
});
|
||||
}
|
||||
|
||||
const dates = new Array(7)
|
||||
.fill(null)
|
||||
.map((_, i) => new Date(Math.floor(Date.now() / dayInMs + i) * dayInMs))
|
||||
.filter(
|
||||
(date) =>
|
||||
(!event.fromDateTime || date >= event.fromDateTime) &&
|
||||
(!event.toDateTime || date <= event.toDateTime),
|
||||
);
|
||||
const dates = getNextNDays(7, event);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-6 rounded border border-gray-500 p-6">
|
||||
|
@ -19,7 +19,7 @@ export default function Login() {
|
||||
async function login() {
|
||||
try {
|
||||
const res = await apiFunctions.login(username.toUpperCase(), password);
|
||||
useAuthState.setState(res.data);
|
||||
useAuthState.setState(res);
|
||||
window.location.replace('/');
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
|
@ -4,11 +4,11 @@ import { apiFunctions } from '@/api';
|
||||
import { ReservationsTable } from '@/components/reservations-table';
|
||||
import { Reservation } from '@/interfaces/reservation.interface';
|
||||
import { User } from '@/interfaces/user.interface';
|
||||
import { useAuthState } from '@/state/auth-state';
|
||||
import { useUserState } from '@/state/user-state';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
export function UserComponent() {
|
||||
const getUser = useAuthState((s) => s.getUser);
|
||||
const getUser = useUserState((s) => s.getUser);
|
||||
const [user, setUser] = useState<User>();
|
||||
|
||||
useEffect(() => {
|
||||
@ -38,24 +38,20 @@ export function UserComponent() {
|
||||
}
|
||||
|
||||
function MyReservations() {
|
||||
const getAccessToken = useAuthState((s) => s.getAccessToken);
|
||||
const [reservations, setReservations] = useState<Reservation[]>();
|
||||
|
||||
useEffect(() => {
|
||||
getAccessToken().then((token) => {
|
||||
if (!token) return;
|
||||
apiFunctions
|
||||
.getReservationsForSelf(token)
|
||||
.then((res) => {
|
||||
setReservations(
|
||||
res.data.sort((a, b) => b.forDate.valueOf() - a.forDate.valueOf()),
|
||||
);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
});
|
||||
}, [getAccessToken]);
|
||||
apiFunctions
|
||||
.getReservationsForSelf()
|
||||
.then((res) => {
|
||||
setReservations(
|
||||
res.sort((a, b) => b.forDate.valueOf() - a.forDate.valueOf()),
|
||||
);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
@ -2,7 +2,6 @@
|
||||
|
||||
import { apiFunctions } from '@/api';
|
||||
import { Reservation } from '@/interfaces/reservation.interface';
|
||||
import { useAuthState } from '@/state/auth-state';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { ReservationComponent } from './reservation-component';
|
||||
import Link from 'next/link';
|
||||
@ -19,31 +18,26 @@ export default function ReservationPage({
|
||||
}: ReservationPageProps) {
|
||||
const [reservation, setReservation] = useState<Reservation | null>();
|
||||
const [event, setEvent] = useState<Event | null>();
|
||||
const getAccessToken = useAuthState((state) => state.getAccessToken);
|
||||
|
||||
// TODO Server populate
|
||||
useEffect(() => {
|
||||
getAccessToken().then((token) => {
|
||||
if (!token) return;
|
||||
apiFunctions
|
||||
.getReservation(token, id)
|
||||
.then((res) => {
|
||||
setReservation(res.data);
|
||||
apiFunctions
|
||||
.getEvent(res.data.event)
|
||||
.then((res) => {
|
||||
setEvent(res.data);
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log(e);
|
||||
setEvent(null);
|
||||
});
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log(e);
|
||||
setReservation(null);
|
||||
});
|
||||
});
|
||||
}, [id, getAccessToken]);
|
||||
apiFunctions
|
||||
.getReservation(id)
|
||||
.then((res) => {
|
||||
setReservation(res);
|
||||
apiFunctions
|
||||
.getEvent(res.event)
|
||||
.then(setEvent)
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
setEvent(null);
|
||||
});
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
setReservation(null);
|
||||
});
|
||||
}, [id]);
|
||||
|
||||
return (
|
||||
<div className="container py-10">
|
||||
@ -51,7 +45,7 @@ export default function ReservationPage({
|
||||
{reservation === null && (
|
||||
<div className="flex flex-col items-center gap-6">
|
||||
<h1 className="text-2xl font-medium">404 - Rezervacija ne obstaja</h1>
|
||||
<Link href="/" className="button">
|
||||
<Link href="/" className="button is-primary">
|
||||
Nazaj na domačo stran
|
||||
</Link>
|
||||
</div>
|
||||
|
@ -4,7 +4,6 @@ import { apiFunctions } from '@/api';
|
||||
import { Event } from '@/interfaces/event.interface';
|
||||
import { LogSummary } from '@/interfaces/log-summary.interface';
|
||||
import { Reservation } from '@/interfaces/reservation.interface';
|
||||
import { useAuthState } from '@/state/auth-state';
|
||||
import { getUTCString } from '@/util/date.util';
|
||||
import {
|
||||
faFileCircleCheck,
|
||||
@ -109,7 +108,9 @@ function LogSummaryC({ logSummary }: { logSummary: LogSummary }) {
|
||||
</div>
|
||||
|
||||
<div className="text-orange-500 dark:text-yellow-100">
|
||||
{logSummary.warnings && <div className="mt-3">Opozorila:</div>}
|
||||
{(logSummary.warnings?.length ?? 0) > 0 && (
|
||||
<div className="mt-3">Opozorila:</div>
|
||||
)}
|
||||
{logSummary.warnings?.map((warning, i) => <div key={i}>{warning}</div>)}
|
||||
</div>
|
||||
</div>
|
||||
@ -117,27 +118,25 @@ function LogSummaryC({ logSummary }: { logSummary: LogSummary }) {
|
||||
}
|
||||
|
||||
function Upload({ res }: { res: Reservation }) {
|
||||
const [error, setError] = useState<string>();
|
||||
const [file, setFile] = useState<File>();
|
||||
const getAccessToken = useAuthState((state) => state.getAccessToken);
|
||||
|
||||
async function submit() {
|
||||
if (!file) {
|
||||
setError('Datoteka ni izbrana');
|
||||
console.error('No file selected');
|
||||
return;
|
||||
}
|
||||
|
||||
const token = await getAccessToken();
|
||||
if (!token) {
|
||||
console.error('No token');
|
||||
return;
|
||||
}
|
||||
|
||||
apiFunctions
|
||||
.uploadLog(token, res._id, file)
|
||||
.uploadLog(res._id, file)
|
||||
.then(() => {
|
||||
window.location.reload();
|
||||
})
|
||||
.catch(console.error);
|
||||
.catch((e) => {
|
||||
setError(e.response.data.message);
|
||||
console.error(e);
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
@ -157,6 +156,7 @@ function Upload({ res }: { res: Reservation }) {
|
||||
{res.logSummary ? 'Povozi' : 'Pošlji'}
|
||||
</button>
|
||||
</div>
|
||||
{error && <div className="text-red-500">{error}</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ export function CurrentEvents() {
|
||||
const [events, setEvents] = useState<Event[]>();
|
||||
|
||||
useEffect(() => {
|
||||
apiFunctions.getCurrentEvents().then((res) => setEvents(res.data));
|
||||
apiFunctions.getCurrentEvents().then(setEvents).catch(console.error);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
|
@ -4,6 +4,7 @@ import { Role } from '@/enums/role.enum';
|
||||
import { User } from '@/interfaces/user.interface';
|
||||
import { useAuthState } from '@/state/auth-state';
|
||||
import { useThemeState } from '@/state/theme-state';
|
||||
import { useUserState } from '@/state/user-state';
|
||||
import { faMoon, faSun, faUserCircle } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import Link from 'next/link';
|
||||
@ -38,7 +39,8 @@ export function Header() {
|
||||
|
||||
function UserHeader() {
|
||||
const [user, setUser] = useState<User>();
|
||||
const [getUser, logout] = useAuthState((s) => [s.getUser, s.logout]);
|
||||
const getUser = useUserState((s) => s.getUser);
|
||||
const logout = useAuthState((s) => s.logout);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -5,18 +5,13 @@ import { Event } from '@/interfaces/event.interface';
|
||||
import Link from 'next/link';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { EventCard } from './event-card';
|
||||
import { useAuthState } from '@/state/auth-state';
|
||||
|
||||
export function PrivateEvents() {
|
||||
const [events, setEvents] = useState<Event[]>();
|
||||
const getAccessToken = useAuthState((s) => s.getAccessToken);
|
||||
|
||||
useEffect(() => {
|
||||
getAccessToken().then((token) => {
|
||||
if (!token) return;
|
||||
apiFunctions.getPrivateEvents(token).then((res) => setEvents(res.data));
|
||||
});
|
||||
}, [getAccessToken]);
|
||||
apiFunctions.getPrivateEvents().then(setEvents).catch(console.log);
|
||||
}, []);
|
||||
|
||||
if ((events?.length ?? 0) === 0) return <></>;
|
||||
|
||||
|
@ -1,16 +1,15 @@
|
||||
import { apiFunctions } from '@/api';
|
||||
import { User } from '@/interfaces/user.interface';
|
||||
import jwtDecode from 'jwt-decode';
|
||||
import secureLocalStorage from 'react-secure-storage';
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
|
||||
const useValidLock = create<Promise<boolean> | undefined>(() => undefined);
|
||||
|
||||
export interface AuthState {
|
||||
accessToken: string | null;
|
||||
refreshToken: string | null;
|
||||
user: User | null;
|
||||
|
||||
getUser: (refresh?: boolean) => Promise<User | null>;
|
||||
isValid: () => Promise<boolean>;
|
||||
getAccessToken: () => Promise<string | null>;
|
||||
logout: () => void;
|
||||
@ -21,55 +20,60 @@ export const useAuthState = create(
|
||||
(set, get) => ({
|
||||
accessToken: null,
|
||||
refreshToken: null,
|
||||
user: null,
|
||||
isValidLock: null,
|
||||
|
||||
getUser: async (refresh?: boolean): Promise<User | null> => {
|
||||
const { user, isValid } = get();
|
||||
if (user && !refresh) return user;
|
||||
|
||||
if (!(await isValid())) return null;
|
||||
|
||||
try {
|
||||
const user = (await apiFunctions.getMe(get().accessToken!)).data;
|
||||
set({ user });
|
||||
return user;
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
getAccessToken: async () => {
|
||||
const isValid = await get().isValid();
|
||||
return isValid ? get().accessToken : null;
|
||||
},
|
||||
isValid: async () => {
|
||||
const { accessToken, refreshToken } = get();
|
||||
const currentLock = useValidLock.getState();
|
||||
if (currentLock) return currentLock;
|
||||
|
||||
if (accessToken) {
|
||||
const { exp } = jwtDecode(accessToken) as { exp: number };
|
||||
if (Date.now() < exp * 1000) return true;
|
||||
else set({ accessToken: null });
|
||||
}
|
||||
const newLock = new Promise<boolean>(async (resolve) => {
|
||||
const { accessToken, refreshToken } = get();
|
||||
|
||||
if (refreshToken) {
|
||||
const { exp } = jwtDecode(refreshToken) as { exp: number };
|
||||
if (Date.now() < exp * 1000) {
|
||||
// Try to refresh
|
||||
try {
|
||||
set((await apiFunctions.refresh(refreshToken)).data);
|
||||
return true;
|
||||
} catch (e) {
|
||||
set({ refreshToken: null });
|
||||
if (accessToken) {
|
||||
const { exp } = jwtDecode(accessToken) as { exp: number };
|
||||
if (Date.now() < exp * 1000) {
|
||||
resolve(true);
|
||||
return;
|
||||
} else set({ accessToken: null });
|
||||
}
|
||||
|
||||
if (refreshToken) {
|
||||
const { exp } = jwtDecode(refreshToken) as { exp: number };
|
||||
if (Date.now() < exp * 1000) {
|
||||
// Try to refresh
|
||||
try {
|
||||
set(await apiFunctions.refresh(refreshToken));
|
||||
resolve(true);
|
||||
return;
|
||||
} catch (e) {
|
||||
set({ refreshToken: null });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
set({ refreshToken: null, accessToken: null, user: null });
|
||||
return false;
|
||||
set({ refreshToken: null, accessToken: null });
|
||||
resolve(false);
|
||||
});
|
||||
|
||||
useValidLock.setState(newLock);
|
||||
|
||||
const res = await newLock;
|
||||
|
||||
useValidLock.setState(undefined);
|
||||
|
||||
return res;
|
||||
},
|
||||
logout: async () => {
|
||||
set({ accessToken: null, refreshToken: null, user: null });
|
||||
if (await get().isValid()) apiFunctions.logout(get().accessToken!);
|
||||
try {
|
||||
apiFunctions.logout();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
set({ accessToken: null, refreshToken: null });
|
||||
},
|
||||
}),
|
||||
{
|
||||
|
24
next-app/src/state/user-state.ts
Normal file
24
next-app/src/state/user-state.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { apiFunctions } from '@/api';
|
||||
import { User } from '@/interfaces/user.interface';
|
||||
import { create } from 'zustand';
|
||||
|
||||
interface UserState {
|
||||
user: User | null;
|
||||
getUser: () => Promise<User | null>;
|
||||
}
|
||||
|
||||
export const useUserState = create<UserState>((set, get) => ({
|
||||
user: null,
|
||||
getUser: async () => {
|
||||
const user = get().user;
|
||||
if (user) return user;
|
||||
|
||||
try {
|
||||
const userFromApi = await apiFunctions.getMe();
|
||||
set({ user: userFromApi });
|
||||
return userFromApi;
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
}));
|
@ -1,3 +1,5 @@
|
||||
import { Event } from '@/interfaces/event.interface';
|
||||
|
||||
export function getUTCString(dt: Date) {
|
||||
function pad(num: number) {
|
||||
return num < 10 ? '0' + num : num;
|
||||
@ -14,3 +16,11 @@ export function getUTCString(dt: Date) {
|
||||
export const dayInMs = 1000 * 60 * 60 * 24;
|
||||
|
||||
export const dayInWeeks = ['Ned', 'Pon', 'Tor', 'Sre', 'Čet', 'Pet', 'Sob'];
|
||||
|
||||
export function getNextNDays(n: number, event: Event) {
|
||||
const start = Math.max(Date.now(), event.fromDateTime?.valueOf() ?? 0);
|
||||
return new Array(n)
|
||||
.fill(null)
|
||||
.map((_, i) => new Date(Math.floor(start / dayInMs + i) * dayInMs))
|
||||
.filter((date) => !event.toDateTime || date <= event.toDateTime);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user