Compare commits

...

3 Commits

Author SHA1 Message Date
tylen
dadb094017 frontend: add logout 2025-11-02 21:56:53 +02:00
tylen
27ee5d59c3 frontend: table addition elements refactor 2025-11-02 21:45:12 +02:00
tylen
c39384badf fix attendnace 2025-11-02 21:31:01 +02:00
8 changed files with 133 additions and 100 deletions

View File

@ -264,6 +264,15 @@ nav ul {
justify-content: space-around; /* Space the items evenly */ justify-content: space-around; /* Space the items evenly */
} }
nav ul li p {
color: white;
text-decoration: none;
}
nav ul li p:hover {
text-decoration: underline;
}
nav li { nav li {
display: inline; display: inline;
} }

View File

@ -10,6 +10,17 @@ import { FullScreenLoading } from './components/Loading';
function App() { function App() {
const [isNavOpen, setIsNavOpen] = useState(false); const [isNavOpen, setIsNavOpen] = useState(false);
const handleLogout = () => {
const cookies = document.cookie.split(";");
for (let cookie of cookies) {
const cookieName = cookie.split("=")[0].trim();
// Set the cookie's expiration date to the past to delete it
document.cookie = `${cookieName}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/;`;
}
window.location.reload();
};
const toggleNav = () => { const toggleNav = () => {
setIsNavOpen(!isNavOpen); setIsNavOpen(!isNavOpen);
}; };
@ -35,6 +46,9 @@ function App() {
<li> <li>
<a href="#program">Программа</a> <a href="#program">Программа</a>
</li> </li>
<li>
<button onClick={() => handleLogout()}>Выйти</button>
</li>
</ul> </ul>
</nav> </nav>

View File

@ -2,7 +2,7 @@
import React, { createContext, useContext, useState } from 'react'; import React, { createContext, useContext, useState } from 'react';
type NotificationType = 'success' | 'error'; export type NotificationType = 'success' | 'error';
interface NotificationContextType { interface NotificationContextType {
notify: (message: string, type: NotificationType) => void; notify: (message: string, type: NotificationType) => void;

View File

@ -1,17 +1,51 @@
import { useCookies } from "react-cookie"; import { useCookies } from "react-cookie";
import CenteredContainer from "./ChildrenContainer"; import CenteredContainer from "./ChildrenContainer";
import useFetchUser from "../utils/fetchUser"; import useFetchUser from "../utils/fetchUser";
import type React from "react";
import ApologyMessage from "./Attendance";
import { useState } from "react";
function Greeting() { const Attendance: React.FC = () => {
const [cookie] = useCookies<string>(['userName']) const { updateAttendance, getAttendance } = useFetchUser()
const userName = cookie.userName; const [attendance, setAttendance] = useState<boolean | null>(null)
const { updateAttendance } = useFetchUser() const fetchAttendance = async () => {
const is = await getAttendance()
setAttendance(is)
}
fetchAttendance()
const handleUpdate = async (status: boolean) => { const handleUpdate = async (status: boolean) => {
await updateAttendance(status) await updateAttendance(status)
window.location.reload(); window.location.reload();
} }
if (attendance == false) {
return (<ApologyMessage/>)
}
return (
<>
{attendance == null && (
<>
<h3>Присоеденишься?</h3>
<p>(Можно и потом ответить)</p>
<button style={localStyles.buttonOk} onClick={() => handleUpdate(true)}>Да!</button>
<button style={localStyles.buttonNok} onClick={() => handleUpdate(false)}>Не смогу в этот раз</button>
</>
)}
{attendance == true && (
<>
<h3>Круто! Ты с нами!</h3>
<p>Если все же по разным обстоятельствам ты не сможешь/не захочешь, то всегда можно передумать</p>
<button style={localStyles.buttonNok} onClick={() => handleUpdate(false)}>Все же никак</button>
</>)}
</>)
}
function Greeting() {
const [cookie] = useCookies<string>(['userName'])
const userName = cookie.userName;
return ( return (
<> <>
<CenteredContainer> <CenteredContainer>
@ -24,11 +58,9 @@ function Greeting() {
Приглашаем тебя отпраздновать предстоящий Новый Год <b>2025-2026</b> с нами в сосновой избе, в которой, ко всему прочему, будет праздноваться годовщина нашей жизни в ней! Приглашаем тебя отпраздновать предстоящий Новый Год <b>2025-2026</b> с нами в сосновой избе, в которой, ко всему прочему, будет праздноваться годовщина нашей жизни в ней!
Наши двери открыты с <b>30.12.2025</b>. Праздник обычно длится до <b>01.01.2025</b>, но если тебе или твоим спутникам будет безумно плохо, то можно остаться и до второго числа. Наши двери открыты с <b>30.12.2025</b>. Праздник обычно длится до <b>01.01.2025</b>, но если тебе или твоим спутникам будет безумно плохо, то можно остаться и до второго числа.
<h3>Присоеденишься?</h3>
<p>(Можно и потом ответить)</p>
<button style={localStyles.buttonOk} onClick={() => handleUpdate(true)}>Да!</button>
<button style={localStyles.buttonNok}onClick={() => handleUpdate(false)}>Не смогу в этот раз</button>
</p> </p>
<Attendance/>
</CenteredContainer> </CenteredContainer>
</> </>
) )

View File

@ -1,5 +1,5 @@
import useFetchHosting from "../utils/fetchHosting"; import useFetchHosting from "../utils/fetchHosting";
import { useNotification } from "../NotificationContext"; import { useNotification, type NotificationType } from "../NotificationContext";
import { useCookies } from "react-cookie"; import { useCookies } from "react-cookie";
import CenteredContainer from "./ChildrenContainer"; import CenteredContainer from "./ChildrenContainer";
import { useState } from "react"; import { useState } from "react";
@ -9,16 +9,16 @@ interface ReserveButtonProps {
update: (name: string, id: number) => void, update: (name: string, id: number) => void,
unreserve: (token: string, id: number) => void, unreserve: (token: string, id: number) => void,
reservedBy: string, reservedBy: string,
notify: (message: string, type: NotificationType) => void,
id: number, id: number,
} }
const ReserveButton: React.FC<ReserveButtonProps> = (props) => { const ReserveButton: React.FC<ReserveButtonProps> = (props) => {
const { reservedBy, update, id, unreserve } = props; const { reservedBy, update, id, unreserve, notify } = props;
const [cookie] = useCookies<string>(['userName']) const [cookie] = useCookies<string>(['userName'])
const [tokenCookie] = useCookies<string>(['apiToken']) const [tokenCookie] = useCookies<string>(['apiToken'])
const userName = cookie.userName; const userName = cookie.userName;
const isReserved = reservedBy !== ''; const isReserved = reservedBy !== '';
const notify = useNotification();
const handleReserve = async (name: string) => { const handleReserve = async (name: string) => {
try { try {
@ -50,68 +50,33 @@ const ReserveButton: React.FC<ReserveButtonProps> = (props) => {
); );
}; };
interface CreateHostingFormProps {
createHosting: (token: string, name: string, capacity: number) => void,
}
const CreateHostingForm: React.FC<CreateHostingFormProps> = (props) => { const Hosting = () => {
const { createHosting } = props; const { data, error, loading, update, unreserveHosting, createHosting } = useFetchHosting();
const [name, setName] = useState(''); const [name, setName] = useState('');
const [capacity, setCapacity] = useState(''); const [capacity, setCapacity] = useState('');
const [tokenCookie] = useCookies<string>(['apiToken']) const [tokenCookie] = useCookies<string>(['apiToken'])
const notify = useNotification(); const notify = useNotification();
const handleSubmit = async () => { const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault(); // Prevent the default form submission
if (!name || !capacity) { if (!name || !capacity) {
notify('Надо заполнить все поля', 'error') notify('Надо заполнить все поля', 'error');
return return;
} }
try { try {
await createHosting(tokenCookie.apiToken, name, Number(capacity)); // Call the existing hook await createHosting(tokenCookie.apiToken, name, Number(capacity));
// Reset form fields // Reset form fields
setName(''); setName('');
setCapacity(''); setCapacity('');
notify('Удалось создать!', 'success') notify('Удалось создать!', 'success');
} catch (error) { } catch (error) {
notify(`Не удалось создать: ${error instanceof Error ? error.message : 'Unknown error'}`, 'error'); // Capture any error message notify(`Не удалось создать: ${error instanceof Error ? error.message : 'Unknown error'}`, 'error');
} }
}
return (
<form onSubmit={handleSubmit}>
<div>
<label>
Размещение:
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
required
/>
</label>
</div>
<div>
<label>
Спальных мест:
<input
type="number"
value={capacity}
onChange={(e) => setCapacity(e.target.value)}
required
/>
</label>
</div>
<button type="submit" onClick={() => handleSubmit()}>
{'Создать размещение'}
</button>
</form>
);
}; };
const Hosting = () => {
const { data, error, loading, update, unreserveHosting, createHosting } = useFetchHosting();
return ( return (
<> <>
<CenteredContainer> <CenteredContainer>
@ -142,7 +107,7 @@ const Hosting = () => {
<td>{item.name}</td> <td>{item.name}</td>
<td>{item.capacity}</td> <td>{item.capacity}</td>
<td> <td>
<ReserveButton update={update} reservedBy={item.reservedBy} id={item.id} unreserve={unreserveHosting} /> <ReserveButton update={update} reservedBy={item.reservedBy} id={item.id} unreserve={unreserveHosting} notify={notify} />
</td> </td>
</tr> </tr>
))} ))}
@ -152,7 +117,34 @@ const Hosting = () => {
</div> </div>
)} )}
Если вы хотите организовать себе свои спальные места и хотите, чтобы остальные это видели, вы можете добавить свое месо в таблицу. Если вы хотите организовать себе свои спальные места и хотите, чтобы остальные это видели, вы можете добавить свое месо в таблицу.
<CreateHostingForm createHosting={createHosting}/> <form onSubmit={handleSubmit}>
<div>
<label>
Размещение:
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
required
/>
</label>
</div>
<div>
<label>
Спальных мест:
<input
type="number"
value={capacity}
onChange={(e) => setCapacity(e.target.value)}
required
/>
</label>
</div>
<button type="submit">
{'Создать размещение'}
</button>
</form>
</CenteredContainer> </CenteredContainer>
<br /> <br />
</> </>

View File

@ -3,7 +3,6 @@ import { useCookies } from 'react-cookie';
import { GUESTS } from '../constants/constants'; import { GUESTS } from '../constants/constants';
import useFetchUser from '../utils/fetchUser'; // Import your custom hook import useFetchUser from '../utils/fetchUser'; // Import your custom hook
import { useNotification } from '../NotificationContext'; import { useNotification } from '../NotificationContext';
import ApologyMessage from './Attendance';
import {Loading} from './Loading'; import {Loading} from './Loading';
const InitialSetup = () => { const InitialSetup = () => {
@ -13,9 +12,8 @@ const InitialSetup = () => {
const [isSubmitted, setIsSubmitted] = useState(false); const [isSubmitted, setIsSubmitted] = useState(false);
const [password, setPassword] = useState(''); const [password, setPassword] = useState('');
const [isPasswordSet, setIsPasswordSet] = useState(false); // To track if password is set const [isPasswordSet, setIsPasswordSet] = useState(false); // To track if password is set
const [userAttendance, setUserAttendance] = useState<boolean | null>(null);
const { userSet, passwordCreate, signUser, validToken, getAttendance, isLoading } = useFetchUser(); // Destructure functions from the hook const { userSet, passwordCreate, signUser, validToken, isLoading } = useFetchUser(); // Destructure functions from the hook
const notify = useNotification(); const notify = useNotification();
const checkUserPassword = async (name: string) => { const checkUserPassword = async (name: string) => {
@ -35,14 +33,8 @@ const InitialSetup = () => {
setIsSubmitted(isTokenValid); setIsSubmitted(isTokenValid);
}; };
const getUserAttendance = async () => {
const attendance = await getAttendance()
setUserAttendance(attendance)
}
useEffect(() => { useEffect(() => {
if (cookie.apiToken !== undefined) { if (cookie.apiToken !== undefined) {
getUserAttendance()
validateToken(); validateToken();
} }
}, [cookie.apiToken]); }, [cookie.apiToken]);
@ -66,16 +58,11 @@ const InitialSetup = () => {
validateToken() validateToken()
}; };
if (isSubmitted && userAttendance !== false) { if (isSubmitted) {
console.log('Selected', selectedName); console.log('Selected', selectedName);
return null; // or you can redirect to another component or page return null; // or you can redirect to another component or page
} }
if (userAttendance == false) {
return (
<ApologyMessage/>
)
}
if (isLoading) { if (isLoading) {
return ( return (

View File

@ -71,7 +71,6 @@ const useFetchHosting = () => {
}; };
const createHosting = async (token: string, name: string, capacity: number) => { const createHosting = async (token: string, name: string, capacity: number) => {
setLoading(true);
setError(null); setError(null);
try { try {
const response = await fetch(`${API_URL}/hosting/create`, { const response = await fetch(`${API_URL}/hosting/create`, {
@ -84,13 +83,13 @@ const useFetchHosting = () => {
if (!response.ok) { // Check for non-200 responses if (!response.ok) { // Check for non-200 responses
const errorText = await response.text(); // Capture the response text for further insights const errorText = await response.text(); // Capture the response text for further insights
console.log(`Error ${response.status}: ${errorText}`)
throw new Error(`Error ${response.status}: ${errorText}`); throw new Error(`Error ${response.status}: ${errorText}`);
} }
// Optional: Fetch the updated data after reservation // Optional: Fetch the updated data after reservation
await fetchData(); await fetchData();
} finally { } finally {
setLoading(false);
} }
}; };

View File

@ -114,6 +114,28 @@ const useFetchUser = () => {
} }
} }
const getAttendance = async (): Promise<boolean | null> => {
const token = apiCookie.apiToken
try {
const response = await fetch(`${API_URL}/users/attendance?token=${encodeURIComponent(token)}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
}
});
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
const data = await response.json();
if (!data.success) throw new Error(data.message);
return data.attendance
} catch (error) {
console.error('Error retrieving attendance:', error);
return null
}
}
const updateAttendance = async (attendanceStatus: boolean): Promise<boolean> => { const updateAttendance = async (attendanceStatus: boolean): Promise<boolean> => {
const token = apiCookie.apiToken const token = apiCookie.apiToken
try { try {
@ -140,28 +162,6 @@ const useFetchUser = () => {
} }
} }
const getAttendance = async (): Promise<boolean | null> => {
const token = apiCookie.apiToken
try {
const response = await fetch(`${API_URL}/users/attendance?token=${encodeURIComponent(token)}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
}
});
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
const data = await response.json();
if (!data.success) throw new Error(data.message);
return data.attendance; // Returns attendance status (true/false)
} catch (error) {
console.error('Error retrieving attendance:', error);
return null; // In case of error or if attendance status is not found
}
}
return { userSet, passwordCreate, signUser, validToken, updateAttendance, getAttendance, isLoading }; return { userSet, passwordCreate, signUser, validToken, updateAttendance, getAttendance, isLoading };
}; };