mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-19 13:21:46 +00:00
feat: Discord links, translation sync, iOS login fix, trip copy fix
- Add Discord button to admin GitHub panel and user menu - Sync all 13 translation files to 1434 keys with native translations - Fix duplicate keys in Polish translation (pl.ts) - Fix iOS login race condition: sameSite strict→lax, loadUser sequence counter - Fix trip copy route: add missing db, Trip, TRIP_SELECT imports
This commit is contained in:
@@ -119,7 +119,7 @@ export default function GitHubPanel() {
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
{/* Support cards */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3">
|
||||
<a
|
||||
href="https://ko-fi.com/mauriceboe"
|
||||
target="_blank"
|
||||
@@ -156,6 +156,24 @@ export default function GitHubPanel() {
|
||||
</div>
|
||||
<ExternalLink size={14} className="ml-auto flex-shrink-0" style={{ color: 'var(--text-faint)' }} />
|
||||
</a>
|
||||
<a
|
||||
href="https://discord.gg/nSdKaXgN"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="rounded-xl border overflow-hidden flex items-center gap-4 px-5 py-4 transition-all"
|
||||
style={{ background: 'var(--bg-card)', borderColor: 'var(--border-primary)', textDecoration: 'none' }}
|
||||
onMouseEnter={e => { 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' }}
|
||||
>
|
||||
<div style={{ width: 40, height: 40, borderRadius: 10, background: '#5865F215', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="#5865F2"><path d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028c.462-.63.874-1.295 1.226-1.994a.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.095 2.157 2.42 0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.095 2.157 2.42 0 1.333-.946 2.418-2.157 2.418z"/></svg>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-sm font-semibold" style={{ color: 'var(--text-primary)' }}>Discord</div>
|
||||
<div className="text-xs" style={{ color: 'var(--text-faint)' }}>Join the community</div>
|
||||
</div>
|
||||
<ExternalLink size={14} className="ml-auto flex-shrink-0" style={{ color: 'var(--text-faint)' }} />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{/* Loading / Error / Releases */}
|
||||
|
||||
@@ -232,9 +232,18 @@ export default function Navbar({ tripTitle, tripId, onBack, showBack, onShare }:
|
||||
</button>
|
||||
{appVersion && (
|
||||
<div className="px-4 pt-2 pb-2.5 text-center" style={{ marginTop: 4, borderTop: '1px solid var(--border-secondary)' }}>
|
||||
<div style={{ display: 'inline-flex', alignItems: 'center', gap: 5, background: 'var(--bg-tertiary)', borderRadius: 99, padding: '4px 12px' }}>
|
||||
<img src={dark ? '/text-light.svg' : '/text-dark.svg'} alt="TREK" style={{ height: 10, opacity: 0.5 }} />
|
||||
<span style={{ fontSize: 10, fontWeight: 600, color: 'var(--text-faint)' }}>v{appVersion}</span>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 6 }}>
|
||||
<div style={{ display: 'inline-flex', alignItems: 'center', gap: 5, background: 'var(--bg-tertiary)', borderRadius: 99, padding: '4px 12px' }}>
|
||||
<img src={dark ? '/text-light.svg' : '/text-dark.svg'} alt="TREK" style={{ height: 10, opacity: 0.5 }} />
|
||||
<span style={{ fontSize: 10, fontWeight: 600, color: 'var(--text-faint)' }}>v{appVersion}</span>
|
||||
</div>
|
||||
<a href="https://discord.gg/nSdKaXgN" target="_blank" rel="noopener noreferrer"
|
||||
style={{ display: 'inline-flex', alignItems: 'center', justifyContent: 'center', width: 24, height: 24, borderRadius: 99, background: 'var(--bg-tertiary)', transition: 'background 0.15s' }}
|
||||
onMouseEnter={e => e.currentTarget.style.background = '#5865F220'}
|
||||
onMouseLeave={e => e.currentTarget.style.background = 'var(--bg-tertiary)'}
|
||||
title="Discord">
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="var(--text-faint)"><path d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028c.462-.63.874-1.295 1.226-1.994a.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.095 2.157 2.42 0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.095 2.157 2.42 0 1.333-.946 2.418-2.157 2.418z"/></svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
+1545
-1522
File diff suppressed because it is too large
Load Diff
+1540
-1517
File diff suppressed because it is too large
Load Diff
+1545
-1520
File diff suppressed because it is too large
Load Diff
+1542
-1519
File diff suppressed because it is too large
Load Diff
+1547
-1524
File diff suppressed because it is too large
Load Diff
+1541
-1518
File diff suppressed because it is too large
Load Diff
+1542
-1519
File diff suppressed because it is too large
Load Diff
+1542
-1521
File diff suppressed because it is too large
Load Diff
+1541
-1518
File diff suppressed because it is too large
Load Diff
@@ -661,7 +661,6 @@ const pl: Record<string, string | { name: string; category: string }[]> = {
|
||||
'atlas.statsTab': 'Statystyki',
|
||||
'atlas.bucketTab': 'Lista marzeń',
|
||||
'atlas.addBucket': 'Dodaj do listy marzeń',
|
||||
'atlas.bucketNamePlaceholder': 'Miejsce lub cel podróży...',
|
||||
'atlas.bucketNotesPlaceholder': 'Notatki (opcjonalnie)',
|
||||
'atlas.bucketEmpty': 'Twoja lista marzeń jest pusta',
|
||||
'atlas.bucketEmptyHint': 'Dodaj miejsca, które chcesz odwiedzić',
|
||||
@@ -674,7 +673,6 @@ const pl: Record<string, string | { name: string; category: string }[]> = {
|
||||
'atlas.nextTrip': 'Następna podróż',
|
||||
'atlas.daysLeft': 'dni do wyjazdu',
|
||||
'atlas.streak': 'Streak',
|
||||
'atlas.year': 'rok',
|
||||
'atlas.years': 'lata',
|
||||
'atlas.yearInRow': 'rok z rzędu',
|
||||
'atlas.yearsInRow': 'lat z rzędu',
|
||||
@@ -1388,6 +1386,151 @@ const pl: Record<string, string | { name: string; category: string }[]> = {
|
||||
'collab.polls.options': 'Opcje',
|
||||
'collab.polls.delete': 'Usuń',
|
||||
'collab.polls.closedSection': 'Zamknięte',
|
||||
'common.import': 'Importuj',
|
||||
'common.saved': 'Zapisano',
|
||||
'trips.reminder': 'Przypomnienie',
|
||||
'trips.reminderNone': 'Brak',
|
||||
'trips.reminderDay': 'dzień',
|
||||
'trips.reminderDays': 'dni',
|
||||
'trips.reminderCustom': 'Niestandardowe',
|
||||
'trips.reminderDaysBefore': 'dni przed wyjazdem',
|
||||
'trips.reminderDisabledHint': 'Przypomnienia o podróżach są wyłączone.',
|
||||
'dashboard.members': 'Towarzysze',
|
||||
'dashboard.copyTrip': 'Kopiuj',
|
||||
'dashboard.copySuffix': 'kopia',
|
||||
'dashboard.toast.copied': 'Podróż skopiowana!',
|
||||
'dashboard.toast.copyError': 'Nie udało się skopiować podróży',
|
||||
'admin.notifications.title': 'Powiadomienia',
|
||||
'admin.notifications.hint': 'Wybierz jeden kanał powiadomień.',
|
||||
'admin.notifications.none': 'Wyłączone',
|
||||
'admin.notifications.email': 'Email (SMTP)',
|
||||
'admin.notifications.webhook': 'Webhook',
|
||||
'admin.notifications.events': 'Zdarzenia powiadomień',
|
||||
'admin.notifications.eventsHint': 'Wybierz zdarzenia wyzwalające powiadomienia.',
|
||||
'admin.notifications.configureFirst': 'Najpierw skonfiguruj ustawienia SMTP lub webhook.',
|
||||
'admin.notifications.save': 'Zapisz ustawienia powiadomień',
|
||||
'admin.notifications.saved': 'Ustawienia powiadomień zapisane',
|
||||
'admin.notifications.testWebhook': 'Wyślij testowy webhook',
|
||||
'admin.notifications.testWebhookSuccess': 'Testowy webhook wysłany pomyślnie',
|
||||
'admin.notifications.testWebhookFailed': 'Testowy webhook nie powiódł się',
|
||||
'admin.webhook.hint': 'Wysyłaj powiadomienia do zewnętrznego webhooka.',
|
||||
'settings.notificationsDisabled': 'Powiadomienia nie są skonfigurowane.',
|
||||
'settings.notificationsActive': 'Aktywny kanał',
|
||||
'settings.notificationsManagedByAdmin': 'Zdarzenia konfigurowane przez administratora.',
|
||||
'settings.mustChangePassword': 'Musisz zmienić hasło przed kontynuowaniem.',
|
||||
'login.setNewPassword': 'Ustaw nowe hasło',
|
||||
'login.setNewPasswordHint': 'Musisz zmienić hasło.',
|
||||
'atlas.searchCountry': 'Szukaj kraju...',
|
||||
'trip.loadingPhotos': 'Ładowanie zdjęć...',
|
||||
'places.importGoogleList': 'Lista Google',
|
||||
'places.googleListHint': 'Wklej link do listy Google Maps.',
|
||||
'places.googleListImported': 'Zaimportowano {count} miejsc',
|
||||
'places.googleListError': 'Nie udało się zaimportować listy',
|
||||
'places.viewDetails': 'Zobacz szczegóły',
|
||||
'inspector.trackStats': 'Statystyki trasy',
|
||||
'budget.exportCsv': 'Eksportuj CSV',
|
||||
'budget.table.date': 'Data',
|
||||
'memories.testFirst': 'Najpierw przetestuj połączenie',
|
||||
'memories.linkAlbum': 'Połącz album',
|
||||
'memories.selectAlbum': 'Wybierz album Immich',
|
||||
'memories.noAlbums': 'Nie znaleziono albumów',
|
||||
'memories.syncAlbum': 'Synchronizuj album',
|
||||
'memories.unlinkAlbum': 'Odłącz album',
|
||||
'memories.photos': 'zdjęcia',
|
||||
'memories.error.loadAlbums': 'Nie udało się załadować albumów',
|
||||
'memories.error.linkAlbum': 'Nie udało się połączyć albumu',
|
||||
'memories.error.unlinkAlbum': 'Nie udało się odłączyć albumu',
|
||||
'memories.error.syncAlbum': 'Nie udało się zsynchronizować albumu',
|
||||
'memories.error.loadPhotos': 'Nie udało się załadować zdjęć',
|
||||
'memories.error.addPhotos': 'Nie udało się dodać zdjęć',
|
||||
'memories.error.removePhoto': 'Nie udało się usunąć zdjęcia',
|
||||
'memories.error.toggleSharing': 'Nie udało się zaktualizować udostępniania',
|
||||
'collab.chat.reply': 'Odpowiedz',
|
||||
'admin.tabs.permissions': 'Uprawnienia',
|
||||
'perm.title': 'Ustawienia uprawnień',
|
||||
'perm.subtitle': 'Kontroluj uprawnienia w aplikacji',
|
||||
'perm.saved': 'Ustawienia uprawnień zapisane',
|
||||
'perm.resetDefaults': 'Przywróć domyślne',
|
||||
'perm.customized': 'dostosowane',
|
||||
'perm.level.admin': 'Tylko admin',
|
||||
'perm.level.tripOwner': 'Właściciel podróży',
|
||||
'perm.level.tripMember': 'Członkowie podróży',
|
||||
'perm.level.everybody': 'Wszyscy',
|
||||
'perm.cat.trip': 'Zarządzanie podróżami',
|
||||
'perm.cat.members': 'Zarządzanie członkami',
|
||||
'perm.cat.files': 'Pliki',
|
||||
'perm.cat.content': 'Treść i harmonogram',
|
||||
'perm.cat.extras': 'Budżet, pakowanie i współpraca',
|
||||
'perm.action.trip_create': 'Tworzenie podróży',
|
||||
'perm.action.trip_edit': 'Edytowanie podróży',
|
||||
'perm.action.trip_delete': 'Usuwanie podróży',
|
||||
'perm.action.trip_archive': 'Archiwizacja podróży',
|
||||
'perm.action.trip_cover_upload': 'Przesyłanie okładki',
|
||||
'perm.action.member_manage': 'Zarządzanie członkami',
|
||||
'perm.action.file_upload': 'Przesyłanie plików',
|
||||
'perm.action.file_edit': 'Edytowanie plików',
|
||||
'perm.action.file_delete': 'Usuwanie plików',
|
||||
'perm.action.place_edit': 'Zarządzanie miejscami',
|
||||
'perm.action.day_edit': 'Edytowanie dni',
|
||||
'perm.action.reservation_edit': 'Zarządzanie rezerwacjami',
|
||||
'perm.action.budget_edit': 'Zarządzanie budżetem',
|
||||
'perm.action.packing_edit': 'Zarządzanie pakowaniem',
|
||||
'perm.action.collab_edit': 'Współpraca',
|
||||
'perm.action.share_manage': 'Zarządzanie udostępnianiem',
|
||||
'perm.actionHint.trip_create': 'Kto może tworzyć nowe podróże',
|
||||
'perm.actionHint.trip_edit': 'Kto może edytować szczegóły podróży',
|
||||
'perm.actionHint.trip_delete': 'Kto może usunąć podróż',
|
||||
'perm.actionHint.trip_archive': 'Kto może archiwizować podróż',
|
||||
'perm.actionHint.trip_cover_upload': 'Kto może zmieniać okładkę',
|
||||
'perm.actionHint.member_manage': 'Kto może zapraszać lub usuwać członków',
|
||||
'perm.actionHint.file_upload': 'Kto może przesyłać pliki',
|
||||
'perm.actionHint.file_edit': 'Kto może edytować pliki',
|
||||
'perm.actionHint.file_delete': 'Kto może usuwać pliki',
|
||||
'perm.actionHint.place_edit': 'Kto może zarządzać miejscami',
|
||||
'perm.actionHint.day_edit': 'Kto może edytować dni i przypisania',
|
||||
'perm.actionHint.reservation_edit': 'Kto może zarządzać rezerwacjami',
|
||||
'perm.actionHint.budget_edit': 'Kto może zarządzać budżetem',
|
||||
'perm.actionHint.packing_edit': 'Kto może zarządzać pakowaniem',
|
||||
'perm.actionHint.collab_edit': 'Kto może korzystać ze współpracy',
|
||||
'perm.actionHint.share_manage': 'Kto może zarządzać linkami',
|
||||
'undo.button': 'Cofnij',
|
||||
'undo.tooltip': 'Cofnij: {action}',
|
||||
'undo.assignPlace': 'Miejsce przypisane do dnia',
|
||||
'undo.removeAssignment': 'Miejsce usunięte z dnia',
|
||||
'undo.reorder': 'Kolejność zmieniona',
|
||||
'undo.optimize': 'Trasa zoptymalizowana',
|
||||
'undo.deletePlace': 'Miejsce usunięte',
|
||||
'undo.moveDay': 'Miejsce przeniesione',
|
||||
'undo.lock': 'Blokada przełączona',
|
||||
'undo.importGpx': 'Import GPX',
|
||||
'undo.importGoogleList': 'Import Google Maps',
|
||||
'undo.addPlace': 'Miejsce dodane',
|
||||
'undo.done': 'Cofnięto: {action}',
|
||||
'notifications.title': 'Powiadomienia',
|
||||
'notifications.markAllRead': 'Oznacz wszystkie jako przeczytane',
|
||||
'notifications.deleteAll': 'Usuń wszystkie',
|
||||
'notifications.showAll': 'Pokaż wszystkie',
|
||||
'notifications.empty': 'Brak powiadomień',
|
||||
'notifications.emptyDescription': "You're all caught up!",
|
||||
'notifications.all': 'Wszystkie',
|
||||
'notifications.unreadOnly': 'Nieprzeczytane',
|
||||
'notifications.markRead': 'Oznacz jako przeczytane',
|
||||
'notifications.markUnread': 'Oznacz jako nieprzeczytane',
|
||||
'notifications.delete': 'Usuń',
|
||||
'notifications.system': 'System',
|
||||
'notifications.test.title': 'Testowe powiadomienie od {actor}',
|
||||
'notifications.test.text': 'To jest powiadomienie testowe.',
|
||||
'notifications.test.booleanTitle': '{actor} prosi o akceptację',
|
||||
'notifications.test.booleanText': 'Testowe powiadomienie z wyborem.',
|
||||
'notifications.test.accept': 'Zatwierdź',
|
||||
'notifications.test.decline': 'Odrzuć',
|
||||
'notifications.test.navigateTitle': 'Sprawdź coś',
|
||||
'notifications.test.navigateText': 'Testowe powiadomienie nawigacyjne.',
|
||||
'notifications.test.goThere': 'Przejdź tam',
|
||||
'notifications.test.adminTitle': 'Komunikat administracyjny',
|
||||
'notifications.test.adminText': '{actor} wysłał testowe powiadomienie.',
|
||||
'notifications.test.tripTitle': '{actor} opublikował w Twojej podróży',
|
||||
'notifications.test.tripText': 'Testowe powiadomienie dla podróży "{trip}".',
|
||||
}
|
||||
|
||||
export default pl
|
||||
|
||||
+1541
-1518
File diff suppressed because it is too large
Load Diff
+1541
-1518
File diff suppressed because it is too large
Load Diff
@@ -48,6 +48,9 @@ interface AuthState {
|
||||
demoLogin: () => Promise<AuthResponse>
|
||||
}
|
||||
|
||||
// Sequence counter to prevent stale loadUser responses from overwriting fresh auth state
|
||||
let authSequence = 0
|
||||
|
||||
export const useAuthStore = create<AuthState>((set, get) => ({
|
||||
user: null,
|
||||
isAuthenticated: false,
|
||||
@@ -61,6 +64,7 @@ export const useAuthStore = create<AuthState>((set, get) => ({
|
||||
tripRemindersEnabled: false,
|
||||
|
||||
login: async (email: string, password: string) => {
|
||||
authSequence++
|
||||
set({ isLoading: true, error: null })
|
||||
try {
|
||||
const data = await authApi.login({ email, password }) as AuthResponse & { mfa_required?: boolean; mfa_token?: string }
|
||||
@@ -84,6 +88,7 @@ export const useAuthStore = create<AuthState>((set, get) => ({
|
||||
},
|
||||
|
||||
completeMfaLogin: async (mfaToken: string, code: string) => {
|
||||
authSequence++
|
||||
set({ isLoading: true, error: null })
|
||||
try {
|
||||
const data = await authApi.verifyMfaLogin({ mfa_token: mfaToken, code: code.replace(/\s/g, '') })
|
||||
@@ -103,6 +108,7 @@ export const useAuthStore = create<AuthState>((set, get) => ({
|
||||
},
|
||||
|
||||
register: async (username: string, email: string, password: string, invite_token?: string) => {
|
||||
authSequence++
|
||||
set({ isLoading: true, error: null })
|
||||
try {
|
||||
const data = await authApi.register({ username, email, password, invite_token })
|
||||
@@ -138,10 +144,12 @@ export const useAuthStore = create<AuthState>((set, get) => ({
|
||||
},
|
||||
|
||||
loadUser: async (opts?: { silent?: boolean }) => {
|
||||
const seq = authSequence
|
||||
const silent = !!opts?.silent
|
||||
if (!silent) set({ isLoading: true })
|
||||
try {
|
||||
const data = await authApi.me()
|
||||
if (seq !== authSequence) return // stale response — a login/register happened meanwhile
|
||||
set({
|
||||
user: data.user,
|
||||
isAuthenticated: true,
|
||||
@@ -149,6 +157,7 @@ export const useAuthStore = create<AuthState>((set, get) => ({
|
||||
})
|
||||
connect()
|
||||
} catch (err: unknown) {
|
||||
if (seq !== authSequence) return // stale response — ignore
|
||||
// Only clear auth state on 401 (invalid/expired token), not on network errors
|
||||
const isAuthError = err && typeof err === 'object' && 'response' in err &&
|
||||
(err as { response?: { status?: number } }).response?.status === 401
|
||||
@@ -219,6 +228,7 @@ export const useAuthStore = create<AuthState>((set, get) => ({
|
||||
setTripRemindersEnabled: (val: boolean) => set({ tripRemindersEnabled: val }),
|
||||
|
||||
demoLogin: async () => {
|
||||
authSequence++
|
||||
set({ isLoading: true, error: null })
|
||||
try {
|
||||
const data = await authApi.demoLogin()
|
||||
|
||||
@@ -3,10 +3,10 @@ import multer from 'multer';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { canAccessTrip } from '../db/database';
|
||||
import { db, canAccessTrip } from '../db/database';
|
||||
import { authenticate, demoUploadBlock } from '../middleware/auth';
|
||||
import { broadcast } from '../websocket';
|
||||
import { AuthRequest } from '../types';
|
||||
import { AuthRequest, Trip } from '../types';
|
||||
import { writeAudit, getClientIp, logInfo } from '../services/auditLog';
|
||||
import { checkPermission } from '../services/permissions';
|
||||
import {
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
verifyTripAccess,
|
||||
NotFoundError,
|
||||
ValidationError,
|
||||
TRIP_SELECT,
|
||||
} from '../services/tripService';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
@@ -7,7 +7,7 @@ export function cookieOptions(clear = false) {
|
||||
return {
|
||||
httpOnly: true,
|
||||
secure,
|
||||
sameSite: 'strict' as const,
|
||||
sameSite: 'lax' as const,
|
||||
path: '/',
|
||||
...(clear ? {} : { maxAge: 24 * 60 * 60 * 1000 }), // 24h — matches JWT expiry
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Trip, User } from '../types';
|
||||
export const MS_PER_DAY = 86400000;
|
||||
export const MAX_TRIP_DAYS = 365;
|
||||
|
||||
const TRIP_SELECT = `
|
||||
export const TRIP_SELECT = `
|
||||
SELECT t.*,
|
||||
(SELECT COUNT(*) FROM days d WHERE d.trip_id = t.id) as day_count,
|
||||
(SELECT COUNT(*) FROM places p WHERE p.trip_id = t.id) as place_count,
|
||||
|
||||
Reference in New Issue
Block a user