diff --git a/LICENSE b/LICENSE index e13fb3a..792a64f 100644 --- a/LICENSE +++ b/LICENSE @@ -1,5 +1,5 @@ AdventureLog: Self-hostable travel tracker and trip planner. - Copyright (C) 2024 Sean Morley + Copyright (C) 2023-2025 Sean Morley Contact: contact@seanmorley.com This program is free software: you can redistribute it and/or modify diff --git a/backend/entrypoint.sh b/backend/entrypoint.sh index fa81d11..48adee3 100644 --- a/backend/entrypoint.sh +++ b/backend/entrypoint.sh @@ -39,4 +39,4 @@ python manage.py download-countries cat /code/adventurelog.txt # Start gunicorn -gunicorn main.wsgi:application --bind 0.0.0.0:8000 \ No newline at end of file +gunicorn main.wsgi:application --bind 0.0.0.0:8000 --timeout 120 --workers 2 \ No newline at end of file diff --git a/backend/server/adventures/migrations/0015_transportation_destination_latitude_and_more.py b/backend/server/adventures/migrations/0015_transportation_destination_latitude_and_more.py new file mode 100644 index 0000000..7971839 --- /dev/null +++ b/backend/server/adventures/migrations/0015_transportation_destination_latitude_and_more.py @@ -0,0 +1,33 @@ +# Generated by Django 5.0.8 on 2024-12-19 17:16 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('adventures', '0014_alter_category_unique_together'), + ] + + operations = [ + migrations.AddField( + model_name='transportation', + name='destination_latitude', + field=models.DecimalField(blank=True, decimal_places=6, max_digits=9, null=True), + ), + migrations.AddField( + model_name='transportation', + name='destination_longitude', + field=models.DecimalField(blank=True, decimal_places=6, max_digits=9, null=True), + ), + migrations.AddField( + model_name='transportation', + name='origin_latitude', + field=models.DecimalField(blank=True, decimal_places=6, max_digits=9, null=True), + ), + migrations.AddField( + model_name='transportation', + name='origin_longitude', + field=models.DecimalField(blank=True, decimal_places=6, max_digits=9, null=True), + ), + ] diff --git a/backend/server/adventures/models.py b/backend/server/adventures/models.py index a8460d7..98ae268 100644 --- a/backend/server/adventures/models.py +++ b/backend/server/adventures/models.py @@ -167,6 +167,10 @@ class Transportation(models.Model): end_date = models.DateTimeField(blank=True, null=True) flight_number = models.CharField(max_length=100, blank=True, null=True) from_location = models.CharField(max_length=200, blank=True, null=True) + origin_latitude = models.DecimalField(max_digits=9, decimal_places=6, null=True, blank=True) + origin_longitude = models.DecimalField(max_digits=9, decimal_places=6, null=True, blank=True) + destination_latitude = models.DecimalField(max_digits=9, decimal_places=6, null=True, blank=True) + destination_longitude = models.DecimalField(max_digits=9, decimal_places=6, null=True, blank=True) to_location = models.CharField(max_length=200, blank=True, null=True) is_public = models.BooleanField(default=False) collection = models.ForeignKey('Collection', on_delete=models.CASCADE, blank=True, null=True) diff --git a/backend/server/adventures/serializers.py b/backend/server/adventures/serializers.py index 9b538ed..45a2141 100644 --- a/backend/server/adventures/serializers.py +++ b/backend/server/adventures/serializers.py @@ -170,7 +170,7 @@ class TransportationSerializer(CustomModelSerializer): fields = [ 'id', 'user_id', 'type', 'name', 'description', 'rating', 'link', 'date', 'flight_number', 'from_location', 'to_location', - 'is_public', 'collection', 'created_at', 'updated_at', 'end_date' + 'is_public', 'collection', 'created_at', 'updated_at', 'end_date', 'origin_latitude', 'origin_longitude', 'destination_latitude', 'destination_longitude' ] read_only_fields = ['id', 'created_at', 'updated_at', 'user_id'] diff --git a/documentation/.vitepress/config.mts b/documentation/.vitepress/config.mts index c14b50c..1c47f29 100644 --- a/documentation/.vitepress/config.mts +++ b/documentation/.vitepress/config.mts @@ -41,7 +41,7 @@ export default defineConfig({ footer: { message: "AdventureLog", - copyright: "Copyright © 2023-2024 Sean Morley", + copyright: "Copyright © 2023-2025 Sean Morley", }, logo: "/adventurelog.png", diff --git a/documentation/pnpm-lock.yaml b/documentation/pnpm-lock.yaml index 0371b16..109ad48 100644 --- a/documentation/pnpm-lock.yaml +++ b/documentation/pnpm-lock.yaml @@ -612,8 +612,8 @@ packages: mitt@3.0.1: resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} - nanoid@3.3.7: - resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} + nanoid@3.3.8: + resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true @@ -1352,7 +1352,7 @@ snapshots: mitt@3.0.1: {} - nanoid@3.3.7: {} + nanoid@3.3.8: {} oniguruma-to-es@0.4.1: dependencies: @@ -1366,7 +1366,7 @@ snapshots: postcss@8.4.49: dependencies: - nanoid: 3.3.7 + nanoid: 3.3.8 picocolors: 1.1.1 source-map-js: 1.2.1 diff --git a/frontend/package.json b/frontend/package.json index da9ceec..e2b9a26 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -41,6 +41,7 @@ "dependencies": { "@lukulent/svelte-umami": "^0.0.3", "emoji-picker-element": "^1.26.0", + "marked": "^15.0.4", "qrcode": "^1.5.4", "svelte-i18n": "^4.0.1", "svelte-maplibre": "^0.9.8" diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 702ef84..1c141b3 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -14,6 +14,9 @@ importers: emoji-picker-element: specifier: ^1.26.0 version: 1.26.0 + marked: + specifier: ^15.0.4 + version: 15.0.4 qrcode: specifier: ^1.5.4 version: 1.5.4 @@ -899,8 +902,8 @@ packages: resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} engines: {node: '>= 0.6'} - cross-spawn@7.0.3: - resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} css-selector-tokenizer@0.8.0: @@ -1351,6 +1354,11 @@ packages: resolution: {integrity: sha512-qOS1hn4d/pn2i0uva4S5Oz+fACzTkgBKq+NpwT/Tqzi4MSyzcWNtDELzLUSgWqHfNIkGCl5CZ/w7dtis+t4RCw==} engines: {node: '>=16.14.0', npm: '>=8.1.0'} + marked@15.0.4: + resolution: {integrity: sha512-TCHvDqmb3ZJ4PWG7VEGVgtefA5/euFmsIhxtD0XsBxI39gUSKL81mIRFdt0AiNQozUahd4ke98ZdirExd/vSEw==} + engines: {node: '>= 18'} + hasBin: true + mdn-data@2.0.30: resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} @@ -1432,8 +1440,8 @@ packages: mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} - nanoid@3.3.7: - resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} + nanoid@3.3.8: + resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true @@ -2358,7 +2366,7 @@ snapshots: '@jsdevtools/ez-spawn@3.0.4': dependencies: call-me-maybe: 1.0.2 - cross-spawn: 7.0.3 + cross-spawn: 7.0.6 string-argv: 0.3.2 type-detect: 4.0.8 @@ -2819,7 +2827,7 @@ snapshots: cookie@0.6.0: {} - cross-spawn@7.0.3: + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 shebang-command: 2.0.0 @@ -3000,7 +3008,7 @@ snapshots: execa@5.1.1: dependencies: - cross-spawn: 7.0.3 + cross-spawn: 7.0.6 get-stream: 6.0.1 human-signals: 2.1.0 is-stream: 2.0.1 @@ -3057,7 +3065,7 @@ snapshots: foreground-child@3.2.1: dependencies: - cross-spawn: 7.0.3 + cross-spawn: 7.0.6 signal-exit: 4.1.0 fraction.js@4.3.7: {} @@ -3318,6 +3326,8 @@ snapshots: tinyqueue: 2.0.3 vt-pbf: 3.1.3 + marked@15.0.4: {} + mdn-data@2.0.30: {} memoizee@0.4.17: @@ -3394,7 +3404,7 @@ snapshots: object-assign: 4.1.1 thenify-all: 1.6.0 - nanoid@3.3.7: {} + nanoid@3.3.8: {} next-tick@1.1.0: {} @@ -3548,13 +3558,13 @@ snapshots: postcss@8.4.38: dependencies: - nanoid: 3.3.7 + nanoid: 3.3.8 picocolors: 1.0.1 source-map-js: 1.2.0 postcss@8.4.47: dependencies: - nanoid: 3.3.7 + nanoid: 3.3.8 picocolors: 1.1.0 source-map-js: 1.2.1 diff --git a/frontend/src/lib/components/AboutModal.svelte b/frontend/src/lib/components/AboutModal.svelte index 60faa89..7b8fbf2 100644 --- a/frontend/src/lib/components/AboutModal.svelte +++ b/frontend/src/lib/components/AboutModal.svelte @@ -1,13 +1,14 @@ - - + - - + +
+

+ © {copyrightYear} + + Sean Morley + +

+

{$t('about.license')}

+

+ + {$t('about.source_code')} + +

+

{$t('about.message')}

+
+ + +
+ + +
+

+ {$t('about.oss_attributions')} +

+

+ {$t('about.nominatim_1')} + + OpenStreetMap + + . {$t('about.nominatim_2')} +

+

{$t('about.other_attributions')}

