mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-21 06:11:45 +00:00
add Emil-style UI polish pass (animations, shared components, feel)
This commit is contained in:
@@ -11,6 +11,7 @@ import { getApiErrorMessage } from '../types'
|
||||
import Navbar from '../components/Layout/Navbar'
|
||||
import Modal from '../components/shared/Modal'
|
||||
import { useToast } from '../components/shared/Toast'
|
||||
import { useCountUp } from '../hooks/useCountUp'
|
||||
import CategoryManager from '../components/Admin/CategoryManager'
|
||||
import BackupPanel from '../components/Admin/BackupPanel'
|
||||
import GitHubPanel from '../components/Admin/GitHubPanel'
|
||||
@@ -161,6 +162,21 @@ function AdminNotificationsPanel({ t, toast }: { t: (k: string) => string; toast
|
||||
)
|
||||
}
|
||||
|
||||
function AdminStatCard({ label, value, icon: Icon }: { label: string; value: number; icon: React.ComponentType<{ className?: string; style?: React.CSSProperties }> }): React.ReactElement {
|
||||
const animated = useCountUp(value, 900)
|
||||
return (
|
||||
<div className="rounded-xl border p-4" style={{ background: 'var(--bg-card)', borderColor: 'var(--border-primary)' }}>
|
||||
<div className="flex items-center gap-4">
|
||||
<Icon className="w-5 h-5" style={{ color: 'var(--text-primary)' }} />
|
||||
<div>
|
||||
<p className="text-xl font-bold tabular-nums" style={{ color: 'var(--text-primary)' }}>{animated}</p>
|
||||
<p className="text-xs" style={{ color: 'var(--text-muted)' }}>{label}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default function AdminPage(): React.ReactElement {
|
||||
const { demoMode, serverTimezone } = useAuthStore()
|
||||
const { t, locale } = useTranslation()
|
||||
@@ -565,15 +581,7 @@ export default function AdminPage(): React.ReactElement {
|
||||
{ label: t('admin.stats.places'), value: stats.totalPlaces, icon: Map },
|
||||
{ label: t('admin.stats.files'), value: stats.totalFiles || 0, icon: FileText },
|
||||
].map(({ label, value, icon: Icon }) => (
|
||||
<div key={label} className="rounded-xl border p-4" style={{ background: 'var(--bg-card)', borderColor: 'var(--border-primary)' }}>
|
||||
<div className="flex items-center gap-4">
|
||||
<Icon className="w-5 h-5" style={{ color: 'var(--text-primary)' }} />
|
||||
<div>
|
||||
<p className="text-xl font-bold" style={{ color: 'var(--text-primary)' }}>{value}</p>
|
||||
<p className="text-xs" style={{ color: 'var(--text-muted)' }}>{label}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<AdminStatCard key={label} label={label} value={value} icon={Icon} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
@@ -629,7 +637,7 @@ export default function AdminPage(): React.ReactElement {
|
||||
<th className="px-5 py-3 text-right">{t('admin.table.actions')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-slate-100">
|
||||
<tbody className="divide-y divide-slate-100 trek-stagger">
|
||||
{users.map(u => (
|
||||
<tr key={u.id} className={`hover:bg-slate-50 transition-colors ${u.id === currentUser?.id ? 'bg-slate-50/60' : ''}`}>
|
||||
<td className="px-5 py-3">
|
||||
|
||||
@@ -938,7 +938,7 @@ export default function AtlasPage(): React.ReactElement {
|
||||
ref={panelRef}
|
||||
onMouseMove={handlePanelMouseMove}
|
||||
onMouseLeave={handlePanelMouseLeave}
|
||||
className="hidden md:flex flex-col absolute z-10 overflow-hidden transition-all duration-300"
|
||||
className="hidden md:flex flex-col absolute z-10 overflow-hidden transition-[width,height,transform,box-shadow] duration-300 ease-[cubic-bezier(0.23,1,0.32,1)]"
|
||||
style={{
|
||||
bottom: 16,
|
||||
left: '50%',
|
||||
|
||||
@@ -13,6 +13,7 @@ import TimezoneWidget from '../components/Dashboard/TimezoneWidget'
|
||||
import TripFormModal from '../components/Trips/TripFormModal'
|
||||
import ConfirmDialog from '../components/shared/ConfirmDialog'
|
||||
import { useToast } from '../components/shared/Toast'
|
||||
import { useCountUp } from '../hooks/useCountUp'
|
||||
import {
|
||||
Plus, Calendar, Trash2, Edit2, Map, ChevronDown, ChevronUp,
|
||||
Archive, ArchiveRestore, Clock, MapPin, Settings, X, ArrowRightLeft, Users,
|
||||
@@ -152,6 +153,28 @@ interface TripCardProps {
|
||||
dark?: boolean
|
||||
}
|
||||
|
||||
function SpotlightStats({ trip, totalDays, t }: { trip: DashboardTrip; totalDays: number; t: TripCardProps['t'] }): React.ReactElement {
|
||||
const days = useCountUp(trip.day_count || totalDays)
|
||||
const places = useCountUp(trip.place_count || 0)
|
||||
const buddies = useCountUp(trip.shared_count || 0)
|
||||
return (
|
||||
<div className="grid grid-cols-3 gap-2.5 p-3.5 bg-black/25 backdrop-blur-sm border border-white/10 rounded-2xl">
|
||||
<div className="text-center">
|
||||
<p className="text-[22px] font-extrabold tracking-[-0.02em] leading-none tabular-nums">{days}</p>
|
||||
<p className="text-[9px] uppercase tracking-[0.1em] opacity-70 font-semibold mt-1">{t('dashboard.mobile.days')}</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<p className="text-[22px] font-extrabold tracking-[-0.02em] leading-none tabular-nums">{places}</p>
|
||||
<p className="text-[9px] uppercase tracking-[0.1em] opacity-70 font-semibold mt-1">{t('dashboard.mobile.places')}</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<p className="text-[22px] font-extrabold tracking-[-0.02em] leading-none tabular-nums">{buddies}</p>
|
||||
<p className="text-[9px] uppercase tracking-[0.1em] opacity-70 font-semibold mt-1">{t('dashboard.mobile.buddies')}</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function SpotlightCard({ trip, onEdit, onCopy, onDelete, onArchive, onClick, t, locale }: TripCardProps): React.ReactElement {
|
||||
const status = getTripStatus(trip)
|
||||
const isLive = status === 'ongoing'
|
||||
@@ -173,16 +196,16 @@ function SpotlightCard({ trip, onEdit, onCopy, onDelete, onArchive, onClick, t,
|
||||
return (
|
||||
<div
|
||||
onClick={() => onClick(trip)}
|
||||
className="group relative rounded-3xl overflow-hidden cursor-pointer mb-8"
|
||||
style={{ minHeight: 340, boxShadow: '0 8px 40px rgba(0,0,0,0.13)' }}
|
||||
className="group relative rounded-3xl overflow-hidden cursor-pointer mb-8 transition-[transform,box-shadow] duration-300 ease-[cubic-bezier(0.23,1,0.32,1)] hover:-translate-y-1 hover:shadow-[0_16px_60px_rgba(0,0,0,0.22)] active:scale-[0.995]"
|
||||
style={{ minHeight: 340, boxShadow: '0 8px 40px rgba(0,0,0,0.13)', isolation: 'isolate' }}
|
||||
>
|
||||
{/* Background */}
|
||||
<div className="absolute inset-0" style={{
|
||||
<div className="absolute inset-0 overflow-hidden rounded-3xl" style={{
|
||||
background: trip.cover_image ? undefined : tripGradient(trip.id),
|
||||
}}>
|
||||
{trip.cover_image && (
|
||||
<>
|
||||
<img src={trip.cover_image} className="w-full h-full object-cover" alt="" />
|
||||
<img src={trip.cover_image} className="w-full h-full object-cover transition-transform duration-[1200ms] ease-[cubic-bezier(0.23,1,0.32,1)] group-hover:scale-[1.06]" alt="" />
|
||||
<div className="absolute inset-0" style={{ background: 'linear-gradient(180deg, rgba(0,0,0,0.2) 0%, rgba(0,0,0,0.6) 100%)' }} />
|
||||
</>
|
||||
)}
|
||||
@@ -233,7 +256,14 @@ function SpotlightCard({ trip, onEdit, onCopy, onDelete, onArchive, onClick, t,
|
||||
<span className="opacity-70">{t('dashboard.mobile.daysLeft', { count: daysLeft })}</span>
|
||||
</div>
|
||||
<div className="h-1.5 bg-white/15 rounded-full overflow-hidden">
|
||||
<div className="h-full bg-white rounded-full relative" style={{ width: `${progress}%` }}>
|
||||
<div
|
||||
className="h-full bg-white rounded-full relative"
|
||||
style={{
|
||||
width: `${progress}%`,
|
||||
animation: 'trek-progress-fill 900ms cubic-bezier(0.23,1,0.32,1) both',
|
||||
['--trek-progress-to' as string]: `${progress}%`,
|
||||
}}
|
||||
>
|
||||
<span className="absolute right-0 top-1/2 -translate-y-1/2 w-3 h-3 bg-white rounded-full shadow-[0_0_12px_rgba(255,255,255,0.9)]" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -241,20 +271,7 @@ function SpotlightCard({ trip, onEdit, onCopy, onDelete, onArchive, onClick, t,
|
||||
)}
|
||||
|
||||
{/* Stats */}
|
||||
<div className="grid grid-cols-3 gap-2.5 p-3.5 bg-black/25 backdrop-blur-sm border border-white/10 rounded-2xl">
|
||||
<div className="text-center">
|
||||
<p className="text-[22px] font-extrabold tracking-[-0.02em] leading-none">{trip.day_count || totalDays}</p>
|
||||
<p className="text-[9px] uppercase tracking-[0.1em] opacity-70 font-semibold mt-1">{t('dashboard.mobile.days')}</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<p className="text-[22px] font-extrabold tracking-[-0.02em] leading-none">{trip.place_count || 0}</p>
|
||||
<p className="text-[9px] uppercase tracking-[0.1em] opacity-70 font-semibold mt-1">{t('dashboard.mobile.places')}</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<p className="text-[22px] font-extrabold tracking-[-0.02em] leading-none">{trip.shared_count || 0}</p>
|
||||
<p className="text-[9px] uppercase tracking-[0.1em] opacity-70 font-semibold mt-1">{t('dashboard.mobile.buddies')}</p>
|
||||
</div>
|
||||
</div>
|
||||
<SpotlightStats trip={trip} totalDays={totalDays} t={t} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
@@ -278,13 +295,13 @@ function MobileTripCard({ trip, onEdit, onCopy, onDelete, onArchive, onClick, t,
|
||||
return (
|
||||
<div
|
||||
onClick={() => onClick?.(trip)}
|
||||
className="rounded-2xl border border-zinc-200 dark:border-zinc-700 overflow-hidden cursor-pointer transition-all hover:-translate-y-0.5 hover:shadow-md"
|
||||
style={{ background: 'var(--bg-card)' }}
|
||||
className="group rounded-2xl border border-zinc-200 dark:border-zinc-700 overflow-hidden cursor-pointer transition-[transform,box-shadow,border-color] duration-200 ease-[cubic-bezier(0.23,1,0.32,1)] hover:-translate-y-0.5 hover:shadow-md"
|
||||
style={{ background: 'var(--bg-card)', isolation: 'isolate' }}
|
||||
>
|
||||
{/* Cover */}
|
||||
<div className="relative h-[120px] overflow-hidden" style={{ background: trip.cover_image ? undefined : tripGradient(trip.id) }}>
|
||||
{trip.cover_image && (
|
||||
<img src={trip.cover_image} className="absolute inset-0 w-full h-full object-cover" alt="" />
|
||||
<img src={trip.cover_image} className="absolute inset-0 w-full h-full object-cover transition-transform duration-[800ms] ease-[cubic-bezier(0.23,1,0.32,1)] group-hover:scale-[1.08]" alt="" />
|
||||
)}
|
||||
<div className="absolute inset-0" style={{ background: 'linear-gradient(180deg, transparent 30%, rgba(0,0,0,0.5) 100%)' }} />
|
||||
|
||||
@@ -370,13 +387,13 @@ function TripCard({ trip, onEdit, onCopy, onDelete, onArchive, onClick, t, local
|
||||
return (
|
||||
<div
|
||||
onClick={() => onClick(trip)}
|
||||
className="group rounded-2xl border border-zinc-200 dark:border-zinc-700 overflow-hidden cursor-pointer transition-all hover:-translate-y-0.5 hover:shadow-lg hover:border-zinc-300 dark:hover:border-zinc-600"
|
||||
style={{ background: 'var(--bg-card)' }}
|
||||
className="group rounded-2xl border border-zinc-200 dark:border-zinc-700 overflow-hidden cursor-pointer transition-[transform,box-shadow,border-color] duration-200 ease-[cubic-bezier(0.23,1,0.32,1)] hover:-translate-y-0.5 hover:shadow-lg hover:border-zinc-300 dark:hover:border-zinc-600"
|
||||
style={{ background: 'var(--bg-card)', isolation: 'isolate' }}
|
||||
>
|
||||
{/* Cover */}
|
||||
<div className="relative h-[140px] overflow-hidden" style={{ background: trip.cover_image ? undefined : tripGradient(trip.id) }}>
|
||||
{trip.cover_image && (
|
||||
<img src={trip.cover_image} className="absolute inset-0 w-full h-full object-cover" alt="" />
|
||||
<img src={trip.cover_image} className="absolute inset-0 w-full h-full object-cover transition-transform duration-[800ms] ease-[cubic-bezier(0.23,1,0.32,1)] group-hover:scale-[1.08]" alt="" />
|
||||
)}
|
||||
<div className="absolute inset-0" style={{ background: 'linear-gradient(180deg, transparent 30%, rgba(0,0,0,0.55) 100%)' }} />
|
||||
|
||||
@@ -658,11 +675,14 @@ function IconBtn({ onClick, title, danger, loading, children }: { onClick: () =>
|
||||
// ── Skeleton ─────────────────────────────────────────────────────────────────
|
||||
function SkeletonCard(): React.ReactElement {
|
||||
return (
|
||||
<div style={{ background: 'white', borderRadius: 16, overflow: 'hidden', border: '1px solid #f3f4f6' }}>
|
||||
<div style={{ height: 120, background: '#f3f4f6', animation: 'pulse 1.5s ease-in-out infinite' }} />
|
||||
<div style={{ padding: '12px 14px 14px' }}>
|
||||
<div style={{ height: 14, background: '#f3f4f6', borderRadius: 6, marginBottom: 8, width: '70%' }} />
|
||||
<div style={{ height: 11, background: '#f3f4f6', borderRadius: 6, width: '50%' }} />
|
||||
<div
|
||||
className="rounded-2xl border border-zinc-200 dark:border-zinc-700 overflow-hidden"
|
||||
style={{ background: 'var(--bg-card)' }}
|
||||
>
|
||||
<div className="trek-skeleton" style={{ height: 120, borderRadius: 0 }} />
|
||||
<div style={{ padding: '12px 14px 14px', display: 'flex', flexDirection: 'column', gap: 6 }}>
|
||||
<div className="trek-skeleton" style={{ height: 14, width: '70%' }} />
|
||||
<div className="trek-skeleton" style={{ height: 11, width: '50%' }} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
@@ -958,10 +978,8 @@ export default function DashboardPage(): React.ReactElement {
|
||||
padding: '9px 14px', borderRadius: 10, fontSize: 13, fontWeight: 500,
|
||||
background: 'var(--accent)', color: 'var(--accent-text)', flexShrink: 0,
|
||||
marginLeft: 2,
|
||||
transition: 'opacity 0.15s ease',
|
||||
}}
|
||||
onMouseEnter={e => e.currentTarget.style.opacity = '0.88'}
|
||||
onMouseLeave={e => e.currentTarget.style.opacity = '1'}
|
||||
className="hover:opacity-[0.88]"
|
||||
>
|
||||
<Plus size={14} strokeWidth={2.5} /> {t('dashboard.newTrip')}
|
||||
</button>
|
||||
@@ -1004,7 +1022,7 @@ export default function DashboardPage(): React.ReactElement {
|
||||
{/* Loading skeletons */}
|
||||
{isLoading && (
|
||||
<>
|
||||
<div style={{ height: 260, background: '#e5e7eb', borderRadius: 20, marginBottom: 32, animation: 'pulse 1.5s ease-in-out infinite' }} />
|
||||
<div className="trek-skeleton" style={{ height: 260, borderRadius: 24, marginBottom: 32 }} />
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(280px, 1fr))', gap: 16 }}>
|
||||
{[1, 2, 3].map(i => <SkeletonCard key={i} />)}
|
||||
</div>
|
||||
@@ -1070,7 +1088,7 @@ export default function DashboardPage(): React.ReactElement {
|
||||
{/* Trips — desktop grid or list */}
|
||||
{!isLoading && (viewMode === 'grid' ? rest : trips).length > 0 && (
|
||||
viewMode === 'grid' ? (
|
||||
<div className="trip-grid hidden md:grid" style={{ gap: 16, marginBottom: 40 }}>
|
||||
<div className="trip-grid hidden md:grid trek-stagger" style={{ gap: 16, marginBottom: 40 }}>
|
||||
{rest.map(trip => (
|
||||
<TripCard
|
||||
key={trip.id}
|
||||
@@ -1085,7 +1103,7 @@ export default function DashboardPage(): React.ReactElement {
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="hidden md:flex" style={{ flexDirection: 'column', gap: 8, marginBottom: 40 }}>
|
||||
<div className="hidden md:flex trek-stagger" style={{ flexDirection: 'column', gap: 8, marginBottom: 40 }}>
|
||||
{trips.map(trip => (
|
||||
<TripListItem
|
||||
key={trip.id}
|
||||
|
||||
@@ -437,7 +437,7 @@ export default function JourneyDetailPage() {
|
||||
const locations = [...new Set(entries.map(e => e.location_name).filter(Boolean))]
|
||||
|
||||
return (
|
||||
<div key={date} className="flex flex-col gap-3">
|
||||
<div key={date} className="flex flex-col gap-3 trek-stagger">
|
||||
<div className="sticky top-0 md:top-[68px] z-[5] bg-white/95 dark:bg-zinc-900/95 backdrop-blur border-y md:border border-zinc-200 dark:border-zinc-700 rounded-none md:rounded-xl -mx-4 md:mx-0 px-4 py-3.5 flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-8 h-8 rounded-lg bg-zinc-900 dark:bg-white text-white dark:text-zinc-900 flex items-center justify-center text-[13px] font-bold">
|
||||
@@ -1251,7 +1251,7 @@ function EntryCard({ entry, onEdit, onDelete, onPhotoClick }: {
|
||||
const hasProscons = prosArr.length > 0 || consArr.length > 0
|
||||
|
||||
return (
|
||||
<div className="bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-700 rounded-2xl overflow-hidden transition-all hover:border-zinc-400 dark:hover:border-zinc-500 hover:shadow-sm">
|
||||
<div className="bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-700 rounded-2xl overflow-hidden transition-[border-color,box-shadow] duration-200 ease-[cubic-bezier(0.23,1,0.32,1)] hover:border-zinc-400 dark:hover:border-zinc-500 hover:shadow-sm">
|
||||
|
||||
{/* Hero area: photos with title overlay */}
|
||||
{photos.length > 0 ? (
|
||||
@@ -1371,7 +1371,7 @@ function SkeletonCard({ entry, onClick }: { entry: JourneyEntry; onClick: () =>
|
||||
return (
|
||||
<div
|
||||
onClick={onClick}
|
||||
className="bg-white dark:bg-zinc-900 border border-dashed border-zinc-200 dark:border-zinc-700 rounded-xl px-4 py-3.5 flex items-center gap-3 transition-all hover:border-solid hover:border-zinc-400 dark:hover:border-zinc-500 cursor-pointer"
|
||||
className="bg-white dark:bg-zinc-900 border border-dashed border-zinc-200 dark:border-zinc-700 rounded-xl px-4 py-3.5 flex items-center gap-3 transition-[border-color,border-style] duration-150 ease-[cubic-bezier(0.23,1,0.32,1)] hover:border-solid hover:border-zinc-400 dark:hover:border-zinc-500 cursor-pointer"
|
||||
>
|
||||
<div className="w-9 h-9 rounded-lg bg-zinc-100 dark:bg-zinc-800 flex items-center justify-center text-zinc-500 flex-shrink-0">
|
||||
<MapPin size={14} />
|
||||
@@ -1395,7 +1395,7 @@ function CheckinCard({ entry, onClick }: { entry: JourneyEntry; onClick: () => v
|
||||
return (
|
||||
<div
|
||||
onClick={onClick}
|
||||
className="bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-700 rounded-xl px-3.5 py-2.5 flex items-center gap-2.5 transition-all hover:border-zinc-400 dark:hover:border-zinc-500 cursor-pointer"
|
||||
className="bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-700 rounded-xl px-3.5 py-2.5 flex items-center gap-2.5 transition-colors duration-150 ease-[cubic-bezier(0.23,1,0.32,1)] hover:border-zinc-400 dark:hover:border-zinc-500 cursor-pointer"
|
||||
>
|
||||
<div className="w-7 h-7 rounded-lg bg-indigo-50 dark:bg-indigo-900/30 text-indigo-600 dark:text-indigo-400 flex items-center justify-center flex-shrink-0">
|
||||
<MapPin size={13} />
|
||||
|
||||
@@ -238,7 +238,7 @@ export default function JourneyPage() {
|
||||
|
||||
<div
|
||||
onClick={() => navigate(`/journey/${activeJourney.id}`)}
|
||||
className="relative rounded-3xl overflow-hidden cursor-pointer transition-all duration-300 hover:-translate-y-1 hover:shadow-xl h-[340px] md:h-[400px]"
|
||||
className="relative rounded-3xl overflow-hidden cursor-pointer transition-[transform,box-shadow] duration-300 ease-[cubic-bezier(0.23,1,0.32,1)] hover:-translate-y-1 hover:shadow-xl h-[340px] md:h-[400px]"
|
||||
style={{ background: pickGradient(activeJourney.id) }}
|
||||
>
|
||||
{/* Cover image */}
|
||||
@@ -333,9 +333,9 @@ export default function JourneyPage() {
|
||||
{/* Create card */}
|
||||
<button
|
||||
onClick={() => openCreateModal()}
|
||||
className="group min-h-[320px] rounded-2xl border-[1.5px] border-dashed border-zinc-200 dark:border-zinc-700 bg-white dark:bg-zinc-900 flex flex-col items-center justify-center gap-2.5 hover:border-zinc-400 hover:bg-zinc-50 dark:hover:bg-zinc-800 transition-all cursor-pointer hover:-translate-y-0.5"
|
||||
className="group min-h-[320px] rounded-2xl border-[1.5px] border-dashed border-zinc-200 dark:border-zinc-700 bg-white dark:bg-zinc-900 flex flex-col items-center justify-center gap-2.5 hover:border-zinc-400 hover:bg-zinc-50 dark:hover:bg-zinc-800 transition-[border-color,background-color,transform] duration-200 ease-[cubic-bezier(0.23,1,0.32,1)] cursor-pointer hover:-translate-y-0.5"
|
||||
>
|
||||
<div className="w-14 h-14 rounded-full bg-zinc-100 dark:bg-zinc-800 flex items-center justify-center text-zinc-400 group-hover:bg-white dark:group-hover:bg-zinc-700 transition-all group-hover:rotate-90 duration-300">
|
||||
<div className="w-14 h-14 rounded-full bg-zinc-100 dark:bg-zinc-800 flex items-center justify-center text-zinc-400 group-hover:bg-white dark:group-hover:bg-zinc-700 transition-[background-color,transform] group-hover:rotate-90 duration-300 ease-[cubic-bezier(0.23,1,0.32,1)]">
|
||||
<Plus size={22} />
|
||||
</div>
|
||||
<span className="text-[14px] font-semibold text-zinc-700 dark:text-zinc-300">{t("journey.frontpage.createNew")}</span>
|
||||
@@ -394,7 +394,7 @@ export default function JourneyPage() {
|
||||
return next
|
||||
})
|
||||
}}
|
||||
className={`flex items-center gap-3 p-3 rounded-xl border cursor-pointer transition-all ${
|
||||
className={`flex items-center gap-3 p-3 rounded-xl border cursor-pointer transition-[border-color,background-color] duration-150 ease-[cubic-bezier(0.23,1,0.32,1)] ${
|
||||
selected
|
||||
? 'border-zinc-900 dark:border-zinc-400 bg-zinc-50 dark:bg-zinc-800'
|
||||
: 'border-zinc-200 dark:border-zinc-700 hover:border-zinc-400 dark:hover:border-zinc-500'
|
||||
@@ -468,7 +468,7 @@ function JourneyCard({ journey, onClick }: { journey: Journey & { entry_count?:
|
||||
return (
|
||||
<div
|
||||
onClick={onClick}
|
||||
className="rounded-2xl border border-zinc-200 dark:border-zinc-700 bg-white dark:bg-zinc-900 overflow-hidden cursor-pointer transition-all duration-250 hover:border-zinc-400 hover:-translate-y-1 hover:shadow-[0_20px_40px_rgba(0,0,0,0.06)] flex flex-col"
|
||||
className="rounded-2xl border border-zinc-200 dark:border-zinc-700 bg-white dark:bg-zinc-900 overflow-hidden cursor-pointer transition-[transform,box-shadow,border-color] duration-250 ease-[cubic-bezier(0.23,1,0.32,1)] hover:border-zinc-400 hover:-translate-y-1 hover:shadow-[0_20px_40px_rgba(0,0,0,0.06)] flex flex-col"
|
||||
>
|
||||
{/* Cover */}
|
||||
<div className="h-[170px] relative overflow-hidden" style={{ background: pickGradient(j.id) }}>
|
||||
|
||||
@@ -574,7 +574,7 @@ export default function LoginPage(): React.ReactElement {
|
||||
{ Icon: FolderOpen, label: t('login.features.files'), desc: t('login.features.filesDesc') },
|
||||
{ Icon: Route, label: t('login.features.routes'), desc: t('login.features.routesDesc') },
|
||||
].map(({ Icon, label, desc }) => (
|
||||
<div key={label} style={{ background: 'rgba(255,255,255,0.04)', borderRadius: 14, padding: '14px 12px', border: '1px solid rgba(255,255,255,0.06)', textAlign: 'left', transition: 'all 0.2s' }}
|
||||
<div key={label} style={{ background: 'rgba(255,255,255,0.04)', borderRadius: 14, padding: '14px 12px', border: '1px solid rgba(255,255,255,0.06)', textAlign: 'left', transition: 'background 200ms cubic-bezier(0.23,1,0.32,1), border-color 200ms cubic-bezier(0.23,1,0.32,1)' }}
|
||||
onMouseEnter={(e: React.MouseEvent<HTMLDivElement>) => { e.currentTarget.style.background = 'rgba(255,255,255,0.08)'; e.currentTarget.style.borderColor = 'rgba(255,255,255,0.12)' }}
|
||||
onMouseLeave={(e: React.MouseEvent<HTMLDivElement>) => { e.currentTarget.style.background = 'rgba(255,255,255,0.04)'; e.currentTarget.style.borderColor = 'rgba(255,255,255,0.06)' }}>
|
||||
<Icon size={17} style={{ color: 'rgba(255,255,255,0.7)', marginBottom: 7 }} />
|
||||
@@ -619,7 +619,7 @@ export default function LoginPage(): React.ReactElement {
|
||||
border: 'none', borderRadius: 12,
|
||||
fontSize: 14, fontWeight: 700, cursor: 'pointer',
|
||||
fontFamily: 'inherit', display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8,
|
||||
textDecoration: 'none', transition: 'all 0.15s',
|
||||
textDecoration: 'none', transition: 'background 180ms cubic-bezier(0.23,1,0.32,1)',
|
||||
boxSizing: 'border-box',
|
||||
}}
|
||||
onMouseEnter={(e: React.MouseEvent<HTMLAnchorElement>) => { e.currentTarget.style.background = '#1f2937' }}
|
||||
@@ -764,9 +764,21 @@ export default function LoginPage(): React.ReactElement {
|
||||
/>
|
||||
<button type="button" onClick={() => setShowPassword(v => !v)} style={{
|
||||
position: 'absolute', right: 12, top: '50%', transform: 'translateY(-50%)',
|
||||
background: 'none', border: 'none', cursor: 'pointer', padding: 2, display: 'flex', color: '#9ca3af',
|
||||
background: 'none', border: 'none', cursor: 'pointer', padding: 2, color: '#9ca3af',
|
||||
width: 22, height: 22,
|
||||
}}>
|
||||
{showPassword ? <EyeOff size={16} /> : <Eye size={16} />}
|
||||
<Eye size={16} style={{
|
||||
position: 'absolute', inset: 3,
|
||||
opacity: showPassword ? 0 : 1,
|
||||
transform: showPassword ? 'scale(0.7) rotate(-20deg)' : 'scale(1) rotate(0)',
|
||||
transition: 'opacity 180ms cubic-bezier(0.23,1,0.32,1), transform 180ms cubic-bezier(0.23,1,0.32,1)',
|
||||
}} />
|
||||
<EyeOff size={16} style={{
|
||||
position: 'absolute', inset: 3,
|
||||
opacity: showPassword ? 1 : 0,
|
||||
transform: showPassword ? 'scale(1) rotate(0)' : 'scale(0.7) rotate(20deg)',
|
||||
transition: 'opacity 180ms cubic-bezier(0.23,1,0.32,1), transform 180ms cubic-bezier(0.23,1,0.32,1)',
|
||||
}} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -816,7 +828,7 @@ export default function LoginPage(): React.ReactElement {
|
||||
border: '1px solid #d1d5db', borderRadius: 12,
|
||||
fontSize: 14, fontWeight: 600, cursor: 'pointer',
|
||||
fontFamily: 'inherit', display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8,
|
||||
textDecoration: 'none', transition: 'all 0.15s',
|
||||
textDecoration: 'none', transition: 'background 180ms cubic-bezier(0.23,1,0.32,1), border-color 180ms cubic-bezier(0.23,1,0.32,1)',
|
||||
boxSizing: 'border-box',
|
||||
}}
|
||||
onMouseEnter={(e: React.MouseEvent<HTMLAnchorElement>) => { e.currentTarget.style.background = '#f9fafb'; e.currentTarget.style.borderColor = '#9ca3af' }}
|
||||
@@ -837,7 +849,7 @@ export default function LoginPage(): React.ReactElement {
|
||||
color: '#451a03', border: 'none', borderRadius: 14,
|
||||
fontSize: 15, fontWeight: 700, cursor: isLoading ? 'default' : 'pointer',
|
||||
fontFamily: 'inherit', display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 10,
|
||||
opacity: isLoading ? 0.7 : 1, transition: 'all 0.2s',
|
||||
opacity: isLoading ? 0.7 : 1, transition: 'transform 200ms cubic-bezier(0.23,1,0.32,1), box-shadow 200ms cubic-bezier(0.23,1,0.32,1), opacity 200ms cubic-bezier(0.23,1,0.32,1)',
|
||||
boxShadow: '0 2px 12px rgba(245, 158, 11, 0.3)',
|
||||
}}
|
||||
onMouseEnter={(e: React.MouseEvent<HTMLButtonElement>) => { if (!isLoading) e.currentTarget.style.transform = 'translateY(-1px)'; e.currentTarget.style.boxShadow = '0 4px 16px rgba(245, 158, 11, 0.4)' }}
|
||||
|
||||
@@ -100,7 +100,7 @@ export default function RegisterPage(): React.ReactElement {
|
||||
required
|
||||
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"
|
||||
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-[border-color,box-shadow] duration-150 ease-[cubic-bezier(0.23,1,0.32,1)]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -115,7 +115,7 @@ export default function RegisterPage(): React.ReactElement {
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setEmail(e.target.value)}
|
||||
required
|
||||
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"
|
||||
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-[border-color,box-shadow] duration-150 ease-[cubic-bezier(0.23,1,0.32,1)]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -130,7 +130,7 @@ export default function RegisterPage(): React.ReactElement {
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setPassword(e.target.value)}
|
||||
required
|
||||
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"
|
||||
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-[border-color,box-shadow] duration-150 ease-[cubic-bezier(0.23,1,0.32,1)]"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
@@ -152,7 +152,7 @@ export default function RegisterPage(): React.ReactElement {
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setConfirmPassword(e.target.value)}
|
||||
required
|
||||
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"
|
||||
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-[border-color,box-shadow] duration-150 ease-[cubic-bezier(0.23,1,0.32,1)]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -12,12 +12,14 @@ import PlaceInspector from '../components/Planner/PlaceInspector'
|
||||
import DayDetailPanel from '../components/Planner/DayDetailPanel'
|
||||
import PlaceFormModal from '../components/Planner/PlaceFormModal'
|
||||
import TripFormModal from '../components/Trips/TripFormModal'
|
||||
import SlidingTabs from '../components/shared/SlidingTabs'
|
||||
import TripMembersModal from '../components/Trips/TripMembersModal'
|
||||
import { ReservationModal } from '../components/Planner/ReservationModal'
|
||||
import { TransportModal } from '../components/Planner/TransportModal'
|
||||
// MemoriesPanel moved to Journey addon
|
||||
import ReservationsPanel from '../components/Planner/ReservationsPanel'
|
||||
import PackingListPanel from '../components/Packing/PackingListPanel'
|
||||
import ApplyTemplateButton from '../components/Packing/ApplyTemplateButton'
|
||||
import TodoListPanel from '../components/Todo/TodoListPanel'
|
||||
import FileManager from '../components/Files/FileManager'
|
||||
import BudgetPanel from '../components/Budget/BudgetPanel'
|
||||
@@ -37,7 +39,7 @@ import { useRouteCalculation } from '../hooks/useRouteCalculation'
|
||||
import { usePlaceSelection } from '../hooks/usePlaceSelection'
|
||||
import { usePlannerHistory } from '../hooks/usePlannerHistory'
|
||||
import type { Accommodation, TripMember, Day, Place, Reservation, PackingItem, TodoItem } from '../types'
|
||||
import { ListTodo, Upload, Plus } from 'lucide-react'
|
||||
import { ListTodo, Upload, Plus, Trash2, FolderPlus } from 'lucide-react'
|
||||
|
||||
function ListsContainer({ tripId, packingItems, todoItems }: { tripId: number; packingItems: PackingItem[]; todoItems: TodoItem[] }) {
|
||||
const [subTab, setSubTab] = useState<'packing' | 'todo'>(() => {
|
||||
@@ -45,6 +47,8 @@ function ListsContainer({ tripId, packingItems, todoItems }: { tripId: number; p
|
||||
})
|
||||
const setSubTabPersist = (tab: 'packing' | 'todo') => { setSubTab(tab); sessionStorage.setItem(`trip-lists-subtab-${tripId}`, tab) }
|
||||
const [importPackingSignal, setImportPackingSignal] = useState(0)
|
||||
const [clearCheckedSignal, setClearCheckedSignal] = useState(0)
|
||||
const [saveTemplateSignal, setSaveTemplateSignal] = useState(0)
|
||||
const [addTodoSignal, setAddTodoSignal] = useState(0)
|
||||
const { t } = useTranslation()
|
||||
|
||||
@@ -79,7 +83,7 @@ function ListsContainer({ tripId, packingItems, todoItems }: { tripId: number; p
|
||||
color: active ? 'var(--text-primary)' : 'var(--text-muted)',
|
||||
fontWeight: active ? 500 : 400,
|
||||
boxShadow: active ? '0 1px 2px rgba(0,0,0,0.06)' : 'none',
|
||||
transition: 'all 0.15s ease',
|
||||
transition: 'background 180ms cubic-bezier(0.23,1,0.32,1), color 180ms cubic-bezier(0.23,1,0.32,1), box-shadow 180ms cubic-bezier(0.23,1,0.32,1)',
|
||||
}}
|
||||
>
|
||||
<Icon size={13} style={{ color: active ? 'var(--text-primary)' : 'var(--text-faint)' }} />
|
||||
@@ -95,33 +99,58 @@ function ListsContainer({ tripId, packingItems, todoItems }: { tripId: number; p
|
||||
})}
|
||||
</div>
|
||||
|
||||
{subTab === 'packing' && (
|
||||
<button onClick={() => setImportPackingSignal(s => s + 1)} style={{
|
||||
{subTab === 'packing' && (() => {
|
||||
const packingAbgehakt = packingItems.filter(i => i.checked).length
|
||||
const sharedBtnClass = 'inline-flex items-center gap-1.5 px-2.5 sm:px-[14px] py-[7px] sm:py-[9px] hover:opacity-[0.88]'
|
||||
const sharedBtnStyle: React.CSSProperties = {
|
||||
appearance: 'none', border: 'none', cursor: 'pointer', fontFamily: 'inherit',
|
||||
display: 'inline-flex', alignItems: 'center', gap: 6,
|
||||
padding: '9px 14px', borderRadius: 10, fontSize: 13, fontWeight: 500,
|
||||
background: 'var(--accent)', color: 'var(--accent-text)', flexShrink: 0,
|
||||
marginLeft: 'auto',
|
||||
transition: 'opacity 0.15s ease',
|
||||
}}
|
||||
onMouseEnter={e => e.currentTarget.style.opacity = '0.88'}
|
||||
onMouseLeave={e => e.currentTarget.style.opacity = '1'}
|
||||
>
|
||||
<Upload size={14} strokeWidth={2.5} />
|
||||
<span className="hidden sm:inline">{t('packing.import')}</span>
|
||||
</button>
|
||||
)}
|
||||
borderRadius: 10, fontSize: 13, fontWeight: 500,
|
||||
}
|
||||
return (
|
||||
<div style={{ display: 'flex', gap: 6, flexShrink: 0, marginLeft: 'auto', flexWrap: 'wrap' }}>
|
||||
{packingAbgehakt > 0 && (
|
||||
<button onClick={() => setClearCheckedSignal(s => s + 1)}
|
||||
className={`hidden sm:inline-flex items-center gap-1.5 px-[14px] py-[9px] hover:opacity-[0.88]`}
|
||||
style={{ ...sharedBtnStyle, background: 'rgba(239,68,68,0.14)', color: '#ef4444' }}
|
||||
>
|
||||
<Trash2 size={14} strokeWidth={2.5} />
|
||||
<span>{t('packing.clearChecked', { count: packingAbgehakt })}</span>
|
||||
</button>
|
||||
)}
|
||||
<ApplyTemplateButton
|
||||
tripId={tripId}
|
||||
className={sharedBtnClass}
|
||||
style={{ ...sharedBtnStyle, background: 'var(--accent)', color: 'var(--accent-text)' }}
|
||||
/>
|
||||
{packingItems.length > 0 && (
|
||||
<button onClick={() => setSaveTemplateSignal(s => s + 1)}
|
||||
className={sharedBtnClass}
|
||||
style={{ ...sharedBtnStyle, background: 'var(--accent)', color: 'var(--accent-text)' }}
|
||||
>
|
||||
<FolderPlus size={14} strokeWidth={2.5} />
|
||||
<span className="hidden sm:inline">{t('packing.saveAsTemplate')}</span>
|
||||
</button>
|
||||
)}
|
||||
<button onClick={() => setImportPackingSignal(s => s + 1)}
|
||||
className={sharedBtnClass}
|
||||
style={{ ...sharedBtnStyle, background: 'var(--accent)', color: 'var(--accent-text)' }}
|
||||
>
|
||||
<Upload size={14} strokeWidth={2.5} />
|
||||
<span className="hidden sm:inline">{t('packing.import')}</span>
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
})()}
|
||||
{subTab === 'todo' && (
|
||||
<button onClick={() => setAddTodoSignal(s => s + 1)} style={{
|
||||
appearance: 'none', border: 'none', cursor: 'pointer', fontFamily: 'inherit',
|
||||
display: 'inline-flex', alignItems: 'center', gap: 6,
|
||||
padding: '9px 14px', borderRadius: 10, fontSize: 13, fontWeight: 500,
|
||||
background: 'var(--accent)', color: 'var(--accent-text)', flexShrink: 0,
|
||||
marginLeft: 'auto',
|
||||
transition: 'opacity 0.15s ease',
|
||||
}}
|
||||
onMouseEnter={e => e.currentTarget.style.opacity = '0.88'}
|
||||
onMouseLeave={e => e.currentTarget.style.opacity = '1'}
|
||||
<button onClick={() => setAddTodoSignal(s => s + 1)}
|
||||
className="hover:opacity-[0.88]"
|
||||
style={{
|
||||
appearance: 'none', border: 'none', cursor: 'pointer', fontFamily: 'inherit',
|
||||
display: 'inline-flex', alignItems: 'center', gap: 6,
|
||||
padding: '9px 14px', borderRadius: 10, fontSize: 13, fontWeight: 500,
|
||||
background: 'var(--accent)', color: 'var(--accent-text)', flexShrink: 0,
|
||||
marginLeft: 'auto',
|
||||
}}
|
||||
>
|
||||
<Plus size={14} strokeWidth={2.5} />
|
||||
<span className="hidden sm:inline">{t('todo.addItem')}</span>
|
||||
@@ -130,7 +159,7 @@ function ListsContainer({ tripId, packingItems, todoItems }: { tripId: number; p
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ padding: '16px 28px 0' }} className="max-md:!px-4">
|
||||
{subTab === 'packing' && <PackingListPanel tripId={tripId} items={packingItems} openImportSignal={importPackingSignal} inlineHeader={false} />}
|
||||
{subTab === 'packing' && <PackingListPanel tripId={tripId} items={packingItems} openImportSignal={importPackingSignal} clearCheckedSignal={clearCheckedSignal} saveTemplateSignal={saveTemplateSignal} inlineHeader={false} />}
|
||||
{subTab === 'todo' && <TodoListPanel tripId={tripId} items={todoItems} addItemSignal={addTodoSignal} />}
|
||||
</div>
|
||||
</div>
|
||||
@@ -720,34 +749,17 @@ export default function TripPlannerPage(): React.ReactElement | null {
|
||||
WebkitBackdropFilter: 'blur(16px)',
|
||||
borderBottom: '1px solid var(--border-faint)',
|
||||
height: 44,
|
||||
overflowX: 'auto', scrollbarWidth: 'none', msOverflowStyle: 'none',
|
||||
gap: 2,
|
||||
}}>
|
||||
{TRIP_TABS.map(tab => {
|
||||
const isActive = activeTab === tab.id
|
||||
const TabIcon = tab.icon
|
||||
return (
|
||||
<button
|
||||
key={tab.id}
|
||||
onClick={() => handleTabChange(tab.id)}
|
||||
title={tab.label}
|
||||
style={{
|
||||
flexShrink: 0,
|
||||
padding: '5px 14px', borderRadius: 20, border: 'none', cursor: 'pointer',
|
||||
fontSize: 13, fontWeight: isActive ? 600 : 400,
|
||||
background: isActive ? 'var(--accent)' : 'transparent',
|
||||
color: isActive ? 'var(--accent-text)' : 'var(--text-muted)',
|
||||
fontFamily: 'inherit', transition: 'all 0.15s',
|
||||
display: 'flex', alignItems: 'center', gap: 5,
|
||||
}}
|
||||
onMouseEnter={e => { if (!isActive) e.currentTarget.style.background = 'var(--bg-hover)'; e.currentTarget.style.color = isActive ? 'var(--accent-text)' : 'var(--text-primary)' }}
|
||||
onMouseLeave={e => { if (!isActive) e.currentTarget.style.background = 'transparent'; e.currentTarget.style.color = isActive ? 'var(--accent-text)' : 'var(--text-muted)' }}
|
||||
>
|
||||
{TabIcon && <><TabIcon size={20} className="sm:hidden" /><TabIcon size={15} className="hidden sm:block" /></>}
|
||||
<span className="hidden sm:inline">{tab.shortLabel || tab.label}</span>
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
<SlidingTabs
|
||||
tabs={TRIP_TABS.map(tab => ({
|
||||
id: tab.id,
|
||||
label: <span className="hidden sm:inline">{tab.shortLabel || tab.label}</span>,
|
||||
title: tab.label,
|
||||
icon: tab.icon,
|
||||
}))}
|
||||
activeTab={activeTab}
|
||||
onChange={handleTabChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Offset by navbar + tab bar (44px) */}
|
||||
|
||||
@@ -92,7 +92,7 @@ export default function VacayPage(): React.ReactElement {
|
||||
<div className="grid grid-cols-4 gap-1">
|
||||
{years.map(y => (
|
||||
<div key={y} onClick={() => setSelectedYear(y)}
|
||||
className="group relative py-1.5 rounded-lg text-xs font-medium transition-all text-center cursor-pointer"
|
||||
className="group relative py-1.5 rounded-lg text-xs font-medium transition-[background-color,color] duration-150 ease-[cubic-bezier(0.23,1,0.32,1)] text-center cursor-pointer"
|
||||
style={{
|
||||
background: y === selectedYear ? 'var(--text-primary)' : 'var(--bg-secondary)',
|
||||
color: y === selectedYear ? 'var(--bg-card)' : 'var(--text-muted)',
|
||||
@@ -262,8 +262,8 @@ export default function VacayPage(): React.ReactElement {
|
||||
<div className="fixed inset-0 flex items-center justify-center px-4"
|
||||
style={{ zIndex: 99995, backgroundColor: 'rgba(0,0,0,0.7)', backdropFilter: 'blur(8px)' }}>
|
||||
{incomingInvites.map(inv => (
|
||||
<div key={inv.id} className="w-full max-w-md rounded-2xl shadow-2xl overflow-hidden"
|
||||
style={{ background: 'var(--bg-card)', animation: 'modalIn 0.25s ease-out' }}>
|
||||
<div key={inv.id} className="trek-modal-enter w-full max-w-md rounded-2xl shadow-2xl overflow-hidden"
|
||||
style={{ background: 'var(--bg-card)' }}>
|
||||
<div className="px-6 pt-6 pb-4 text-center">
|
||||
<div className="w-14 h-14 rounded-full mx-auto mb-4 flex items-center justify-center text-lg font-bold"
|
||||
style={{ background: 'var(--bg-secondary)', color: 'var(--text-primary)' }}>
|
||||
|
||||
Reference in New Issue
Block a user