From 9614a5cf841e2228c5170d1553cdb41889a276ed Mon Sep 17 00:00:00 2001 From: Maurice Date: Sun, 31 May 2026 17:15:54 +0200 Subject: [PATCH] Migrate static theme inline styles to Tailwind utilities and extract page sub-components Replace the static, color-only inline `style={{ ... 'var(--bg-primary)' ... }}` props with the new semantic Tailwind utilities (bg-surface, text-content, border-edge, ...) wherever the result is byte-identical; dynamic/conditional theme styles and hardcoded status colors are left inline. Extract the Atlas country-search autocomplete, the Admin update banner, and two Journey dialogs into their own presentational components to shrink the oversized page files, keeping behaviour and markup identical. --- client/src/components/Admin/AddonManager.tsx | 61 ++--- client/src/components/Admin/GitHubPanel.tsx | 105 ++++--- client/src/components/Budget/BudgetPanel.tsx | 25 +- client/src/components/Collab/CollabChat.tsx | 20 +- client/src/components/Collab/CollabNotes.tsx | 41 ++- client/src/components/Collab/CollabPolls.tsx | 35 ++- .../Journey/ContributorInviteDialog.tsx | 136 +++++++++ .../Journey/JourneyShareSection.tsx | 112 ++++++++ .../Layout/InAppNotificationBell.tsx | 28 +- client/src/components/Layout/Navbar.tsx | 38 ++- client/src/components/Layout/PageSidebar.tsx | 31 +-- .../src/components/Map/ReservationOverlay.tsx | 4 +- .../Notifications/InAppNotificationItem.tsx | 13 +- .../src/components/OAuth/ScopeGroupPicker.tsx | 12 +- .../Packing/ApplyTemplateButton.tsx | 2 +- .../components/Packing/PackingListPanel.tsx | 40 +-- client/src/components/Photos/PhotoUpload.tsx | 3 + .../src/components/Planner/DayDetailPanel.tsx | 6 +- .../src/components/Planner/DayPlanSidebar.tsx | 17 +- .../src/components/Planner/PlaceInspector.tsx | 31 +-- .../src/components/Planner/PlacesSidebar.tsx | 21 +- .../components/Planner/ReservationModal.tsx | 6 +- .../components/Planner/ReservationsPanel.tsx | 8 +- .../src/components/Planner/TransportModal.tsx | 6 +- client/src/components/Settings/AboutTab.tsx | 66 ++--- client/src/components/Settings/AccountTab.tsx | 78 +++--- .../components/Settings/IntegrationsTab.tsx | 185 ++++++------- client/src/components/Todo/TodoListPanel.tsx | 4 +- client/src/components/Trips/TripFormModal.tsx | 19 +- .../src/components/Trips/TripMembersModal.tsx | 51 ++-- client/src/components/Vacay/VacayCalendar.tsx | 2 +- .../src/components/Vacay/VacayMonthCard.tsx | 8 +- client/src/components/Vacay/VacayPersons.tsx | 46 ++-- client/src/components/Vacay/VacaySettings.tsx | 43 ++- client/src/components/Vacay/VacayStats.tsx | 38 +-- .../src/components/shared/ConfirmDialog.tsx | 13 +- .../src/components/shared/CopyTripDialog.tsx | 22 +- client/src/components/shared/Modal.tsx | 8 +- client/src/components/shared/Skeleton.tsx | 7 +- client/src/components/shared/Tooltip.tsx | 5 +- client/src/pages/AdminPage.tsx | 67 ++--- client/src/pages/AtlasPage.tsx | 259 ++++++------------ client/src/pages/InAppNotificationsPage.tsx | 22 +- client/src/pages/JourneyDetailPage.tsx | 255 +---------------- client/src/pages/JourneyPage.tsx | 12 +- client/src/pages/SettingsPage.tsx | 8 +- client/src/pages/SharedTripPage.tsx | 14 +- client/src/pages/TripPlannerPage.tsx | 42 +-- client/src/pages/VacayPage.tsx | 86 +++--- client/src/pages/admin/AdminUpdateBanner.tsx | 50 ++++ client/src/pages/atlas/AtlasCountrySearch.tsx | 152 ++++++++++ .../src/pages/tripPlanner/useTripPlanner.ts | 4 +- 52 files changed, 1218 insertions(+), 1149 deletions(-) create mode 100644 client/src/components/Journey/ContributorInviteDialog.tsx create mode 100644 client/src/components/Journey/JourneyShareSection.tsx create mode 100644 client/src/pages/admin/AdminUpdateBanner.tsx create mode 100644 client/src/pages/atlas/AtlasCountrySearch.tsx diff --git a/client/src/components/Admin/AddonManager.tsx b/client/src/components/Admin/AddonManager.tsx index c2db2218..e9d4b48d 100644 --- a/client/src/components/Admin/AddonManager.tsx +++ b/client/src/components/Admin/AddonManager.tsx @@ -158,16 +158,16 @@ export default function AddonManager({ bagTrackingEnabled, onToggleBagTracking, return (
{/* Header */} -
-
-

{t('admin.addons.title')}

-

+

+
+

{t('admin.addons.title')}

+

{t('admin.addons.subtitleBefore')}TREK{t('admin.addons.subtitleAfter')}

{addons.length === 0 ? ( -
+
{t('admin.addons.noAddons')}
) : ( @@ -175,9 +175,9 @@ export default function AddonManager({ bagTrackingEnabled, onToggleBagTracking, {/* Trip Addons */} {tripAddons.length > 0 && (
-
- - +
+ + {t('admin.addons.type.trip')} — {t('admin.addons.tripHint')}
@@ -185,11 +185,11 @@ export default function AddonManager({ bagTrackingEnabled, onToggleBagTracking,
{addon.id === 'packing' && addon.enabled && onToggleBagTracking && ( -
+
-
{t('admin.bagTracking.title')}
-
{t('admin.bagTracking.subtitle')}
+
{t('admin.bagTracking.title')}
+
{t('admin.bagTracking.subtitle')}
@@ -205,7 +205,7 @@ export default function AddonManager({ bagTrackingEnabled, onToggleBagTracking,
)} {addon.id === 'collab' && addon.enabled && collabFeatures && onToggleCollabFeature && ( -
+
{COLLAB_SUB_FEATURES.map(feat => { const enabled = collabFeatures[feat.key] @@ -214,8 +214,8 @@ export default function AddonManager({ bagTrackingEnabled, onToggleBagTracking,
-
{t(feat.titleKey)}
-
{t(feat.subtitleKey)}
+
{t(feat.titleKey)}
+
{t(feat.subtitleKey)}
@@ -242,9 +242,9 @@ export default function AddonManager({ bagTrackingEnabled, onToggleBagTracking, {/* Global Addons */} {globalAddons.length > 0 && (
-
- - +
+ + {t('admin.addons.type.global')} — {t('admin.addons.globalHint')}
@@ -253,7 +253,7 @@ export default function AddonManager({ bagTrackingEnabled, onToggleBagTracking, {/* Memories providers as sub-items under Journey addon */} {addon.id === 'journey' && providerOptions.length > 0 && ( -
+
{providerOptions.map(provider => { const ProviderIcon = PROVIDER_ICONS[provider.key] @@ -261,8 +261,8 @@ export default function AddonManager({ bagTrackingEnabled, onToggleBagTracking,
{ProviderIcon && }
-
{provider.label}
-
{provider.description}
+
{provider.label}
+
{provider.description}
@@ -291,9 +291,9 @@ export default function AddonManager({ bagTrackingEnabled, onToggleBagTracking, {/* Integration Addons */} {integrationAddons.length > 0 && (
-
- - +
+ + {t('admin.addons.type.integration')} — {t('admin.addons.integrationHint')}
@@ -336,26 +336,26 @@ function AddonRow({ addon, onToggle, t, nameOverride, descriptionOverride, statu const displayDescription = descriptionOverride || label.description const enabledState = statusOverride ?? addon.enabled return ( -
+
{/* Icon */} -
+
{/* Info */}
- {displayName} + {displayName} {isComingSoon && ( - + Coming Soon )} - + {addon.type === 'global' ? t('admin.addons.type.global') : addon.type === 'integration' ? t('admin.addons.type.integration') : t('admin.addons.type.trip')}
-

{displayDescription}

+

{displayDescription}

{/* Toggle */} @@ -371,9 +371,8 @@ function AddonRow({ addon, onToggle, t, nameOverride, descriptionOverride, statu style={{ background: (enabledState && !isComingSoon) ? 'var(--text-primary)' : 'var(--border-primary)', cursor: isComingSoon ? 'not-allowed' : 'pointer' }} > diff --git a/client/src/components/Admin/GitHubPanel.tsx b/client/src/components/Admin/GitHubPanel.tsx index 7cc3f421..325cb5ee 100644 --- a/client/src/components/Admin/GitHubPanel.tsx +++ b/client/src/components/Admin/GitHubPanel.tsx @@ -67,7 +67,7 @@ export default function GitHubPanel({ isPrerelease = false }: { isPrerelease?: b elements.push(
    {listItems.map((item, i) => ( -
  • +
  • @@ -96,14 +96,14 @@ export default function GitHubPanel({ isPrerelease = false }: { isPrerelease?: b if (trimmed.startsWith('### ')) { flushList() elements.push( -

    +

    {trimmed.slice(4)}

    ) } else if (trimmed.startsWith('## ')) { flushList() elements.push( -

    +

    {trimmed.slice(3)}

    ) @@ -112,7 +112,7 @@ export default function GitHubPanel({ isPrerelease = false }: { isPrerelease?: b } else { flushList() elements.push( -

    ) @@ -130,8 +130,8 @@ export default function GitHubPanel({ isPrerelease = false }: { isPrerelease?: b href="https://ko-fi.com/mauriceboe" target="_blank" rel="noopener noreferrer" - className="rounded-xl border overflow-hidden flex items-center gap-4 px-5 py-4 transition-[border-color,box-shadow] duration-200 ease-[cubic-bezier(0.23,1,0.32,1)]" - style={{ background: 'var(--bg-card)', borderColor: 'var(--border-primary)', textDecoration: 'none' }} + className="rounded-xl border overflow-hidden flex items-center gap-4 px-5 py-4 transition-[border-color,box-shadow] duration-200 ease-[cubic-bezier(0.23,1,0.32,1)] bg-surface-card" + style={{ borderColor: 'var(--border-primary)', textDecoration: 'none' }} onMouseEnter={e => { e.currentTarget.style.borderColor = '#ff5e5b'; e.currentTarget.style.boxShadow = '0 0 0 1px #ff5e5b22' }} onMouseLeave={e => { e.currentTarget.style.borderColor = 'var(--border-primary)'; e.currentTarget.style.boxShadow = 'none' }} > @@ -139,17 +139,17 @@ export default function GitHubPanel({ isPrerelease = false }: { isPrerelease?: b

-
Ko-fi
-
{t('admin.github.support')}
+
Ko-fi
+
{t('admin.github.support')}
- + { e.currentTarget.style.borderColor = '#ffdd00'; e.currentTarget.style.boxShadow = '0 0 0 1px #ffdd0022' }} onMouseLeave={e => { e.currentTarget.style.borderColor = 'var(--border-primary)'; e.currentTarget.style.boxShadow = 'none' }} > @@ -157,17 +157,17 @@ export default function GitHubPanel({ isPrerelease = false }: { isPrerelease?: b
-
Buy Me a Coffee
-
{t('admin.github.support')}
+
Buy Me a Coffee
+
{t('admin.github.support')}
- +
{ e.currentTarget.style.borderColor = '#5865F2'; e.currentTarget.style.boxShadow = '0 0 0 1px #5865F222' }} onMouseLeave={e => { e.currentTarget.style.borderColor = 'var(--border-primary)'; e.currentTarget.style.boxShadow = 'none' }} > @@ -175,10 +175,10 @@ export default function GitHubPanel({ isPrerelease = false }: { isPrerelease?: b
-
Discord
-
Join the community
+
Discord
+
Join the community
- +
@@ -187,8 +187,8 @@ export default function GitHubPanel({ isPrerelease = false }: { isPrerelease?: b href="https://github.com/mauriceboe/TREK/issues/new?template=bug_report.yml" target="_blank" rel="noopener noreferrer" - className="rounded-xl border overflow-hidden flex items-center gap-4 px-5 py-4 transition-[border-color,box-shadow] duration-200 ease-[cubic-bezier(0.23,1,0.32,1)]" - style={{ background: 'var(--bg-card)', borderColor: 'var(--border-primary)', textDecoration: 'none' }} + className="rounded-xl border overflow-hidden flex items-center gap-4 px-5 py-4 transition-[border-color,box-shadow] duration-200 ease-[cubic-bezier(0.23,1,0.32,1)] bg-surface-card" + style={{ borderColor: 'var(--border-primary)', textDecoration: 'none' }} onMouseEnter={e => { e.currentTarget.style.borderColor = '#ef4444'; e.currentTarget.style.boxShadow = '0 0 0 1px #ef444422' }} onMouseLeave={e => { e.currentTarget.style.borderColor = 'var(--border-primary)'; e.currentTarget.style.boxShadow = 'none' }} > @@ -196,17 +196,17 @@ export default function GitHubPanel({ isPrerelease = false }: { isPrerelease?: b
-
{t('settings.about.reportBug')}
-
{t('settings.about.reportBugHint')}
+
{t('settings.about.reportBug')}
+
{t('settings.about.reportBugHint')}
- + { e.currentTarget.style.borderColor = '#f59e0b'; e.currentTarget.style.boxShadow = '0 0 0 1px #f59e0b22' }} onMouseLeave={e => { e.currentTarget.style.borderColor = 'var(--border-primary)'; e.currentTarget.style.boxShadow = 'none' }} > @@ -214,17 +214,17 @@ export default function GitHubPanel({ isPrerelease = false }: { isPrerelease?: b
-
{t('settings.about.featureRequest')}
-
{t('settings.about.featureRequestHint')}
+
{t('settings.about.featureRequest')}
+
{t('settings.about.featureRequestHint')}
- +
{ e.currentTarget.style.borderColor = '#6366f1'; e.currentTarget.style.boxShadow = '0 0 0 1px #6366f122' }} onMouseLeave={e => { e.currentTarget.style.borderColor = 'var(--border-primary)'; e.currentTarget.style.boxShadow = 'none' }} > @@ -232,40 +232,39 @@ export default function GitHubPanel({ isPrerelease = false }: { isPrerelease?: b
-
Wiki
-
{t('settings.about.wikiHint')}
+
Wiki
+
{t('settings.about.wikiHint')}
- +
{/* Loading / Error / Releases */} {loading ? ( -
+
- +
) : error ? ( -
+
-

{t('admin.github.error')}

-

{error}

+

{t('admin.github.error')}

+

{error}

) : ( -
-
+
+
-

{t('admin.github.title')}

-

{t('admin.github.subtitle').replace('{repo}', REPO)}

+

{t('admin.github.title')}

+

{t('admin.github.subtitle').replace('{repo}', REPO)}

GitHub @@ -299,7 +298,7 @@ export default function GitHubPanel({ isPrerelease = false }: { isPrerelease?: b {/* Release content */}
- + {release.tag_name} {isLatest && ( @@ -317,18 +316,18 @@ export default function GitHubPanel({ isPrerelease = false }: { isPrerelease?: b
{release.name && release.name !== release.tag_name && ( -

+

{release.name}

)}
- + {formatDate(release.published_at || release.created_at)} {release.author && ( - + {t('admin.github.by')} {release.author.login} )} @@ -339,15 +338,14 @@ export default function GitHubPanel({ isPrerelease = false }: { isPrerelease?: b
{isExpanded && ( -
+
{renderBody(release.body)}
)} @@ -366,8 +364,7 @@ export default function GitHubPanel({ isPrerelease = false }: { isPrerelease?: b +
+ +
+ {/* Search */} +
+ + setSearch(e.target.value)} + placeholder={t('journey.contributors.searchPlaceholder')} + className="w-full px-3 py-2 border border-zinc-200 dark:border-zinc-700 rounded-lg text-[13px] bg-white dark:bg-zinc-800 text-zinc-900 dark:text-white outline-none focus:border-zinc-400 dark:focus:border-zinc-500" + /> +
+ + {/* User list */} +
+ {filtered.length === 0 && ( +

{t('journey.contributors.noUsers')}

+ )} + {filtered.map(u => ( +
setSelectedUserId(u.id)} + className={`flex items-center gap-2.5 p-2.5 rounded-lg cursor-pointer transition-all ${ + selectedUserId === u.id + ? 'bg-zinc-100 dark:bg-zinc-800 border border-zinc-900 dark:border-white' + : 'hover:bg-zinc-50 dark:hover:bg-zinc-800 border border-transparent' + }`} + > +
+ {u.username[0].toUpperCase()} +
+
+
{u.username}
+
{u.email}
+
+ {selectedUserId === u.id && ( +
+ +
+ )} +
+ ))} +
+ + {/* Role selector */} +
+ +
+ {(['viewer', 'editor'] as const).map(r => ( + + ))} +
+
+
+ +
+ + +
+
+
+ ) +} diff --git a/client/src/components/Journey/JourneyShareSection.tsx b/client/src/components/Journey/JourneyShareSection.tsx new file mode 100644 index 00000000..122987b1 --- /dev/null +++ b/client/src/components/Journey/JourneyShareSection.tsx @@ -0,0 +1,112 @@ +import { useEffect, useState } from 'react' +import { Link, List, Grid, MapPin, Check } from 'lucide-react' +import { journeyApi } from '../../api/client' +import { useTranslation } from '../../i18n' +import { useToast } from '../shared/Toast' + +export default function JourneyShareSection({ journeyId }: { journeyId: number }) { + const { t } = useTranslation() + const [link, setLink] = useState<{ token: string; share_timeline: boolean; share_gallery: boolean; share_map: boolean } | null>(null) + const [loading, setLoading] = useState(true) + const [copied, setCopied] = useState(false) + const toast = useToast() + + useEffect(() => { + journeyApi.getShareLink(journeyId).then(d => setLink(d.link || null)).catch(() => {}).finally(() => setLoading(false)) + }, [journeyId]) + + const createLink = async () => { + try { + const res = await journeyApi.createShareLink(journeyId, { share_timeline: true, share_gallery: true, share_map: true }) + setLink({ token: res.token, share_timeline: true, share_gallery: true, share_map: true }) + toast.success(t('journey.share.linkCreated')) + } catch { toast.error(t('journey.share.createFailed')) } + } + + const togglePerm = async (key: 'share_timeline' | 'share_gallery' | 'share_map') => { + if (!link) return + const updated = { ...link, [key]: !link[key] } + setLink(updated) + try { + await journeyApi.createShareLink(journeyId, { share_timeline: updated.share_timeline, share_gallery: updated.share_gallery, share_map: updated.share_map }) + } catch { setLink(link); toast.error(t('journey.share.updateFailed')) } + } + + const deleteLink = async () => { + try { + await journeyApi.deleteShareLink(journeyId) + setLink(null) + toast.success(t('journey.share.linkDeleted')) + } catch { toast.error(t('journey.share.deleteFailed')) } + } + + const shareUrl = link ? `${window.location.origin}/public/journey/${link.token}` : '' + + const copyLink = () => { + navigator.clipboard.writeText(shareUrl) + setCopied(true) + setTimeout(() => setCopied(false), 2000) + } + + if (loading) return null + + return ( +
+ + + {!link ? ( + + ) : ( +
+ {/* URL + Copy */} +
+ + {shareUrl} + +
+ + {/* Permission toggles */} +
+ {[ + { key: 'share_timeline' as const, label: t('journey.share.timeline'), icon: List }, + { key: 'share_gallery' as const, label: t('journey.share.gallery'), icon: Grid }, + { key: 'share_map' as const, label: t('journey.share.map'), icon: MapPin }, + ].map(({ key, label, icon: Icon }) => ( + + ))} +
+ + {/* Delete link */} + +
+ )} +
+ ) +} diff --git a/client/src/components/Layout/InAppNotificationBell.tsx b/client/src/components/Layout/InAppNotificationBell.tsx index 0b220382..42df852b 100644 --- a/client/src/components/Layout/InAppNotificationBell.tsx +++ b/client/src/components/Layout/InAppNotificationBell.tsx @@ -45,8 +45,7 @@ export default function InAppNotificationBell(): React.ReactElement {
) : notifications.length === 0 ? (
- -

{t('notifications.empty')}

-

{t('notifications.emptyDescription')}

+ +

{t('notifications.empty')}

+

{t('notifications.emptyDescription')}

) : ( notifications.slice(0, 10).map(n => ( @@ -151,10 +145,8 @@ export default function InAppNotificationBell(): React.ReactElement { {/* Footer */} {userMenuOpen && ReactDOM.createPortal( <>
setUserMenuOpen(false)} /> -
-
-

{user.username}

-

{user.email}

+
+
+

{user.username}

+

{user.email}

{user.role === 'admin' && ( - + {t('nav.administrator')} )} @@ -249,8 +247,7 @@ export default function Navbar({ tripTitle, tripId, onBack, showBack, onShare }:
setUserMenuOpen(false)} - className="flex items-center gap-2 px-4 py-2 text-sm transition-colors" - style={{ color: 'var(--text-secondary)' }} + className="flex items-center gap-2 px-4 py-2 text-sm transition-colors text-content-secondary" onMouseEnter={e => e.currentTarget.style.background = 'var(--bg-hover)'} onMouseLeave={e => e.currentTarget.style.background = 'transparent'}> @@ -259,8 +256,7 @@ export default function Navbar({ tripTitle, tripId, onBack, showBack, onShare }: {user.role === 'admin' && ( setUserMenuOpen(false)} - className="flex items-center gap-2 px-4 py-2 text-sm transition-colors" - style={{ color: 'var(--text-secondary)' }} + className="flex items-center gap-2 px-4 py-2 text-sm transition-colors text-content-secondary" onMouseEnter={e => e.currentTarget.style.background = 'var(--bg-hover)'} onMouseLeave={e => e.currentTarget.style.background = 'transparent'}> @@ -269,14 +265,14 @@ export default function Navbar({ tripTitle, tripId, onBack, showBack, onShare }: )}
-
+
{appVersion && ( -
+
TREK diff --git a/client/src/components/Layout/PageSidebar.tsx b/client/src/components/Layout/PageSidebar.tsx index 9c293b9f..74832354 100644 --- a/client/src/components/Layout/PageSidebar.tsx +++ b/client/src/components/Layout/PageSidebar.tsx @@ -47,27 +47,23 @@ export default function PageSidebar({ return (
{/* Mobile top bar with hamburger */}
-
+
{activeLabel}
@@ -75,11 +71,9 @@ export default function PageSidebar({ {/* Desktop sidebar (always visible on lg) */}