mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-22 06:41:46 +00:00
v2.5.7: Reservation overhaul, Day Detail Panel, i18n, paste support, auto dark mode
BREAKING: Reservations have been completely rebuilt. Existing place-level reservations are no longer used. All reservations must be re-created via the Bookings tab. Your trips, places, and other data are unaffected. Reservation System (rebuilt from scratch): - Reservations now link to specific day assignments instead of places - Same place on different days can have independent reservations - New assignment picker in booking modal (grouped by day, searchable) - Removed day/place dropdowns from booking form - Reservation badges in day plan sidebar with type-specific icons - Reservation details in place inspector (only for selected assignment) - Reservation summary in day detail panel Day Detail Panel (new): - Opens on day click in the sidebar - Detailed weather: hourly forecast, precipitation, wind, sunrise/sunset - Historical climate averages for dates beyond 16 days - Accommodation management with check-in/check-out, confirmation number - Hotel assignment across multiple days with day range picker - Reservation overview for the day Places: - Places can now be assigned to the same day multiple times - Start time + end time fields (replaces single time field) - Map badges show multiple position numbers (e.g. "1 · 4") - Route optimization fixed for duplicate places - File attachments during place editing (not just creation) - Cover image upload during trip creation (not just editing) - Paste support (Ctrl+V) for images in trip, place, and file forms Internationalization: - 200+ hardcoded German strings translated to i18n (EN + DE) - Server error messages in English - Category seeds in English for new installations - All planner, register, photo, packing components translated UI/UX: - Auto dark mode (follows system preference, configurable in settings) - Navbar toggle switches light/dark (overrides auto) - Sidebar minimize buttons z-index fixed - Transport mode selector removed from day plan - CustomSelect supports grouped headers (isHeader option) - Optimistic updates for day notes (instant feedback) - Booking cards redesigned with type-colored headers and structured details Weather: - Wind speed in mph when using Fahrenheit setting - Weather description language matches app language Admin: - Weather info panel replaces OpenWeatherMap key input - "Recommended" badge styling updated
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
import React, { useState } from 'react'
|
||||
import { Link, useNavigate } from 'react-router-dom'
|
||||
import { useAuthStore } from '../store/authStore'
|
||||
import { useTranslation } from '../i18n'
|
||||
import { Map, Eye, EyeOff, Mail, Lock, User } from 'lucide-react'
|
||||
|
||||
export default function RegisterPage() {
|
||||
const { t } = useTranslation()
|
||||
const [username, setUsername] = useState('')
|
||||
const [email, setEmail] = useState('')
|
||||
const [password, setPassword] = useState('')
|
||||
@@ -20,12 +22,12 @@ export default function RegisterPage() {
|
||||
setError('')
|
||||
|
||||
if (password !== confirmPassword) {
|
||||
setError('Passwörter stimmen nicht überein')
|
||||
setError(t('register.passwordMismatch'))
|
||||
return
|
||||
}
|
||||
|
||||
if (password.length < 6) {
|
||||
setError('Passwort muss mindestens 6 Zeichen lang sein')
|
||||
setError(t('register.passwordTooShort'))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -34,7 +36,7 @@ export default function RegisterPage() {
|
||||
await register(username, email, password)
|
||||
navigate('/dashboard')
|
||||
} catch (err) {
|
||||
setError(err.message || 'Registrierung fehlgeschlagen')
|
||||
setError(err.message || t('register.failed'))
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
@@ -48,19 +50,19 @@ export default function RegisterPage() {
|
||||
<div className="w-20 h-20 bg-white/10 rounded-2xl flex items-center justify-center mx-auto mb-6">
|
||||
<Map className="w-10 h-10 text-white" />
|
||||
</div>
|
||||
<h1 className="text-4xl font-bold mb-4">Jetzt starten</h1>
|
||||
<h1 className="text-4xl font-bold mb-4">{t('register.getStarted')}</h1>
|
||||
<p className="text-slate-300 text-lg leading-relaxed">
|
||||
Erstellen Sie ein Konto und beginnen Sie, Ihre Traumreisen zu planen.
|
||||
{t('register.subtitle')}
|
||||
</p>
|
||||
|
||||
<div className="mt-10 space-y-3 text-left">
|
||||
{[
|
||||
'✓ Unbegrenzte Reisepläne',
|
||||
'✓ Interaktive Kartenansicht',
|
||||
'✓ Orte und Kategorien verwalten',
|
||||
'✓ Reservierungen tracken',
|
||||
'✓ Packlisten erstellen',
|
||||
'✓ Fotos und Dateien speichern',
|
||||
`✓ ${t('register.feature1')}`,
|
||||
`✓ ${t('register.feature2')}`,
|
||||
`✓ ${t('register.feature3')}`,
|
||||
`✓ ${t('register.feature4')}`,
|
||||
`✓ ${t('register.feature5')}`,
|
||||
`✓ ${t('register.feature6')}`,
|
||||
].map(item => (
|
||||
<p key={item} className="text-slate-200 text-sm">{item}</p>
|
||||
))}
|
||||
@@ -77,8 +79,8 @@ export default function RegisterPage() {
|
||||
</div>
|
||||
|
||||
<div className="bg-white rounded-2xl shadow-sm border border-slate-200 p-8">
|
||||
<h2 className="text-2xl font-bold text-slate-900 mb-1">Konto erstellen</h2>
|
||||
<p className="text-slate-500 mb-8">Beginnen Sie Ihre Reiseplanung</p>
|
||||
<h2 className="text-2xl font-bold text-slate-900 mb-1">{t('register.createAccount')}</h2>
|
||||
<p className="text-slate-500 mb-8">{t('register.startPlanning')}</p>
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-5">
|
||||
{error && (
|
||||
@@ -88,7 +90,7 @@ export default function RegisterPage() {
|
||||
)}
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-1.5">Benutzername</label>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-1.5">{t('settings.username')}</label>
|
||||
<div className="relative">
|
||||
<User className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-slate-400" />
|
||||
<input
|
||||
@@ -96,7 +98,7 @@ export default function RegisterPage() {
|
||||
value={username}
|
||||
onChange={e => setUsername(e.target.value)}
|
||||
required
|
||||
placeholder="maxmustermann"
|
||||
placeholder="johndoe"
|
||||
minLength={3}
|
||||
className="w-full pl-10 pr-4 py-2.5 border border-slate-300 rounded-lg text-slate-900 placeholder-slate-400 focus:ring-2 focus:ring-slate-400 focus:border-transparent transition-all"
|
||||
/>
|
||||
@@ -104,7 +106,7 @@ export default function RegisterPage() {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-1.5">E-Mail</label>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-1.5">{t('common.email')}</label>
|
||||
<div className="relative">
|
||||
<Mail className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-slate-400" />
|
||||
<input
|
||||
@@ -112,14 +114,14 @@ export default function RegisterPage() {
|
||||
value={email}
|
||||
onChange={e => setEmail(e.target.value)}
|
||||
required
|
||||
placeholder="ihre@email.de"
|
||||
placeholder="your@email.com"
|
||||
className="w-full pl-10 pr-4 py-2.5 border border-slate-300 rounded-lg text-slate-900 placeholder-slate-400 focus:ring-2 focus:ring-slate-400 focus:border-transparent transition-all"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-1.5">Passwort</label>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-1.5">{t('common.password')}</label>
|
||||
<div className="relative">
|
||||
<Lock className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-slate-400" />
|
||||
<input
|
||||
@@ -127,7 +129,7 @@ export default function RegisterPage() {
|
||||
value={password}
|
||||
onChange={e => setPassword(e.target.value)}
|
||||
required
|
||||
placeholder="Mind. 6 Zeichen"
|
||||
placeholder={t('register.minChars')}
|
||||
className="w-full pl-10 pr-12 py-2.5 border border-slate-300 rounded-lg text-slate-900 placeholder-slate-400 focus:ring-2 focus:ring-slate-400 focus:border-transparent transition-all"
|
||||
/>
|
||||
<button
|
||||
@@ -141,7 +143,7 @@ export default function RegisterPage() {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-1.5">Passwort bestätigen</label>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-1.5">{t('register.confirmPassword')}</label>
|
||||
<div className="relative">
|
||||
<Lock className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-slate-400" />
|
||||
<input
|
||||
@@ -149,7 +151,7 @@ export default function RegisterPage() {
|
||||
value={confirmPassword}
|
||||
onChange={e => setConfirmPassword(e.target.value)}
|
||||
required
|
||||
placeholder="Passwort wiederholen"
|
||||
placeholder={t('register.repeatPassword')}
|
||||
className="w-full pl-10 pr-4 py-2.5 border border-slate-300 rounded-lg text-slate-900 placeholder-slate-400 focus:ring-2 focus:ring-slate-400 focus:border-transparent transition-all"
|
||||
/>
|
||||
</div>
|
||||
@@ -163,17 +165,17 @@ export default function RegisterPage() {
|
||||
{isLoading ? (
|
||||
<>
|
||||
<div className="w-4 h-4 border-2 border-white/30 border-t-white rounded-full animate-spin"></div>
|
||||
Registrieren...
|
||||
{t('register.registering')}
|
||||
</>
|
||||
) : 'Registrieren'}
|
||||
) : t('register.register')}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div className="mt-6 text-center">
|
||||
<p className="text-sm text-slate-500">
|
||||
Bereits ein Konto?{' '}
|
||||
{t('register.hasAccount')}{' '}
|
||||
<Link to="/login" className="text-slate-900 hover:text-slate-700 font-medium">
|
||||
Anmelden
|
||||
{t('register.signIn')}
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user