booking-flow-finalized-design kindaaaa #7

Merged
jare2473 merged 20 commits from booking-flow-finalized-design into main 2025-09-30 10:50:54 +02:00
9 changed files with 307 additions and 30 deletions
Showing only changes of commit 647b4bf8e0 - Show all commits

View File

@@ -1,16 +1,40 @@
import React from 'react';
import React, { useState, useEffect } from 'react';
import { BrowserRouter as Router } from 'react-router-dom';
import AppRoutes from './AppRoutes'; // move the routing and loading logic here
import { SettingsProvider } from './context/SettingsContext';
import { SettingsProvider, useSettingsContext } from './context/SettingsContext';
import { ThemeProvider } from './context/ThemeContext';
import { NamePrompt } from './components/ui/NamePrompt';
function AppContent() {
const { shouldShowNamePrompt } = useSettingsContext();
const [showNamePrompt, setShowNamePrompt] = useState(false);
useEffect(() => {
setShowNamePrompt(shouldShowNamePrompt());
}, [shouldShowNamePrompt]);
const handleCloseNamePrompt = () => {
setShowNamePrompt(false);
};
return (
<>
<Router basename={import.meta.env.BASE_URL}>
<AppRoutes />
</Router>
<NamePrompt
isOpen={showNamePrompt}
onClose={handleCloseNamePrompt}
/>
</>
);
}
function App() {
return (
<ThemeProvider>
<SettingsProvider>
<Router basename={import.meta.env.BASE_URL}>
<AppRoutes />
</Router>
<AppContent />
</SettingsProvider>
</ThemeProvider>
);

View File

@@ -15,6 +15,7 @@ import Home from './pages/Home';
import CoursePage from './pages/CoursePage';
import Profile from './pages/Profile';
import RoomSchedules from './pages/RoomSchedules';
import { useSettingsContext } from './context/SettingsContext';
const AppRoutes = () => {
const location = useLocation();
@@ -27,6 +28,11 @@ const AppRoutes = () => {
const [lastLeftBooking, setLastLeftBooking] = useState(null);
const [showUpdateBanner, setShowUpdateBanner] = useState(false);
const [lastUpdatedBooking, setLastUpdatedBooking] = useState(null);
const { getCurrentUser } = useSettingsContext();
const currentUser = getCurrentUser();
// Mock bookings data
// In a real app, this would come from an API or global state
const [bookings, setBookings] = useState([
{
id: 1,
@@ -37,7 +43,7 @@ const AppRoutes = () => {
roomCategory: 'green',
title: 'Team standup',
participants: [
{ id: 1, name: 'Jacob Reinikainen', username: 'jare2473', email: 'jacob.reinikainen@dsv.su.se' },
currentUser,
{ id: 2, name: 'Filip Norgren', username: 'fino2341', email: 'filip.norgren@dsv.su.se' },
{ id: 3, name: 'Hedvig Engelmark', username: 'heen9876', email: 'hedvig.engelmark@dsv.su.se' },
{ id: 4, name: 'Elin Rudling', username: 'elru4521', email: 'elin.rudling@dsv.su.se' }
@@ -52,7 +58,7 @@ const AppRoutes = () => {
roomCategory: 'red',
title: 'Project planning workshop',
participants: [
{ id: 1, name: 'Jacob Reinikainen', username: 'jare2473', email: 'jacob.reinikainen@dsv.su.se' },
currentUser,
{ id: 5, name: 'Victor Magnusson', username: 'vima8734', email: 'victor.magnusson@dsv.su.se' },
{ id: 6, name: 'Ellen Britschgi', username: 'elbr5623', email: 'ellen.britschgi@dsv.su.se' },
{ id: 7, name: 'Anna Andersson', username: 'anan3457', email: 'anna.andersson@dsv.su.se' },
@@ -70,7 +76,7 @@ const AppRoutes = () => {
roomCategory: 'blue',
title: '1:1 with supervisor',
participants: [
{ id: 1, name: 'Jacob Reinikainen', username: 'jare2473', email: 'jacob.reinikainen@dsv.su.se' },
currentUser,
{ id: 251, name: 'Arjohn Emilsson', username: 'arem1532', email: 'arjohn.emilsson@dsv.su.se' }
]
},
@@ -83,7 +89,7 @@ const AppRoutes = () => {
roomCategory: 'yellow',
title: 'Study group session',
participants: [
{ id: 1, name: 'Jacob Reinikainen', username: 'jare2473', email: 'jacob.reinikainen@dsv.su.se' },
currentUser,
{ id: 11, name: 'Emma Johansson', username: 'emjo4512', email: 'emma.johansson@dsv.su.se' },
{ id: 12, name: 'Oskar Pettersson', username: 'ospe3698', email: 'oskar.pettersson@dsv.su.se' }
]
@@ -99,7 +105,7 @@ const AppRoutes = () => {
createdBy: { id: 3, name: 'Hedvig Engelmark', username: 'heen9876', email: 'hedvig.engelmark@dsv.su.se' },
participants: [
{ id: 3, name: 'Hedvig Engelmark', username: 'heen9876', email: 'hedvig.engelmark@dsv.su.se' },
{ id: 1, name: 'Jacob Reinikainen', username: 'jare2473', email: 'jacob.reinikainen@dsv.su.se' },
currentUser,
{ id: 5, name: 'Victor Magnusson', username: 'vima8734', email: 'victor.magnusson@dsv.su.se' },
{ id: 8, name: 'Erik Larsson', username: 'erla7892', email: 'erik.larsson@dsv.su.se' }
],

View File

@@ -9,7 +9,6 @@ import { ParticipantsSelector } from '../forms/ParticipantsSelector';
import { useBookingContext } from '../../context/BookingContext';
import { useSettingsContext } from '../../context/SettingsContext';
import { generateId } from '../../utils/bookingUtils';
import { USER } from '../../constants/bookingConstants';
import styles from './InlineModalBookingForm.module.css';
import extendedStyles from './InlineModalExtendedBookingForm.module.css';
@@ -41,6 +40,7 @@ export function InlineModalExtendedBookingForm({
const navigate = useNavigate();
const booking = useBookingContext();
const { getCurrentUser, getDefaultBookingTitle } = useSettingsContext();
const currentUser = getCurrentUser();
// Initialize with pre-selected end time if available, or auto-select if only 30 min available
const initialEndTimeIndex = booking.selectedBookingLength > 0 ? startTimeIndex + booking.selectedBookingLength :
@@ -161,9 +161,9 @@ export function InlineModalExtendedBookingForm({
const roomToBook = booking.selectedRoom !== "allRooms" ? booking.selectedRoom : booking.assignedRoom;
// Include the current user as a participant if not already added
const allParticipants = booking.participants.find(p => p.id === USER.id)
const allParticipants = booking.participants.find(p => p.id === currentUser.id)
? booking.participants
: [USER, ...booking.participants];
: [currentUser, ...booking.participants];
const finalTitle = booking.title !== "" ? booking.title : getDefaultBookingTitle();
@@ -204,9 +204,9 @@ export function InlineModalExtendedBookingForm({
</p>
{(() => {
// Include the current user as a participant if not already added
const allParticipants = booking.participants.find(p => p.id === USER.id)
const allParticipants = booking.participants.find(p => p.id === currentUser.id)
? booking.participants
: [USER, ...booking.participants];
: [currentUser, ...booking.participants];
const startTime = getTimeFromIndex(booking.selectedStartIndex);
const endTime = getTimeFromIndex(booking.selectedEndIndex);

View File

@@ -0,0 +1,107 @@
import React, { useState } from 'react';
import { Button, Dialog, Heading, Modal } from 'react-aria-components';
import { useSettingsContext } from '../../context/SettingsContext';
import styles from './NamePrompt.module.css';
export function NamePrompt({ isOpen, onClose }) {
const { updateSettings } = useSettingsContext();
const [name, setName] = useState('');
const [error, setError] = useState('');
// Helper function to generate username from name
const generateUsername = (fullName) => {
const nameParts = fullName.trim().toLowerCase().split(' ');
if (nameParts.length >= 2) {
const firstName = nameParts[0];
const lastName = nameParts[nameParts.length - 1];
// Take first 2 chars of first name + first 2 chars of last name + random 4 digits
const randomDigits = Math.floor(1000 + Math.random() * 9000);
return `${firstName.substring(0, 2)}${lastName.substring(0, 2)}${randomDigits}`;
} else {
// Fallback for single name
const randomDigits = Math.floor(1000 + Math.random() * 9000);
return `${nameParts[0].substring(0, 4)}${randomDigits}`;
}
};
// Helper function to generate email from username
const generateEmail = (username) => {
return `${username}@student.su.se`;
};
const handleSubmit = (e) => {
e.preventDefault();
if (!name.trim()) {
setError('Vänligen ange ditt namn');
return;
}
const trimmedName = name.trim();
const generatedUsername = generateUsername(trimmedName);
const generatedEmail = generateEmail(generatedUsername);
// Update settings with name, username, and email
updateSettings({
currentUserName: trimmedName,
currentUserUsername: generatedUsername,
currentUserEmail: generatedEmail
});
localStorage.setItem('hasSeenNamePrompt', 'true');
onClose();
// Refresh the page to ensure all components get the updated user data
window.location.reload();
};
const handleInputChange = (e) => {
setName(e.target.value);
if (error) setError('');
};
return (
<Modal
isOpen={isOpen}
isDismissable={false}
className={styles.modal}
>
<Dialog className={styles.dialog}>
<div className={styles.content}>
<Heading slot="title" className={styles.title}>
Välkommen!
</Heading>
<p className={styles.description}>
För att komma igång behöver vi veta vad du heter. Ditt namn kommer att visas i bokningar och deltagarlistan.
</p>
<form onSubmit={handleSubmit} className={styles.form}>
<div className={styles.inputGroup}>
<label htmlFor="userName" className={styles.label}>
Ditt namn
</label>
<input
id="userName"
type="text"
value={name}
onChange={handleInputChange}
className={`${styles.input} ${error ? styles.inputError : ''}`}
placeholder="Ange ditt för- och efternamn"
autoFocus
/>
{error && <span className={styles.errorMessage}>{error}</span>}
</div>
<div className={styles.footer}>
<Button
type="submit"
className={styles.submitButton}
>
Fortsätt
</Button>
</div>
</form>
</div>
</Dialog>
</Modal>
);
}

View File

@@ -0,0 +1,132 @@
.modal {
display: flex;
align-items: center;
justify-content: center;
position: fixed;
inset: 0;
z-index: 100;
background: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(4px);
}
.dialog {
background: white;
border-radius: 12px;
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
max-width: 480px;
width: 90vw;
padding: 0;
border: none;
overflow: hidden;
}
.content {
padding: 32px;
}
.title {
font-size: 24px;
font-weight: 600;
margin: 0 0 16px 0;
color: #111827;
text-align: center;
}
.description {
font-size: 16px;
line-height: 1.5;
color: #6b7280;
margin: 0 0 24px 0;
text-align: center;
}
.form {
display: flex;
flex-direction: column;
gap: 24px;
}
.inputGroup {
display: flex;
flex-direction: column;
gap: 8px;
}
.label {
font-size: 14px;
font-weight: 500;
color: #374151;
}
.input {
padding: 12px 16px;
border: 2px solid #d1d5db;
border-radius: 8px;
font-size: 16px;
transition: border-color 0.2s, box-shadow 0.2s;
}
.input:focus {
outline: none;
border-color: #3b82f6;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
.inputError {
border-color: #ef4444;
}
.inputError:focus {
border-color: #ef4444;
box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1);
}
.errorMessage {
font-size: 14px;
color: #ef4444;
margin-top: 4px;
}
.footer {
display: flex;
justify-content: center;
}
.submitButton {
background: #3b82f6;
color: white;
border: none;
border-radius: 8px;
padding: 12px 32px;
font-size: 16px;
font-weight: 500;
cursor: pointer;
transition: background-color 0.2s, transform 0.1s;
}
.submitButton:hover {
background: #2563eb;
}
.submitButton:active {
transform: translateY(1px);
}
.submitButton:focus {
outline: none;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.3);
}
@media (max-width: 640px) {
.content {
padding: 24px;
}
.title {
font-size: 20px;
}
.description {
font-size: 15px;
}
}

View File

@@ -26,7 +26,6 @@
.container:active,
.container[data-pressed] {
background-color: var(--timecard-active-bg);
transform: translateY(1px);
box-shadow: var(--timecard-active-shadow);
transition: all 0.1s ease;
}
@@ -84,7 +83,11 @@
border: 1px solid var(--timecard-unavailable-border);
height: 50px;
width: 165px;
background: red;
}
.unavailableSlot:active {
background-color: var(--timecard-unavailable-bg);
box-shadow: none;
}
.modalFooter {
@@ -142,13 +145,11 @@
.saveButton:active {
background-color: var(--modal-save-active-bg);
transform: translateY(1px);
box-shadow: var(--modal-save-active-shadow);
}
.cancelButton:active {
background-color: var(--modal-cancel-active-bg);
transform: translateY(1px);
}
.timeSpan {

View File

@@ -13,6 +13,8 @@ const DEFAULT_SETTINGS = {
earliestTimeSlot: 0,
latestTimeSlot: 23,
currentUserName: USER.name,
currentUserUsername: USER.username,
currentUserEmail: USER.email,
showDevelopmentBanner: false,
showBookingConfirmationBanner: false,
showBookingDeleteBanner: false,
@@ -44,8 +46,10 @@ export const SettingsProvider = ({ children }) => {
...parsed,
// Convert date strings back to DateValue objects
mockToday: parsed.mockToday ? new Date(parsed.mockToday) : null,
// Ensure currentUserName has a fallback
// Ensure user fields have fallbacks
currentUserName: parsed.currentUserName || USER.name,
currentUserUsername: parsed.currentUserUsername || USER.username,
currentUserEmail: parsed.currentUserEmail || USER.email,
};
} catch (e) {
console.warn('Failed to parse saved settings:', e);
@@ -55,6 +59,13 @@ export const SettingsProvider = ({ children }) => {
return DEFAULT_SETTINGS;
});
// Check if user should see name prompt
const shouldShowNamePrompt = () => {
const hasSeenPrompt = localStorage.getItem('hasSeenNamePrompt');
const hasCustomName = settings.currentUserName !== USER.name;
return !hasSeenPrompt && !hasCustomName;
};
// Save settings to localStorage whenever they change
useEffect(() => {
const toSave = {
@@ -93,8 +104,8 @@ export const SettingsProvider = ({ children }) => {
return {
id: USER.id,
name: settings.currentUserName,
username: USER.username,
email: USER.email
username: settings.currentUserUsername,
email: settings.currentUserEmail
};
};
@@ -112,6 +123,7 @@ export const SettingsProvider = ({ children }) => {
getEffectiveToday,
getCurrentUser,
getDefaultBookingTitle,
shouldShowNamePrompt,
}}>
{children}
</SettingsContext.Provider>

View File

@@ -6,6 +6,8 @@ import styles from './Profile.module.css';
const Profile = () => {
const { getCurrentUser } = useSettingsContext();
const user = getCurrentUser();
console.log(user, "Current User Data");
// Helper function to get user's initials
const getInitials = (name) => {

View File

@@ -26,14 +26,7 @@ export function RoomBooking({ bookings, showSuccessBanner, lastCreatedBooking, o
return (
<>
{isTestSessionActive && (
<div className={styles.welcomeSection}>
<div className={styles.welcomeContent}>
<h1 className={styles.welcomeTitle}>Välkommen, {settings.currentUserName}!</h1>
<p className={styles.welcomeSubtitle}>Hantera dina bokningar och reservera nya lokaler</p>
</div>
</div>
)}
<PageHeader title="Lokalbokning" subtitle="Reservera lokaler för möten och studier" />
<PageContainer>
<h2 className={styles.sectionHeading}>Ny bokning</h2>