+
+ + +
+ +
+ + diff --git a/frontend/src/lib/components/AdventureCard.svelte b/frontend/src/lib/components/AdventureCard.svelte index da80e43..0d903ee 100644 --- a/frontend/src/lib/components/AdventureCard.svelte +++ b/frontend/src/lib/components/AdventureCard.svelte @@ -41,6 +41,24 @@ } } + let unlinked: boolean = false; + + // Reactive block to update `unlinked` when dependencies change + $: { + if (collection && collection?.start_date && collection.end_date) { + unlinked = adventure.visits.every((visit) => { + // Check if visit dates exist + if (!visit.start_date || !visit.end_date) return true; // Consider "unlinked" for incomplete visit data + + // Check if collection dates are completely outside this visit's range + const isBeforeVisit = collection.end_date && collection.end_date < visit.start_date; + const isAfterVisit = collection.start_date && collection.start_date > visit.end_date; + + return isBeforeVisit || isAfterVisit; + }); + } + } + async function deleteAdventure() { let res = await fetch(`/adventures/${adventure.id}?/delete`, { method: 'POST', @@ -140,6 +158,9 @@ {adventure.is_public ? $t('adventures.public') : $t('adventures.private')} + {#if unlinked} +
{$t('adventures.out_of_range')}
+ {/if} {#if adventure.location && adventure.location !== ''}
diff --git a/frontend/src/lib/components/AdventureModal.svelte b/frontend/src/lib/components/AdventureModal.svelte index c0dc277..ff9770d 100644 --- a/frontend/src/lib/components/AdventureModal.svelte +++ b/frontend/src/lib/components/AdventureModal.svelte @@ -32,6 +32,7 @@ import { appVersion } from '$lib/config'; import CategoryDropdown from './CategoryDropdown.svelte'; import { findFirstValue } from '$lib'; + import MarkdownEditor from './MarkdownEditor.svelte'; let wikiError: string = ''; @@ -577,15 +578,10 @@

- +
-
@@ -687,7 +683,7 @@
-
diff --git a/frontend/src/lib/components/CollectionModal.svelte b/frontend/src/lib/components/CollectionModal.svelte new file mode 100644 index 0000000..e5cb294 --- /dev/null +++ b/frontend/src/lib/components/CollectionModal.svelte @@ -0,0 +1,204 @@ + + + + + + + diff --git a/frontend/src/lib/components/EditCollection.svelte b/frontend/src/lib/components/EditCollection.svelte deleted file mode 100644 index 6c96bc8..0000000 --- a/frontend/src/lib/components/EditCollection.svelte +++ /dev/null @@ -1,209 +0,0 @@ - - - - - - - diff --git a/frontend/src/lib/components/EditTransportation.svelte b/frontend/src/lib/components/EditTransportation.svelte deleted file mode 100644 index f3e4ca0..0000000 --- a/frontend/src/lib/components/EditTransportation.svelte +++ /dev/null @@ -1,281 +0,0 @@ - - - - - - - diff --git a/frontend/src/lib/components/MarkdownEditor.svelte b/frontend/src/lib/components/MarkdownEditor.svelte new file mode 100644 index 0000000..a280d7e --- /dev/null +++ b/frontend/src/lib/components/MarkdownEditor.svelte @@ -0,0 +1,81 @@ + + +
+ + +
+ +
+ + {#if !is_preview} + + {/if} + + + {#if is_preview} +
+ {@html renderMarkdown(text || '')} +
+ {/if} +
+ + diff --git a/frontend/src/lib/components/NewCollection.svelte b/frontend/src/lib/components/NewCollection.svelte deleted file mode 100644 index 5ba50ba..0000000 --- a/frontend/src/lib/components/NewCollection.svelte +++ /dev/null @@ -1,180 +0,0 @@ - - - - - - - - diff --git a/frontend/src/lib/components/NewTransportation.svelte b/frontend/src/lib/components/NewTransportation.svelte deleted file mode 100644 index e841105..0000000 --- a/frontend/src/lib/components/NewTransportation.svelte +++ /dev/null @@ -1,256 +0,0 @@ - - - - - - - diff --git a/frontend/src/lib/components/NoteCard.svelte b/frontend/src/lib/components/NoteCard.svelte index 44ea314..9b6ba62 100644 --- a/frontend/src/lib/components/NoteCard.svelte +++ b/frontend/src/lib/components/NoteCard.svelte @@ -8,11 +8,27 @@ import Launch from '~icons/mdi/launch'; import TrashCan from '~icons/mdi/trash-can'; import Calendar from '~icons/mdi/calendar'; + import DeleteWarning from './DeleteWarning.svelte'; export let note: Note; export let user: User | null = null; export let collection: Collection | null = null; + let isWarningModalOpen: boolean = false; + let unlinked: boolean = false; + + $: { + if (collection?.start_date && collection.end_date) { + const startOutsideRange = + note.date && collection.start_date < note.date && collection.end_date < note.date; + + const endOutsideRange = + note.date && collection.start_date > note.date && collection.end_date > note.date; + + unlinked = !!(startOutsideRange || endOutsideRange || !note.date); + } + } + function editNote() { dispatch('edit', note); } @@ -23,6 +39,7 @@ }); if (res.ok) { addToast('success', $t('notes.note_deleted')); + isWarningModalOpen = false; dispatch('delete', note.id); } else { addToast($t('notes.note_delete_error'), 'error'); @@ -30,6 +47,17 @@ } +{#if isWarningModalOpen} + (isWarningModalOpen = false)} + on:confirm={deleteNote} + /> +{/if} +
@@ -40,6 +68,9 @@
{$t('adventures.note')}
+ {#if unlinked} +
{$t('adventures.out_of_range')}
+ {/if} {#if note.links && note.links.length > 0}

{note.links.length} @@ -59,12 +90,12 @@ - {#if note.user_id == user?.uuid || (collection && user && collection.shared_with.includes(user.uuid))} + {#if note.user_id == user?.uuid || (collection && user && collection.shared_with && collection.shared_with.includes(user.uuid))} (isWarningModalOpen = true)}> {/if} diff --git a/frontend/src/lib/components/NoteModal.svelte b/frontend/src/lib/components/NoteModal.svelte index af9648c..8344984 100644 --- a/frontend/src/lib/components/NoteModal.svelte +++ b/frontend/src/lib/components/NoteModal.svelte @@ -5,12 +5,25 @@ const dispatch = createEventDispatcher(); import { onMount } from 'svelte'; import { t } from 'svelte-i18n'; + import MarkdownEditor from './MarkdownEditor.svelte'; let modal: HTMLDialogElement; + import { marked } from 'marked'; // Import the markdown parser + + const renderMarkdown = (markdown: string) => { + return marked(markdown); + }; export let note: Note | null = null; export let collection: Collection; export let user: User | null = null; + let constrainDates: boolean = false; + + let isReadOnly = + !(note && user?.uuid == note?.user_id) && + !(user && collection && collection.shared_with && collection.shared_with.includes(user.uuid)) && + !!note; + let warning: string | null = ''; let newLink: string = ''; @@ -105,85 +118,137 @@

- - + + + {#if warning} - diff --git a/frontend/src/lib/components/PointSelectionModal.svelte b/frontend/src/lib/components/PointSelectionModal.svelte index f7f1333..56f734f 100644 --- a/frontend/src/lib/components/PointSelectionModal.svelte +++ b/frontend/src/lib/components/PointSelectionModal.svelte @@ -111,7 +111,7 @@

Choose a Point

+
+

{transportation.name}

+
+
+ {$t(`transportation.modes.${transportation.type}`)} +
+ {#if transportation.type == 'plane' && transportation.flight_number} +
{transportation.flight_number}
+ {/if} +
-
- {#if transportation.date} -

{new Date(transportation.date).toLocaleString(undefined, { timeZone: 'UTC' })}

+ {#if unlinked} +
{$t('adventures.out_of_range')}
+ {/if} + + +
+ {#if transportation.from_location} +
+ {$t('adventures.from')}: +

{transportation.from_location}

+
{/if} - {#if transportation.end_date} - -

{new Date(transportation.end_date).toLocaleString(undefined, { timeZone: 'UTC' })}

+ {#if transportation.date} +
+ {$t('adventures.start')}: +

{new Date(transportation.date).toLocaleString(undefined, { timeZone: 'UTC' })}

+
{/if}
- {#if transportation.user_id == user?.uuid || (collection && user && collection.shared_with.includes(user.uuid))} + +
+ {#if transportation.to_location} + +
+ {$t('adventures.to')}: + +

{transportation.to_location}

+
+ {/if} + {#if transportation.end_date} +
+ {$t('adventures.end')}: +

{new Date(transportation.end_date).toLocaleString(undefined, { timeZone: 'UTC' })}

+
+ {/if} +
+ + + {#if transportation.user_id == user?.uuid || (collection && user && collection.shared_with && collection.shared_with.includes(user.uuid))}
- - +
{/if} diff --git a/frontend/src/lib/components/TransportationModal.svelte b/frontend/src/lib/components/TransportationModal.svelte new file mode 100644 index 0000000..9258b10 --- /dev/null +++ b/frontend/src/lib/components/TransportationModal.svelte @@ -0,0 +1,605 @@ + + + + + + + diff --git a/frontend/src/lib/components/UserCard.svelte b/frontend/src/lib/components/UserCard.svelte index 3deb52d..aefa17e 100644 --- a/frontend/src/lib/components/UserCard.svelte +++ b/frontend/src/lib/components/UserCard.svelte @@ -17,34 +17,46 @@ class="card w-full max-w-xs sm:max-w-sm md:max-w-md lg:max-w-md xl:max-w-md bg-neutral text-neutral-content shadow-xl" >
-
+ +
{#if user.profile_pic} -
-
+
+
{user.username}
{/if} -

{user.first_name} {user.last_name}

+ +

+ {user.first_name} + {user.last_name} +

+

{user.username}

+ + + {#if user.is_staff} +
Admin
+ {/if}
-

{user.username}

- {#if user.is_staff} -
Admin
- {/if} - -
- -

+ + +

+ +

{user.date_joined ? 'Joined ' + new Date(user.date_joined).toLocaleDateString() : ''}

-
+ + +
{#if !sharing} - + {:else if shared_with && !shared_with.includes(user.uuid)} - + {:else} - + {/if}
diff --git a/frontend/src/lib/config.ts b/frontend/src/lib/config.ts index 15ee7ef..3b9fde7 100644 --- a/frontend/src/lib/config.ts +++ b/frontend/src/lib/config.ts @@ -1,4 +1,4 @@ -export let appVersion = 'Web v0.7.1'; +export let appVersion = 'v0.7.1'; export let versionChangelog = 'https://github.com/seanmorley15/AdventureLog/releases/tag/v0.7.1'; export let appTitle = 'AdventureLog'; -export let copyrightYear = '2024'; +export let copyrightYear = '2023-2025'; diff --git a/frontend/src/lib/index.ts b/frontend/src/lib/index.ts index 1d1a3bd..b2201ca 100644 --- a/frontend/src/lib/index.ts +++ b/frontend/src/lib/index.ts @@ -289,6 +289,37 @@ export function getAdventureTypeLabel(type: string) { } export function getRandomBackground() { + const today = new Date(); + + // Special dates for specific backgrounds + // New Years week + + const newYearsStart = new Date(today.getFullYear() - 1, 11, 31); + newYearsStart.setHours(0, 0, 0, 0); + const newYearsEnd = new Date(today.getFullYear(), 0, 7); + newYearsEnd.setHours(23, 59, 59, 999); + if (today >= newYearsStart && today <= newYearsEnd) { + return { + url: 'backgrounds/adventurelog_new_year.webp', + author: 'Roven Images', + location: "Happy New Year's from the AdventureLog team!" + } as Background; + } + + // Christmas 12/24 - 12/25 + const christmasStart = new Date(today.getFullYear(), 11, 24); + christmasStart.setHours(0, 0, 0, 0); + const christmasEnd = new Date(today.getFullYear(), 11, 25); + christmasEnd.setHours(23, 59, 59, 999); + + if (today >= christmasStart && today <= christmasEnd) { + return { + url: 'backgrounds/adventurelog_christmas.webp', + author: 'Annie Spratt', + location: 'Merry Christmas from the AdventureLog team!' + } as Background; + } + const randomIndex = Math.floor(Math.random() * randomBackgrounds.backgrounds.length); return randomBackgrounds.backgrounds[randomIndex] as Background; } diff --git a/frontend/src/lib/json/backgrounds.json b/frontend/src/lib/json/backgrounds.json index d1b60c5..be43594 100644 --- a/frontend/src/lib/json/backgrounds.json +++ b/frontend/src/lib/json/backgrounds.json @@ -19,6 +19,11 @@ "url": "backgrounds/adventurelog_showcase_4.webp", "author": "Sean Morley", "location": "Great Sand Dunes National Park, Colorado, USA" + }, + { + "url": "backgrounds/adventurelog_showcase_5.webp", + "author": "Sean Morley", + "location": "Hoboken, New Jersey, USA" } ] } diff --git a/frontend/src/lib/types.ts b/frontend/src/lib/types.ts index 181017b..9dac462 100644 --- a/frontend/src/lib/types.ts +++ b/frontend/src/lib/types.ts @@ -86,14 +86,14 @@ export type Collection = { description: string; is_public: boolean; adventures: Adventure[]; - created_at?: string; - start_date?: string; - end_date?: string; + created_at?: string | null; + start_date: string | null; + end_date: string | null; transportations?: Transportation[]; notes?: Note[]; checklists?: Checklist[]; is_archived?: boolean; - shared_with: string[]; + shared_with: string[] | undefined; link?: string | null; }; @@ -127,8 +127,12 @@ export type Transportation = { flight_number: string | null; from_location: string | null; to_location: string | null; + origin_latitude: number | null; + origin_longitude: number | null; + destination_latitude: number | null; + destination_longitude: number | null; is_public: boolean; - collection: Collection | null; + collection: Collection | null | string; created_at: string; // ISO 8601 date string updated_at: string; // ISO 8601 date string }; diff --git a/frontend/src/locales/de.json b/frontend/src/locales/de.json index 25e2b41..c2485ca 100644 --- a/frontend/src/locales/de.json +++ b/frontend/src/locales/de.json @@ -195,7 +195,27 @@ "emoji_picker": "Emoji-Picker", "hide": "Verstecken", "show": "Zeigen", - "download_calendar": "Kalender herunterladen" + "download_calendar": "Kalender herunterladen", + "md_instructions": "Schreiben Sie hier Ihren Abschlag...", + "preview": "Vorschau", + "checklist_delete_confirm": "Sind Sie sicher, dass Sie diese Checkliste löschen möchten? \nDiese Aktion kann nicht rückgängig gemacht werden.", + "clear_location": "Standort löschen", + "date_information": "Datumsinformationen", + "delete_checklist": "Checkliste löschen", + "delete_note": "Notiz löschen", + "delete_transportation": "Transport löschen", + "end": "Ende", + "ending_airport": "Endflughafen", + "flight_information": "Fluginformationen", + "from": "Aus", + "no_location_found": "Kein Standort gefunden", + "note_delete_confirm": "Sind Sie sicher, dass Sie diese Notiz löschen möchten? \nDiese Aktion kann nicht rückgängig gemacht werden.", + "out_of_range": "Nicht im Datumsbereich der Reiseroute", + "show_region_labels": "Regionsbeschriftungen anzeigen", + "start": "Start", + "starting_airport": "Startflughafen", + "to": "Zu", + "transportation_delete_confirm": "Sind Sie sicher, dass Sie diesen Transport löschen möchten? \nDiese Aktion kann nicht rückgängig gemacht werden." }, "home": { "desc_1": "Entdecken, planen und erkunden Sie mit Leichtigkeit", @@ -362,7 +382,9 @@ "item_cannot_be_empty": "Das Element darf nicht leer sein", "items": "Artikel", "new_item": "Neuer Artikel", - "save": "Speichern" + "save": "Speichern", + "checklist_viewer": "Checklisten-Viewer", + "new_checklist": "Neue Checkliste" }, "collection": { "collection_created": "Sammlung erfolgreich erstellt!", @@ -371,7 +393,8 @@ "edit_collection": "Sammlung bearbeiten", "error_creating_collection": "Fehler beim Erstellen der Sammlung", "error_editing_collection": "Fehler beim Bearbeiten der Sammlung", - "new_collection": "Neue Kollektion" + "new_collection": "Neue Kollektion", + "public_collection": "Öffentliche Sammlung" }, "notes": { "add_a_link": "Fügen Sie einen Link hinzu", @@ -384,7 +407,8 @@ "note_public": "Diese Notiz ist öffentlich, da sie sich in einer öffentlichen Sammlung befindet.", "open": "Offen", "save": "Speichern", - "invalid_url": "Ungültige URL" + "invalid_url": "Ungültige URL", + "note_viewer": "Notizenbetrachter" }, "transportation": { "date_and_time": "Datum", diff --git a/frontend/src/locales/en.json b/frontend/src/locales/en.json index cd9b824..d847826 100644 --- a/frontend/src/locales/en.json +++ b/frontend/src/locales/en.json @@ -64,6 +64,12 @@ "no_image_found": "No image found", "collection_link_error": "Error linking adventure to collection", "adventure_delete_confirm": "Are you sure you want to delete this adventure? This action cannot be undone.", + "checklist_delete_confirm": "Are you sure you want to delete this checklist? This action cannot be undone.", + "note_delete_confirm": "Are you sure you want to delete this note? This action cannot be undone.", + "transportation_delete_confirm": "Are you sure you want to delete this transportation? This action cannot be undone.", + "delete_checklist": "Delete Checklist", + "delete_note": "Delete Note", + "delete_transportation": "Delete Transportation", "open_details": "Open Details", "edit_adventure": "Edit Adventure", "remove_from_collection": "Remove from Collection", @@ -97,6 +103,7 @@ "rating": "Rating", "my_images": "My Images", "add_an_activity": "Add an activity", + "show_region_labels": "Show Region Labels", "no_images": "No Images", "upload_images_here": "Upload images here", "share_adventure": "Share this Adventure!", @@ -216,8 +223,21 @@ "copy_failed": "Copy failed", "show": "Show", "hide": "Hide", + "clear_location": "Clear Location", + "starting_airport": "Starting Airport", + "ending_airport": "Ending Airport", + "no_location_found": "No location found", + "from": "From", + "to": "To", + "start": "Start", + "end": "End", "emoji_picker": "Emoji Picker", "download_calendar": "Download Calendar", + "date_information": "Date Information", + "flight_information": "Flight Information", + "out_of_range": "Not in itinerary date range", + "preview": "Preview", + "md_instructions": "Write your markdown here...", "days": "days", "activities": { "general": "General 🌍", @@ -271,7 +291,7 @@ "public_profile": "Public Profile", "public_tooltip": "With a public profile, users can share collections with you and view your profile on the users page.", "email_required": "Email is required", - "new_password": "New Password", + "new_password": "New Password (6+ characters)", "both_passwords_required": "Both passwords are required", "reset_failed": "Failed to reset password" }, @@ -356,7 +376,8 @@ "create": "Create", "collection_edit_success": "Collection edited successfully!", "error_editing_collection": "Error editing collection", - "edit_collection": "Edit Collection" + "edit_collection": "Edit Collection", + "public_collection": "Public Collection" }, "notes": { "note_deleted": "Note deleted successfully!", @@ -364,6 +385,7 @@ "open": "Open", "failed_to_save": "Failed to save note", "note_editor": "Note Editor", + "note_viewer": "Note Viewer", "editing_note": "Editing note", "content": "Content", "save": "Save", @@ -376,7 +398,9 @@ "checklist_delete_error": "Error deleting checklist", "failed_to_save": "Failed to save checklist", "checklist_editor": "Checklist Editor", + "checklist_viewer": "Checklist Viewer", "editing_checklist": "Editing checklist", + "new_checklist": "New Checklist", "item": "Item", "items": "Items", "add_item": "Add Item", diff --git a/frontend/src/locales/es.json b/frontend/src/locales/es.json index 42da49e..1164d25 100644 --- a/frontend/src/locales/es.json +++ b/frontend/src/locales/es.json @@ -242,7 +242,27 @@ "emoji_picker": "Selector de emojis", "hide": "Esconder", "show": "Espectáculo", - "download_calendar": "Descargar Calendario" + "download_calendar": "Descargar Calendario", + "md_instructions": "Escriba su descuento aquí...", + "preview": "Avance", + "checklist_delete_confirm": "¿Está seguro de que desea eliminar esta lista de verificación? \nEsta acción no se puede deshacer.", + "clear_location": "Borrar ubicación", + "date_information": "Información de fecha", + "delete_checklist": "Eliminar lista de verificación", + "delete_note": "Eliminar nota", + "delete_transportation": "Eliminar transporte", + "end": "Fin", + "ending_airport": "Aeropuerto final", + "flight_information": "Información de vuelo", + "from": "De", + "no_location_found": "No se encontró ninguna ubicación", + "note_delete_confirm": "¿Estás seguro de que deseas eliminar esta nota? \nEsta acción no se puede deshacer.", + "out_of_range": "No en el rango de fechas del itinerario", + "show_region_labels": "Mostrar etiquetas de región", + "start": "Comenzar", + "starting_airport": "Aeropuerto de inicio", + "to": "A", + "transportation_delete_confirm": "¿Está seguro de que desea eliminar este transporte? \nEsta acción no se puede deshacer." }, "worldtravel": { "all": "Todo", @@ -362,7 +382,9 @@ "item_cannot_be_empty": "El artículo no puede estar vacío", "items": "Elementos", "new_item": "Nuevo artículo", - "save": "Ahorrar" + "save": "Ahorrar", + "checklist_viewer": "Visor de lista de verificación", + "new_checklist": "Nueva lista de verificación" }, "collection": { "collection_created": "¡Colección creada con éxito!", @@ -371,7 +393,8 @@ "edit_collection": "Editar colección", "error_creating_collection": "Error al crear la colección", "error_editing_collection": "Error al editar la colección", - "new_collection": "Nueva colección" + "new_collection": "Nueva colección", + "public_collection": "Colección pública" }, "notes": { "add_a_link": "Agregar un enlace", @@ -384,7 +407,8 @@ "note_public": "Esta nota es pública porque está en una colección pública.", "open": "Abierto", "save": "Ahorrar", - "invalid_url": "URL no válida" + "invalid_url": "URL no válida", + "note_viewer": "Visor de notas" }, "transportation": { "date_and_time": "Fecha", diff --git a/frontend/src/locales/fr.json b/frontend/src/locales/fr.json index 9a532de..c66bf03 100644 --- a/frontend/src/locales/fr.json +++ b/frontend/src/locales/fr.json @@ -195,7 +195,27 @@ "emoji_picker": "Sélecteur d'émoticônes", "hide": "Cacher", "show": "Montrer", - "download_calendar": "Télécharger le calendrier" + "download_calendar": "Télécharger le calendrier", + "md_instructions": "Écrivez votre démarque ici...", + "preview": "Aperçu", + "checklist_delete_confirm": "Êtes-vous sûr de vouloir supprimer cette liste de contrôle ? \nCette action ne peut pas être annulée.", + "clear_location": "Effacer l'emplacement", + "date_information": "Informations sur les dates", + "delete_checklist": "Supprimer la liste de contrôle", + "delete_note": "Supprimer la note", + "delete_transportation": "Supprimer le transport", + "end": "Fin", + "ending_airport": "Aéroport de fin", + "flight_information": "Informations sur le vol", + "from": "Depuis", + "no_location_found": "Aucun emplacement trouvé", + "note_delete_confirm": "Êtes-vous sûr de vouloir supprimer cette note ? \nCette action ne peut pas être annulée.", + "out_of_range": "Pas dans la plage de dates de l'itinéraire", + "show_region_labels": "Afficher les étiquettes de région", + "start": "Commencer", + "starting_airport": "Aéroport de départ", + "to": "À", + "transportation_delete_confirm": "Etes-vous sûr de vouloir supprimer ce transport ? \nCette action ne peut pas être annulée." }, "home": { "desc_1": "Découvrez, planifiez et explorez en toute simplicité", @@ -362,7 +382,9 @@ "item_cannot_be_empty": "L'élément ne peut pas être vide", "items": "Articles", "new_item": "Nouvel article", - "save": "Sauvegarder" + "save": "Sauvegarder", + "checklist_viewer": "Visionneuse de liste de contrôle", + "new_checklist": "Nouvelle liste de contrôle" }, "collection": { "collection_created": "Collection créée avec succès !", @@ -371,7 +393,8 @@ "edit_collection": "Modifier la collection", "error_creating_collection": "Erreur lors de la création de la collection", "error_editing_collection": "Erreur lors de la modification de la collection", - "new_collection": "Nouvelle collection" + "new_collection": "Nouvelle collection", + "public_collection": "Collection publique" }, "notes": { "add_a_link": "Ajouter un lien", @@ -384,7 +407,8 @@ "note_public": "Cette note est publique car elle fait partie d'une collection publique.", "open": "Ouvrir", "save": "Sauvegarder", - "invalid_url": "URL invalide" + "invalid_url": "URL invalide", + "note_viewer": "Visionneuse de notes" }, "transportation": { "date_time": "Date de début", diff --git a/frontend/src/locales/it.json b/frontend/src/locales/it.json index fa0b1fb..21bee77 100644 --- a/frontend/src/locales/it.json +++ b/frontend/src/locales/it.json @@ -195,7 +195,27 @@ "emoji_picker": "Selettore di emoji", "hide": "Nascondere", "show": "Spettacolo", - "download_calendar": "Scarica Calendario" + "download_calendar": "Scarica Calendario", + "md_instructions": "Scrivi qui il tuo ribasso...", + "preview": "Anteprima", + "checklist_delete_confirm": "Sei sicuro di voler eliminare questa lista di controllo? \nQuesta azione non può essere annullata.", + "clear_location": "Cancella posizione", + "date_information": "Informazioni sulla data", + "delete_checklist": "Elimina lista di controllo", + "delete_note": "Elimina nota", + "delete_transportation": "Elimina trasporto", + "end": "FINE", + "ending_airport": "Fine dell'aeroporto", + "flight_information": "Informazioni sul volo", + "from": "Da", + "no_location_found": "Nessuna posizione trovata", + "note_delete_confirm": "Sei sicuro di voler eliminare questa nota? \nQuesta azione non può essere annullata.", + "out_of_range": "Non nell'intervallo di date dell'itinerario", + "show_region_labels": "Mostra etichette regione", + "start": "Inizio", + "starting_airport": "Inizio aeroporto", + "to": "A", + "transportation_delete_confirm": "Sei sicuro di voler eliminare questo trasporto? \nQuesta azione non può essere annullata." }, "home": { "desc_1": "Scopri, pianifica ed esplora con facilità", @@ -362,7 +382,9 @@ "item_cannot_be_empty": "L'articolo non può essere vuoto", "items": "Elementi", "save": "Salva", - "new_item": "Nuovo articolo" + "new_item": "Nuovo articolo", + "checklist_viewer": "Visualizzatore della lista di controllo", + "new_checklist": "Nuova lista di controllo" }, "collection": { "edit_collection": "Modifica raccolta", @@ -371,7 +393,8 @@ "new_collection": "Nuova collezione", "collection_created": "Collezione creata con successo!", "collection_edit_success": "Raccolta modificata con successo!", - "create": "Creare" + "create": "Creare", + "public_collection": "Collezione pubblica" }, "notes": { "add_a_link": "Aggiungi un collegamento", @@ -384,7 +407,8 @@ "note_public": "Questa nota è pubblica perché è in una collezione pubblica.", "open": "Aprire", "save": "Salva", - "invalid_url": "URL non valido" + "invalid_url": "URL non valido", + "note_viewer": "Visualizzatore di note" }, "transportation": { "date_and_time": "Data", diff --git a/frontend/src/locales/nl.json b/frontend/src/locales/nl.json index 90ac331..c2a59a5 100644 --- a/frontend/src/locales/nl.json +++ b/frontend/src/locales/nl.json @@ -195,7 +195,27 @@ "emoji_picker": "Emoji-kiezer", "hide": "Verbergen", "show": "Show", - "download_calendar": "Agenda downloaden" + "download_calendar": "Agenda downloaden", + "md_instructions": "Schrijf hier uw korting...", + "preview": "Voorbeeld", + "checklist_delete_confirm": "Weet u zeker dat u deze checklist wilt verwijderen? \nDeze actie kan niet ongedaan worden gemaakt.", + "clear_location": "Locatie wissen", + "date_information": "Datuminformatie", + "delete_checklist": "Controlelijst verwijderen", + "delete_note": "Notitie verwijderen", + "delete_transportation": "Transport verwijderen", + "end": "Einde", + "flight_information": "Vluchtinformatie", + "from": "Van", + "no_location_found": "Geen locatie gevonden", + "note_delete_confirm": "Weet u zeker dat u deze notitie wilt verwijderen? \nDeze actie kan niet ongedaan worden gemaakt.", + "out_of_range": "Niet binnen het datumbereik van het reisplan", + "show_region_labels": "Toon regiolabels", + "start": "Begin", + "starting_airport": "Startende luchthaven", + "to": "Naar", + "transportation_delete_confirm": "Weet u zeker dat u dit transport wilt verwijderen? \nDeze actie kan niet ongedaan worden gemaakt.", + "ending_airport": "Einde luchthaven" }, "home": { "desc_1": "Ontdek, plan en verken met gemak", @@ -362,7 +382,9 @@ "item_cannot_be_empty": "Artikel mag niet leeg zijn", "items": "Artikelen", "new_item": "Nieuw artikel", - "save": "Opslaan" + "save": "Opslaan", + "checklist_viewer": "Controlelijstviewer", + "new_checklist": "Nieuwe checklist" }, "collection": { "collection_created": "Collectie succesvol aangemaakt!", @@ -371,7 +393,8 @@ "edit_collection": "Collectie bewerken", "error_creating_collection": "Fout bij aanmaken collectie", "error_editing_collection": "Fout bij bewerken collectie", - "new_collection": "Nieuwe collectie" + "new_collection": "Nieuwe collectie", + "public_collection": "Openbare collectie" }, "notes": { "add_a_link": "Voeg een link toe", @@ -384,7 +407,8 @@ "note_public": "Deze opmerking is openbaar omdat deze zich in een openbare collectie bevindt.", "open": "Open", "save": "Opslaan", - "invalid_url": "Ongeldige URL" + "invalid_url": "Ongeldige URL", + "note_viewer": "Notitieviewer" }, "transportation": { "date_and_time": "Datum", diff --git a/frontend/src/locales/pl.json b/frontend/src/locales/pl.json index db3e758..eac9634 100644 --- a/frontend/src/locales/pl.json +++ b/frontend/src/locales/pl.json @@ -242,7 +242,27 @@ "emoji_picker": "Wybór emoji", "hide": "Ukrywać", "show": "Pokazywać", - "download_calendar": "Pobierz Kalendarz" + "download_calendar": "Pobierz Kalendarz", + "md_instructions": "Napisz tutaj swoją przecenę...", + "preview": "Zapowiedź", + "checklist_delete_confirm": "Czy na pewno chcesz usunąć tę listę kontrolną? \nTej akcji nie można cofnąć.", + "clear_location": "Wyczyść lokalizację", + "date_information": "Informacje o dacie", + "delete_checklist": "Usuń listę kontrolną", + "delete_note": "Usuń notatkę", + "delete_transportation": "Usuń transport", + "end": "Koniec", + "ending_airport": "Kończy się lotnisko", + "flight_information": "Informacje o locie", + "from": "Z", + "no_location_found": "Nie znaleziono lokalizacji", + "note_delete_confirm": "Czy na pewno chcesz usunąć tę notatkę? \nTej akcji nie można cofnąć.", + "out_of_range": "Nie mieści się w zakresie dat planu podróży", + "show_region_labels": "Pokaż etykiety regionów", + "start": "Start", + "starting_airport": "Początkowe lotnisko", + "to": "Do", + "transportation_delete_confirm": "Czy na pewno chcesz usunąć ten transport? \nTej akcji nie można cofnąć." }, "worldtravel": { "country_list": "Lista krajów", @@ -356,7 +376,8 @@ "create": "Utwórz", "collection_edit_success": "Kolekcja została pomyślnie edytowana!", "error_editing_collection": "Błąd podczas edytowania kolekcji", - "edit_collection": "Edytuj kolekcję" + "edit_collection": "Edytuj kolekcję", + "public_collection": "Kolekcja publiczna" }, "notes": { "note_deleted": "Notatka została pomyślnie usunięta!", @@ -369,7 +390,8 @@ "save": "Zapisz", "note_public": "Ta notatka jest publiczna, ponieważ znajduje się w publicznej kolekcji.", "add_a_link": "Dodaj link", - "invalid_url": "Nieprawidłowy URL" + "invalid_url": "Nieprawidłowy URL", + "note_viewer": "Przeglądarka notatek" }, "checklist": { "checklist_deleted": "Lista kontrolna została pomyślnie usunięta!", @@ -384,7 +406,9 @@ "save": "Zapisz", "checklist_public": "Ta lista kontrolna jest publiczna, ponieważ znajduje się w publicznej kolekcji.", "item_cannot_be_empty": "Element nie może być pusty", - "item_already_exists": "Element już istnieje" + "item_already_exists": "Element już istnieje", + "checklist_viewer": "Przeglądarka listy kontrolnej", + "new_checklist": "Nowa lista kontrolna" }, "transportation": { "transportation_deleted": "Transport został pomyślnie usunięty!", diff --git a/frontend/src/locales/sv.json b/frontend/src/locales/sv.json index aae55d5..495b0ba 100644 --- a/frontend/src/locales/sv.json +++ b/frontend/src/locales/sv.json @@ -195,7 +195,27 @@ "emoji_picker": "Emoji-väljare", "hide": "Dölja", "show": "Visa", - "download_calendar": "Ladda ner kalender" + "download_calendar": "Ladda ner kalender", + "md_instructions": "Skriv din avskrivning här...", + "preview": "Förhandsvisning", + "checklist_delete_confirm": "Är du säker på att du vill ta bort den här checklistan? \nDenna åtgärd kan inte ångras.", + "clear_location": "Rensa plats", + "date_information": "Datuminformation", + "delete_checklist": "Ta bort checklista", + "delete_note": "Ta bort anteckning", + "delete_transportation": "Ta bort Transport", + "end": "Avsluta", + "ending_airport": "Slutar flygplats", + "flight_information": "Flyginformation", + "from": "Från", + "no_location_found": "Ingen plats hittades", + "note_delete_confirm": "Är du säker på att du vill ta bort den här anteckningen? \nDenna åtgärd kan inte ångras.", + "out_of_range": "Inte inom resplanens datumintervall", + "show_region_labels": "Visa regionetiketter", + "start": "Start", + "starting_airport": "Startar flygplats", + "to": "Till", + "transportation_delete_confirm": "Är du säker på att du vill ta bort denna transport? \nDenna åtgärd kan inte ångras." }, "home": { "desc_1": "Upptäck, planera och utforska med lätthet", @@ -362,7 +382,9 @@ "item_cannot_be_empty": "Objektet får inte vara tomt", "items": "Föremål", "new_item": "Nytt föremål", - "save": "Spara" + "save": "Spara", + "checklist_viewer": "Checklista Viewer", + "new_checklist": "Ny checklista" }, "collection": { "collection_created": "Samlingen har skapats!", @@ -371,7 +393,8 @@ "edit_collection": "Redigera samling", "error_creating_collection": "Det gick inte att skapa samlingen", "error_editing_collection": "Fel vid redigering av samling", - "new_collection": "Ny samling" + "new_collection": "Ny samling", + "public_collection": "Offentlig samling" }, "notes": { "add_a_link": "Lägg till en länk", @@ -384,7 +407,8 @@ "note_public": "Den här anteckningen är offentlig eftersom den finns i en offentlig samling.", "open": "Öppna", "save": "Spara", - "invalid_url": "Ogiltig URL" + "invalid_url": "Ogiltig URL", + "note_viewer": "Note Viewer" }, "transportation": { "date_and_time": "Datum", diff --git a/frontend/src/locales/zh.json b/frontend/src/locales/zh.json index 56cf01f..0a686a2 100644 --- a/frontend/src/locales/zh.json +++ b/frontend/src/locales/zh.json @@ -195,7 +195,27 @@ "emoji_picker": "表情符号选择器", "hide": "隐藏", "show": "展示", - "download_calendar": "下载日历" + "download_calendar": "下载日历", + "md_instructions": "在这里写下你的标记...", + "preview": "预览", + "checklist_delete_confirm": "您确定要删除此清单吗?\n此操作无法撤消。", + "clear_location": "明确的位置", + "date_information": "日期信息", + "delete_checklist": "删除清单", + "delete_note": "删除注释", + "delete_transportation": "删除交通", + "end": "结尾", + "ending_airport": "结束机场", + "flight_information": "航班信息", + "from": "从", + "no_location_found": "没有找到位置", + "note_delete_confirm": "您确定要删除此注释吗?\n此操作无法撤消。", + "out_of_range": "不在行程日期范围内", + "show_region_labels": "显示区域标签", + "start": "开始", + "starting_airport": "出发机场", + "to": "到", + "transportation_delete_confirm": "您确定要删除此交通工具吗?\n此操作无法撤消。" }, "home": { "desc_1": "轻松发现、规划和探索", @@ -362,7 +382,9 @@ "item_cannot_be_empty": "项目不能为空", "items": "项目", "new_item": "新商品", - "save": "节省" + "save": "节省", + "checklist_viewer": "清单查看器", + "new_checklist": "新清单" }, "collection": { "collection_created": "收藏创建成功!", @@ -371,7 +393,8 @@ "edit_collection": "编辑收藏", "error_creating_collection": "创建集合时出错", "error_editing_collection": "编辑集合时出错", - "new_collection": "新系列" + "new_collection": "新系列", + "public_collection": "公共收藏" }, "notes": { "add_a_link": "添加链接", @@ -384,7 +407,8 @@ "note_public": "该笔记是公开的,因为它属于公共收藏。", "open": "打开", "save": "节省", - "invalid_url": "无效网址" + "invalid_url": "无效网址", + "note_viewer": "笔记查看器" }, "transportation": { "date_and_time": "日期", diff --git a/frontend/src/routes/adventures/[id]/+page.svelte b/frontend/src/routes/adventures/[id]/+page.svelte index 9e357ea..f151bb4 100644 --- a/frontend/src/routes/adventures/[id]/+page.svelte +++ b/frontend/src/routes/adventures/[id]/+page.svelte @@ -6,6 +6,11 @@ import Lost from '$lib/assets/undraw_lost.svg'; import { DefaultMarker, MapLibre, Popup } from 'svelte-maplibre'; import { t } from 'svelte-i18n'; + import { marked } from 'marked'; // Import the markdown parser + + const renderMarkdown = (markdown: string) => { + return marked(markdown); + }; export let data: PageData; console.log(data); @@ -244,11 +249,12 @@ {/if}
{#if adventure.description} -
-

- {adventure.description} -

-
+

+
+ {@html renderMarkdown(adventure.description)} +
{/if}
@@ -323,7 +329,7 @@

{$t('adventures.adventure_calendar')}

diff --git a/frontend/src/routes/collections/+page.svelte b/frontend/src/routes/collections/+page.svelte index e5789c9..171a5d4 100644 --- a/frontend/src/routes/collections/+page.svelte +++ b/frontend/src/routes/collections/+page.svelte @@ -2,8 +2,8 @@ import { enhance } from '$app/forms'; import { goto } from '$app/navigation'; import CollectionCard from '$lib/components/CollectionCard.svelte'; - import EditCollection from '$lib/components/EditCollection.svelte'; - import NewCollection from '$lib/components/NewCollection.svelte'; + import CollectionLink from '$lib/components/CollectionLink.svelte'; + import CollectionModal from '$lib/components/CollectionModal.svelte'; import NotFound from '$lib/components/NotFound.svelte'; import type { Collection } from '$lib/types'; import { t } from 'svelte-i18n'; @@ -17,10 +17,10 @@ let currentSort = { attribute: 'name', order: 'asc' }; - let isShowingCreateModal: boolean = false; let newType: string = ''; let resultsPerPage: number = 25; + let isShowingCollectionModal: boolean = false; let next: string | null = data.props.next || null; let previous: string | null = data.props.previous || null; @@ -74,33 +74,37 @@ collections = collections.filter((collection) => collection.id !== event.detail); } - function sort({ attribute, order }: { attribute: string; order: string }) { - currentSort.attribute = attribute; - currentSort.order = order; - if (attribute === 'name') { - if (order === 'asc') { - collections = collections.sort((a, b) => b.name.localeCompare(a.name)); - } else { - collections = collections.sort((a, b) => a.name.localeCompare(b.name)); - } + // function sort({ attribute, order }: { attribute: string; order: string }) { + // currentSort.attribute = attribute; + // currentSort.order = order; + // if (attribute === 'name') { + // if (order === 'asc') { + // collections = collections.sort((a, b) => b.name.localeCompare(a.name)); + // } else { + // collections = collections.sort((a, b) => a.name.localeCompare(b.name)); + // } + // } + // } + + let collectionToEdit: Collection | null = null; + + function saveOrCreate(event: CustomEvent) { + if (collections.find((collection) => collection.id === event.detail.id)) { + collections = collections.map((collection) => { + if (collection.id === event.detail.id) { + return event.detail; + } + return collection; + }); + } else { + collections = [event.detail, ...collections]; } - } - - let collectionToEdit: Collection; - let isEditModalOpen: boolean = false; - - function deleteAdventure(event: CustomEvent) { - collections = collections.filter((adventure) => adventure.id !== event.detail); - } - - function createAdventure(event: CustomEvent) { - collections = [event.detail, ...collections]; - isShowingCreateModal = false; + isShowingCollectionModal = false; } function editCollection(event: CustomEvent) { collectionToEdit = event.detail; - isEditModalOpen = true; + isShowingCollectionModal = true; } function saveEdit(event: CustomEvent) { @@ -110,7 +114,7 @@ } return adventure; }); - isEditModalOpen = false; + isShowingCollectionModal = false; } let sidebarOpen = false; @@ -120,18 +124,14 @@ } -{#if isShowingCreateModal} - (isShowingCreateModal = false)} /> -{/if} - -{#if isEditModalOpen} - (isEditModalOpen = false)} + on:close={() => (isShowingCollectionModal = false)} on:saveEdit={saveEdit} + on:save={saveOrCreate} /> {/if} -
@@ -267,6 +263,6 @@
- {$t(`navbar.collections`)} + Collections diff --git a/frontend/src/routes/collections/[id]/+page.svelte b/frontend/src/routes/collections/[id]/+page.svelte index 10bc85b..c4107b8 100644 --- a/frontend/src/routes/collections/[id]/+page.svelte +++ b/frontend/src/routes/collections/[id]/+page.svelte @@ -2,18 +2,23 @@ import type { Adventure, Checklist, Collection, Note, Transportation } from '$lib/types'; import { onMount } from 'svelte'; import type { PageData } from './$types'; - import { goto } from '$app/navigation'; - import Lost from '$lib/assets/undraw_lost.svg'; + import { marked } from 'marked'; // Import the markdown parser + import { t } from 'svelte-i18n'; + // @ts-ignore + import Calendar from '@event-calendar/core'; + // @ts-ignore + import TimeGrid from '@event-calendar/time-grid'; + // @ts-ignore + import DayGrid from '@event-calendar/day-grid'; + import Plus from '~icons/mdi/plus'; import AdventureCard from '$lib/components/AdventureCard.svelte'; import AdventureLink from '$lib/components/AdventureLink.svelte'; import NotFound from '$lib/components/NotFound.svelte'; - import { DefaultMarker, MapLibre, Popup } from 'svelte-maplibre'; + import { DefaultMarker, MapLibre, Marker, Popup } from 'svelte-maplibre'; import TransportationCard from '$lib/components/TransportationCard.svelte'; - import EditTransportation from '$lib/components/EditTransportation.svelte'; - import NewTransportation from '$lib/components/NewTransportation.svelte'; import NoteCard from '$lib/components/NoteCard.svelte'; import NoteModal from '$lib/components/NoteModal.svelte'; @@ -26,12 +31,79 @@ import ChecklistCard from '$lib/components/ChecklistCard.svelte'; import ChecklistModal from '$lib/components/ChecklistModal.svelte'; import AdventureModal from '$lib/components/AdventureModal.svelte'; + import TransportationModal from '$lib/components/TransportationModal.svelte'; export let data: PageData; console.log(data); + const renderMarkdown = (markdown: string) => { + return marked(markdown); + }; + let collection: Collection; + // add christmas and new years + // dates = Array.from({ length: 25 }, (_, i) => { + // const date = new Date(); + // date.setMonth(11); + // date.setDate(i + 1); + // return { + // id: i.toString(), + // start: date.toISOString(), + // end: date.toISOString(), + // title: '🎄' + // }; + // }); + + let dates: Array<{ + id: string; + start: string; + end: string; + title: string; + backgroundColor?: string; + }> = []; + + // Initialize calendar plugins and options + let plugins = [TimeGrid, DayGrid]; + let options = { + view: 'dayGridMonth', + events: dates // Assign `dates` reactively + }; + + // Compute `dates` array reactively + $: { + dates = []; + + if (adventures) { + dates = dates.concat( + adventures.flatMap((adventure) => + adventure.visits.map((visit) => ({ + id: adventure.id, + start: visit.start_date || '', // Ensure it's a string + end: visit.end_date || visit.start_date || '', // Ensure it's a string + title: adventure.name + (adventure.category?.icon ? ' ' + adventure.category.icon : '') + })) + ) + ); + } + + if (transportations) { + dates = dates.concat( + transportations.map((transportation) => ({ + id: transportation.id, + start: transportation.date || '', // Ensure it's a string + end: transportation.end_date || transportation.date || '', // Ensure it's a string + title: transportation.name + (transportation.type ? ` (${transportation.type})` : '') + })) + ); + } + + // Update `options.events` when `dates` changes + options = { ...options, events: dates }; + } + + let currentView: string = 'itinerary'; + let adventures: Adventure[] = []; let numVisited: number = 0; @@ -43,6 +115,29 @@ let numberOfDays: number = NaN; + function getTransportationEmoji(type: string): string { + switch (type) { + case 'car': + return '🚗'; + case 'plane': + return '✈️'; + case 'train': + return '🚆'; + case 'bus': + return '🚌'; + case 'boat': + return '⛵'; + case 'bike': + return '🚲'; + case 'walking': + return '🚶'; + case 'other': + return '🚀'; + default: + return '🚀'; + } + } + $: { numAdventures = adventures.length; numVisited = adventures.filter((adventure) => adventure.is_visited).length; @@ -76,6 +171,11 @@ if (collection.checklists) { checklists = collection.checklists; } + if (!collection.start_date) { + currentView = 'all'; + } else { + currentView = 'itinerary'; + } }); function deleteAdventure(event: CustomEvent) { @@ -108,9 +208,8 @@ } let adventureToEdit: Adventure | null = null; - let transportationToEdit: Transportation; + let transportationToEdit: Transportation | null = null; let isAdventureModalOpen: boolean = false; - let isTransportationEditModalOpen: boolean = false; let isNoteModalOpen: boolean = false; let noteToEdit: Note | null; let checklistToEdit: Checklist | null; @@ -122,17 +221,12 @@ isAdventureModalOpen = true; } - function saveNewTransportation(event: CustomEvent) { - transportations = transportations.map((transportation) => { - if (transportation.id === event.detail.id) { - return event.detail; - } - return transportation; - }); - isTransportationEditModalOpen = false; + function editTransportation(event: CustomEvent) { + transportationToEdit = event.detail; + isShowingTransportationModal = true; } - function saveOrCreate(event: CustomEvent) { + function saveOrCreateAdventure(event: CustomEvent) { if (adventures.find((adventure) => adventure.id === event.detail.id)) { adventures = adventures.map((adventure) => { if (adventure.id === event.detail.id) { @@ -145,6 +239,22 @@ } isAdventureModalOpen = false; } + + function saveOrCreateTransportation(event: CustomEvent) { + if (transportations.find((transportation) => transportation.id === event.detail.id)) { + // Update existing transportation + transportations = transportations.map((transportation) => { + if (transportation.id === event.detail.id) { + return event.detail; + } + return transportation; + }); + } else { + // Create new transportation + transportations = [event.detail, ...transportations]; + } + isShowingTransportationModal = false; + } {#if isShowingLinkModal} @@ -157,13 +267,12 @@ /> {/if} -{#if isTransportationEditModalOpen} - (isTransportationEditModalOpen = false)} - on:saveEdit={saveNewTransportation} - startDate={collection.start_date} - endDate={collection.end_date} + on:close={() => (isShowingTransportationModal = false)} + on:save={saveOrCreateTransportation} + {collection} /> {/if} @@ -171,7 +280,7 @@ (isAdventureModalOpen = false)} - on:save={saveOrCreate} + on:save={saveOrCreateAdventure} {collection} /> {/if} @@ -221,49 +330,13 @@ /> {/if} -{#if isShowingTransportationModal} - (isShowingTransportationModal = false)} - on:add={(event) => { - transportations = [event.detail, ...transportations]; - isShowingTransportationModal = false; - }} - {collection} - startDate={collection.start_date} - endDate={collection.end_date} - /> -{/if} - -{#if notFound} -
-
-
- Lost -
-

- {$t('adventures.not_found')} -

-

- {$t('adventures.not_found_desc')} -

-
- -
-
-
-{/if} - {#if !collection && !notFound}
{/if} {#if collection} - {#if data.user && data.user.uuid && (data.user.uuid == collection.user_id || collection.shared_with.includes(data.user.uuid)) && !collection.is_archived} + {#if data.user && data.user.uuid && (data.user.uuid == collection.user_id || (collection.shared_with && collection.shared_with.includes(data.user.uuid))) && !collection.is_archived}
{/if} - {#if collection.description} -

{collection.description}

+ {#if collection && !collection.start_date && adventures.length == 0 && transportations.length == 0 && notes.length == 0 && checklists.length == 0} + {/if} + + {#if collection.description} +
+
+ {@html renderMarkdown(collection.description)} +
+
+ {/if} + {#if adventures.length > 0}
@@ -386,219 +473,332 @@
{/if} - {#if adventures.length == 0 && transportations.length == 0 && notes.length == 0 && checklists.length == 0} - - {/if} - {#if adventures.length > 0} -

{$t('adventures.linked_adventures')}

- -
- {#each adventures as adventure} - - {/each} + {#if collection.id} + {/if} - {#if transportations.length > 0} -

{$t('adventures.transportations')}

-
- {#each transportations as transportation} - { - transportations = transportations.filter((t) => t.id != event.detail); - }} - on:edit={(event) => { - transportationToEdit = event.detail; - isTransportationEditModalOpen = true; - }} - {collection} - /> - {/each} -
- {/if} + {#if currentView == 'all'} + {#if adventures.length > 0} +

{$t('adventures.linked_adventures')}

- {#if notes.length > 0} -

{$t('adventures.notes')}

-
- {#each notes as note} - { - noteToEdit = event.detail; - isNoteModalOpen = true; - }} - on:delete={(event) => { - notes = notes.filter((n) => n.id != event.detail); - }} - {collection} - /> - {/each} -
- {/if} +
+ {#each adventures as adventure} + + {/each} +
+ {/if} - {#if checklists.length > 0} -

{$t('adventures.checklists')}

-
- {#each checklists as checklist} - { - checklists = checklists.filter((n) => n.id != event.detail); - }} - on:edit={(event) => { - checklistToEdit = event.detail; - isShowingChecklistModal = true; - }} - {collection} - /> - {/each} -
+ {#if transportations.length > 0} +

{$t('adventures.transportations')}

+
+ {#each transportations as transportation} + { + transportations = transportations.filter((t) => t.id != event.detail); + }} + on:edit={editTransportation} + {collection} + /> + {/each} +
+ {/if} + + {#if notes.length > 0} +

{$t('adventures.notes')}

+
+ {#each notes as note} + { + noteToEdit = event.detail; + isNoteModalOpen = true; + }} + on:delete={(event) => { + notes = notes.filter((n) => n.id != event.detail); + }} + {collection} + /> + {/each} +
+ {/if} + + {#if checklists.length > 0} +

{$t('adventures.checklists')}

+
+ {#each checklists as checklist} + { + checklists = checklists.filter((n) => n.id != event.detail); + }} + on:edit={(event) => { + checklistToEdit = event.detail; + isShowingChecklistModal = true; + }} + {collection} + /> + {/each} +
+ {/if} + + + {#if adventures.length == 0 && transportations.length == 0 && notes.length == 0 && checklists.length == 0} + + {/if} {/if} {#if collection.start_date && collection.end_date} -
-

{$t('adventures.itineary_by_date')}

- {#if numberOfDays} -

- {$t('adventures.duration')}: {numberOfDays} - {$t('adventures.days')} -

- {/if} -

- Dates: {new Date(collection.start_date).toLocaleDateString(undefined, { timeZone: 'UTC' })} - {new Date( - collection.end_date - ).toLocaleDateString(undefined, { timeZone: 'UTC' })} -

- - {#each Array(numberOfDays) as _, i} - {@const startDate = new Date(collection.start_date)} - {@const tempDate = new Date(startDate.getTime())} - - {@const adjustedDate = new Date(tempDate.setUTCDate(tempDate.getUTCDate() + i))} - - {@const dateString = adjustedDate.toISOString().split('T')[0]} - - {@const dayAdventures = - groupAdventuresByDate(adventures, new Date(collection.start_date), numberOfDays)[ - dateString - ] || []} - - {@const dayTransportations = - groupTransportationsByDate(transportations, new Date(collection.start_date), numberOfDays)[ - dateString - ] || []} - - {@const dayNotes = - groupNotesByDate(notes, new Date(collection.start_date), numberOfDays)[dateString] || []} - - {@const dayChecklists = - groupChecklistsByDate(checklists, new Date(collection.start_date), numberOfDays)[ - dateString - ] || []} - -

- {$t('adventures.day')} - {i + 1} -

-

- {adjustedDate.toLocaleDateString(undefined, { timeZone: 'UTC' })} -

-
- {#if dayAdventures.length > 0} - {#each dayAdventures as adventure} - - {/each} - {/if} - {#if dayTransportations.length > 0} - {#each dayTransportations as transportation} - { - transportations = transportations.filter((t) => t.id != event.detail); - }} - on:edit={(event) => { - transportationToEdit = event.detail; - isTransportationEditModalOpen = true; - }} - /> - {/each} - {/if} - {#if dayNotes.length > 0} - {#each dayNotes as note} - { - noteToEdit = event.detail; - isNoteModalOpen = true; - }} - on:delete={(event) => { - notes = notes.filter((n) => n.id != event.detail); - }} - /> - {/each} - {/if} - {#if dayChecklists.length > 0} - {#each dayChecklists as checklist} - { - notes = notes.filter((n) => n.id != event.detail); - }} - on:edit={(event) => { - checklistToEdit = event.detail; - isShowingChecklistModal = true; - }} - /> - {/each} - {/if} - - {#if dayAdventures.length == 0 && dayTransportations.length == 0 && dayNotes.length == 0 && dayChecklists.length == 0} -

{$t('adventures.nothing_planned')}

- {/if} -
- {/each} - - - - - - {#each adventures as adventure} - {#if adventure.longitude && adventure.latitude} - - -
{adventure.name}
-

- {adventure.category?.display_name + ' ' + adventure.category?.icon} + {#if currentView == 'itinerary'} +

+
+
+

{$t('adventures.itineary_by_date')}

+ {#if numberOfDays} +

+ {$t('adventures.duration')}: + {numberOfDays} {$t('adventures.days')}

- - - {/if} - {/each} - + {/if} +

+ Dates: {new Date(collection.start_date).toLocaleDateString(undefined, { + timeZone: 'UTC' + })} - + {new Date(collection.end_date).toLocaleDateString(undefined, { + timeZone: 'UTC' + })} +

+
+
+
+ +
+ {#each Array(numberOfDays) as _, i} + {@const startDate = new Date(collection.start_date)} + {@const tempDate = new Date(startDate.getTime())} + {@const adjustedDate = new Date(tempDate.setUTCDate(tempDate.getUTCDate() + i))} + {@const dateString = adjustedDate.toISOString().split('T')[0]} + + {@const dayAdventures = + groupAdventuresByDate(adventures, new Date(collection.start_date), numberOfDays)[ + dateString + ] || []} + {@const dayTransportations = + groupTransportationsByDate( + transportations, + new Date(collection.start_date), + numberOfDays + )[dateString] || []} + {@const dayNotes = + groupNotesByDate(notes, new Date(collection.start_date), numberOfDays)[dateString] || + []} + {@const dayChecklists = + groupChecklistsByDate(checklists, new Date(collection.start_date), numberOfDays)[ + dateString + ] || []} + +
+
+

+ {$t('adventures.day')} + {i + 1} +
+ {adjustedDate.toLocaleDateString(undefined, { timeZone: 'UTC' })} +
+

+ +
+ +
+ {#if dayAdventures.length > 0} + {#each dayAdventures as adventure} + + {/each} + {/if} + {#if dayTransportations.length > 0} + {#each dayTransportations as transportation} + { + transportations = transportations.filter((t) => t.id != event.detail); + }} + on:edit={(event) => { + transportationToEdit = event.detail; + isShowingTransportationModal = true; + }} + /> + {/each} + {/if} + {#if dayNotes.length > 0} + {#each dayNotes as note} + { + noteToEdit = event.detail; + isNoteModalOpen = true; + }} + on:delete={(event) => { + notes = notes.filter((n) => n.id != event.detail); + }} + /> + {/each} + {/if} + {#if dayChecklists.length > 0} + {#each dayChecklists as checklist} + { + notes = notes.filter((n) => n.id != event.detail); + }} + on:edit={(event) => { + checklistToEdit = event.detail; + isShowingChecklistModal = true; + }} + /> + {/each} + {/if} +
+ + {#if dayAdventures.length == 0 && dayTransportations.length == 0 && dayNotes.length == 0 && dayChecklists.length == 0} +

{$t('adventures.nothing_planned')}

+ {/if} +
+
+ {/each} +
+ {/if} + {/if} + + {#if currentView == 'map'} +
+
+

Trip Map

+ + {#each adventures as adventure} + {#if adventure.longitude && adventure.latitude} + + +
{adventure.name}
+

+ {adventure.category?.display_name + ' ' + adventure.category?.icon} +

+
+
+ {/if} + {/each} + {#each transportations as transportation} + {#if transportation.destination_latitude && transportation.destination_longitude} + + + {getTransportationEmoji(transportation.type)} + + +
{transportation.name}
+

+ {transportation.type} +

+
+
+ {/if} + {#if transportation.origin_latitude && transportation.origin_longitude} + + + {getTransportationEmoji(transportation.type)} + + +
{transportation.name}
+

+ {transportation.type} +

+
+
+ {/if} + {/each} +
+
+
+ {/if} + {#if currentView == 'calendar'} +
+
+

+ {$t('adventures.adventure_calendar')} +

+ +
+
{/if} {/if} diff --git a/frontend/src/routes/map/+page.svelte b/frontend/src/routes/map/+page.svelte index 705ce1c..f550760 100644 --- a/frontend/src/routes/map/+page.svelte +++ b/frontend/src/routes/map/+page.svelte @@ -3,7 +3,6 @@ import { DefaultMarker, MapEvents, MapLibre, Popup, Marker } from 'svelte-maplibre'; import { t } from 'svelte-i18n'; import type { Adventure, VisitedRegion } from '$lib/types.js'; - import { getAdventureTypeLabel } from '$lib'; import CardCarousel from '$lib/components/CardCarousel.svelte'; import { goto } from '$app/navigation'; export let data; diff --git a/frontend/src/routes/profile/+page.svelte b/frontend/src/routes/profile/+page.svelte index d57df6b..8722c26 100644 --- a/frontend/src/routes/profile/+page.svelte +++ b/frontend/src/routes/profile/+page.svelte @@ -11,82 +11,88 @@ total_countries: number; } | null; - if (data.stats) { - stats = data.stats; - } else { - stats = null; - } - console.log(stats); + stats = data.stats || null; -{#if data.user.profile_pic} -
-
- - -
+
+
+ + {#if data.user.profile_pic} +
+
+ Profile +
+
+ {/if} + + + {#if data.user && data.user.first_name && data.user.last_name} +

+ {data.user.first_name} + {data.user.last_name} +

+ {/if} +

{data.user.username}

+ + + {#if data.user && data.user.date_joined} +
+

{$t('profile.member_since')}

+
+ +

+ {new Date(data.user.date_joined).toLocaleDateString(undefined, { timeZone: 'UTC' })} +

+
+
+ {/if}
-{/if} -{#if data.user && data.user.first_name && data.user.last_name} -

- {data.user.first_name} - {data.user.last_name} -

-{/if} -

{data.user.username}

+ + {#if stats} +
-{#if data.user && data.user.date_joined} -

{$t('profile.member_since')}

-
- -

- {new Date(data.user.date_joined).toLocaleDateString(undefined, { timeZone: 'UTC' })} -

-
-{/if} +

+ {$t('profile.user_stats')} +

-{#if stats} - -
- -

{$t('profile.user_stats')}

- -
-
-
-
{$t('navbar.adventures')}
-
{stats.adventure_count}
- -
-
-
{$t('navbar.collections')}
-
{stats.trips_count}
- -
- -
-
{$t('profile.visited_countries')}
-
- {Math.round((stats.country_count / stats.total_countries) * 100)}% +
+
+
+
{$t('navbar.adventures')}
+
{stats.adventure_count}
-
- {stats.country_count}/{stats.total_countries} -
-
-
-
{$t('profile.visited_regions')}
-
- {Math.round((stats.visited_region_count / stats.total_regions) * 100)}% +
+
{$t('navbar.collections')}
+
{stats.trips_count}
-
- {stats.visited_region_count}/{stats.total_regions} + +
+
{$t('profile.visited_countries')}
+
+ {Math.round((stats.country_count / stats.total_countries) * 100)}% +
+
+ {stats.country_count}/{stats.total_countries} +
+
+ +
+
{$t('profile.visited_regions')}
+
+ {Math.round((stats.visited_region_count / stats.total_regions) * 100)}% +
+
+ {stats.visited_region_count}/{stats.total_regions} +
-
-{/if} + {/if} +
Profile | AdventureLog diff --git a/frontend/src/routes/search/+page.svelte b/frontend/src/routes/search/+page.svelte index ac3735e..d85902f 100644 --- a/frontend/src/routes/search/+page.svelte +++ b/frontend/src/routes/search/+page.svelte @@ -110,14 +110,6 @@ id="name" on:change={() => (property = 'name')} /> - (property = 'type')} - /> {/if} -

{$t('settings.settings_page')}

- -

{$t('settings.account_settings')}

-
-
- -
- -
- - -
- -
-
-
-
+ -{#if $page.form?.message} -
- {$t($page.form.message)} -
-{/if} - -

{$t('settings.password_change')}

-
-
- -
- -
- -
- -
-
-
-
- -

{$t('settings.email_change')}

- -
-
- {#each emails as email} -

- {email.email} - {#if email.verified} -

{$t('settings.verified')}
- {:else} -
{$t('settings.not_verified')}
- {/if} - {#if email.primary} -
{$t('settings.primary')}
- {/if} - {#if !email.verified} - +
+

+ {$t('settings.password_change')} +

+
+
+
+ - {/if} - {#if !email.primary} - +
+ +
+ + +
+ +
+ - {/if} - +
+ +
-

- {/each} - {#if emails.length === 0} -

{$t('settings.no_emai_set')}

- {/if} -
-
- -
- -
- + +
+
-
- +
+ + +
+

+ {$t('settings.email_change')} +

+
+
+ {#each emails as email} +
+ {email.email} + {#if email.verified} +
{$t('settings.verified')}
+ {:else} +
{$t('settings.not_verified')}
+ {/if} + {#if email.primary} +
{$t('settings.primary')}
+ {/if} + {#if !email.verified} + + {/if} + {#if !email.primary} + + {/if} + +
+ {/each} + {#if emails.length === 0} +

{$t('settings.no_email_set')}

+ {/if} +
+ +
+ + +
- +
+ + +
+

+ {$t('settings.mfa_page_title')} +

+
+ {#if !data.props.authenticators} +

{$t('settings.mfa_not_enabled')}

+ + {:else} + + {/if} +
+
+ + +
+

{$t('adventures.visited_region_check')}

+

{$t('adventures.visited_region_check_desc')}

+ +
+ + + For Debug Use: UUID={user.uuid} | Staff user: {user.is_staff} | {appTitle} + {appVersion} +
-

{$t('settings.mfa_page_title')}

- -
-
- {#if !data.props.authenticators} -

{$t('settings.mfa_not_enabled')}

- - {:else} - - {/if} -
-
- -
-

- {$t('adventures.visited_region_check')} -

-

- {$t('adventures.visited_region_check_desc')} -

-

{$t('adventures.update_visited_regions_disclaimer')}

- - -
- -For Debug Use: UUID={user.uuid} | Staff user: {user.is_staff} - User Settings | AdventureLog -

{$t('settings.reset_password')}

+
+

{$t('settings.reset_password')}

-
-
- -
- - {#if $page.form?.message} -
- {$t(`settings.${$page.form?.message}`)} +
+ +
+ +
- {/if} - {#if $page.form?.success} -
- {$t('settings.possible_reset')} + +
+
- {/if} - -
+ + {#if $page.form?.message} +
+ {$t(`settings.${$page.form?.message}`)} +
+ {/if} + + {#if $page.form?.success} +
+ {$t('settings.possible_reset')} +
+ {/if} + +
+
- Forgot Password + Reset Password diff --git a/frontend/src/routes/user/reset-password/[key]/+page.svelte b/frontend/src/routes/user/reset-password/[key]/+page.svelte index 7e837a4..e710af6 100644 --- a/frontend/src/routes/user/reset-password/[key]/+page.svelte +++ b/frontend/src/routes/user/reset-password/[key]/+page.svelte @@ -1,53 +1,66 @@ -

{$t('settings.change_password')}

+
+

+ {$t('settings.change_password')} +

-
-
- - +
+ +
+ + +
+ +
+ + +
+ +
+ +
+ + {#if $page.form?.message} +
+ {$t($page.form?.message)} +
+ {/if} +
- -
- - -
- - - - {#if $page.form?.message} -
- {$t($page.form?.message)} -
- {/if} - +
- Password Reset Confirm - + Change Password + diff --git a/frontend/src/routes/user/verify-email/[key]/+page.svelte b/frontend/src/routes/user/verify-email/[key]/+page.svelte index f025ad0..cadcc9c 100644 --- a/frontend/src/routes/user/verify-email/[key]/+page.svelte +++ b/frontend/src/routes/user/verify-email/[key]/+page.svelte @@ -5,10 +5,27 @@ export let data: PageData; -{#if data.verified} -

{$t('settings.email_verified')}

-

{$t('settings.email_verified_success')}

-{:else} -

{$t('settings.email_verified_error')}

-

{$t('settings.email_verified_erorr_desc')}

-{/if} +
+
+ {#if data.verified} +

+ {$t('settings.email_verified')} +

+

+ {$t('settings.email_verified_success')} +

+ {:else} +

+ {$t('settings.email_verified_error')} +

+

+ {$t('settings.email_verified_erorr_desc')} +

+ {/if} +
+
+ + + Email Verification + + diff --git a/frontend/src/routes/worldtravel/[id]/+page.server.ts b/frontend/src/routes/worldtravel/[id]/+page.server.ts index 2b00f3c..0191e67 100644 --- a/frontend/src/routes/worldtravel/[id]/+page.server.ts +++ b/frontend/src/routes/worldtravel/[id]/+page.server.ts @@ -6,7 +6,7 @@ import type { PageServerLoad } from './$types'; const endpoint = PUBLIC_SERVER_URL || 'http://localhost:8000'; export const load = (async (event) => { - const id = event.params.id; + const id = event.params.id.toUpperCase(); let regions: Region[] = []; let visitedRegions: VisitedRegion[] = []; diff --git a/frontend/src/routes/worldtravel/[id]/+page.svelte b/frontend/src/routes/worldtravel/[id]/+page.svelte index 012284e..15bd69d 100644 --- a/frontend/src/routes/worldtravel/[id]/+page.svelte +++ b/frontend/src/routes/worldtravel/[id]/+page.svelte @@ -1,7 +1,10 @@