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 */
}
nav ul li p {
color: white;
text-decoration: none;
}
nav ul li p:hover {
text-decoration: underline;
}
nav li {
display: inline;
}

View File

@ -10,6 +10,17 @@ import { FullScreenLoading } from './components/Loading';
function App() {
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 = () => {
setIsNavOpen(!isNavOpen);
};
@ -35,6 +46,9 @@ function App() {
<li>
<a href="#program">Программа</a>
</li>
<li>
<button onClick={() => handleLogout()}>Выйти</button>
</li>
</ul>
</nav>

View File

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

View File

@ -1,17 +1,51 @@
import { useCookies } from "react-cookie";
import CenteredContainer from "./ChildrenContainer";
import useFetchUser from "../utils/fetchUser";
import type React from "react";
import ApologyMessage from "./Attendance";
import { useState } from "react";
function Greeting() {
const [cookie] = useCookies<string>(['userName'])
const userName = cookie.userName;
const { updateAttendance } = useFetchUser()
const Attendance: React.FC = () => {
const { updateAttendance, getAttendance } = useFetchUser()
const [attendance, setAttendance] = useState<boolean | null>(null)
const fetchAttendance = async () => {
const is = await getAttendance()
setAttendance(is)
}
fetchAttendance()
const handleUpdate = async (status: boolean) => {
await updateAttendance(status)
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 (
<>
<CenteredContainer>
@ -24,11 +58,9 @@ function Greeting() {
Приглашаем тебя отпраздновать предстоящий Новый Год <b>2025-2026</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>
<Attendance/>
</CenteredContainer>
</>
)

View File

@ -1,5 +1,5 @@
import useFetchHosting from "../utils/fetchHosting";
import { useNotification } from "../NotificationContext";
import { useNotification, type NotificationType } from "../NotificationContext";
import { useCookies } from "react-cookie";
import CenteredContainer from "./ChildrenContainer";
import { useState } from "react";
@ -9,16 +9,16 @@ interface ReserveButtonProps {
update: (name: string, id: number) => void,
unreserve: (token: string, id: number) => void,
reservedBy: string,
notify: (message: string, type: NotificationType) => void,
id: number,
}
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 [tokenCookie] = useCookies<string>(['apiToken'])
const userName = cookie.userName;
const isReserved = reservedBy !== '';
const notify = useNotification();
const handleReserve = async (name: string) => {
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 { createHosting } = props;
const Hosting = () => {
const { data, error, loading, update, unreserveHosting, createHosting } = useFetchHosting();
const [name, setName] = useState('');
const [capacity, setCapacity] = useState('');
const [tokenCookie] = useCookies<string>(['apiToken'])
const notify = useNotification();
const handleSubmit = async () => {
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault(); // Prevent the default form submission
if (!name || !capacity) {
notify('Надо заполнить все поля', 'error')
return
notify('Надо заполнить все поля', 'error');
return;
}
try {
await createHosting(tokenCookie.apiToken, name, Number(capacity)); // Call the existing hook
await createHosting(tokenCookie.apiToken, name, Number(capacity));
// Reset form fields
setName('');
setCapacity('');
notify('Удалось создать!', 'success')
notify('Удалось создать!', 'success');
} 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 (
<>
<CenteredContainer>
@ -142,7 +107,7 @@ const Hosting = () => {
<td>{item.name}</td>
<td>{item.capacity}</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>
</tr>
))}
@ -152,7 +117,34 @@ const Hosting = () => {
</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>
<br />
</>

View File

@ -3,7 +3,6 @@ import { useCookies } from 'react-cookie';
import { GUESTS } from '../constants/constants';
import useFetchUser from '../utils/fetchUser'; // Import your custom hook
import { useNotification } from '../NotificationContext';
import ApologyMessage from './Attendance';
import {Loading} from './Loading';
const InitialSetup = () => {
@ -13,9 +12,8 @@ const InitialSetup = () => {
const [isSubmitted, setIsSubmitted] = useState(false);
const [password, setPassword] = useState('');
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 checkUserPassword = async (name: string) => {
@ -35,14 +33,8 @@ const InitialSetup = () => {
setIsSubmitted(isTokenValid);
};
const getUserAttendance = async () => {
const attendance = await getAttendance()
setUserAttendance(attendance)
}
useEffect(() => {
if (cookie.apiToken !== undefined) {
getUserAttendance()
validateToken();
}
}, [cookie.apiToken]);
@ -65,17 +57,12 @@ const InitialSetup = () => {
}
validateToken()
};
if (isSubmitted && userAttendance !== false) {
if (isSubmitted) {
console.log('Selected', selectedName);
return null; // or you can redirect to another component or page
}
if (userAttendance == false) {
return (
<ApologyMessage/>
)
}
if (isLoading) {
return (

View File

@ -71,7 +71,6 @@ const useFetchHosting = () => {
};
const createHosting = async (token: string, name: string, capacity: number) => {
setLoading(true);
setError(null);
try {
const response = await fetch(`${API_URL}/hosting/create`, {
@ -84,13 +83,13 @@ const useFetchHosting = () => {
if (!response.ok) { // Check for non-200 responses
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}`);
}
// Optional: Fetch the updated data after reservation
await fetchData();
} 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 token = apiCookie.apiToken
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 };
};