mirror of
https://github.com/seanmorley15/AdventureLog.git
synced 2025-07-21 13:59:36 +02:00
UI changes and updates, collection page refresh
This commit is contained in:
parent
c9dd5fe33d
commit
f7c998ab58
20 changed files with 834 additions and 834 deletions
|
@ -41,7 +41,7 @@ export default defineConfig({
|
||||||
|
|
||||||
footer: {
|
footer: {
|
||||||
message: "AdventureLog",
|
message: "AdventureLog",
|
||||||
copyright: "Copyright © 2023-2024 Sean Morley",
|
copyright: "Copyright © 2023-2025 Sean Morley",
|
||||||
},
|
},
|
||||||
|
|
||||||
logo: "/adventurelog.png",
|
logo: "/adventurelog.png",
|
||||||
|
|
182
frontend/src/lib/components/CollectionModal.svelte
Normal file
182
frontend/src/lib/components/CollectionModal.svelte
Normal file
|
@ -0,0 +1,182 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
import type { Collection, Transportation } from '$lib/types';
|
||||||
|
const dispatch = createEventDispatcher();
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
import { addToast } from '$lib/toasts';
|
||||||
|
let modal: HTMLDialogElement;
|
||||||
|
import { t } from 'svelte-i18n';
|
||||||
|
|
||||||
|
import MarkdownEditor from './MarkdownEditor.svelte';
|
||||||
|
|
||||||
|
export let collectionToEdit: Collection | null = null;
|
||||||
|
|
||||||
|
let collection: Collection = {
|
||||||
|
id: collectionToEdit?.id || '',
|
||||||
|
name: collectionToEdit?.name || '',
|
||||||
|
description: collectionToEdit?.description || '',
|
||||||
|
start_date: collectionToEdit?.start_date || null,
|
||||||
|
end_date: collectionToEdit?.end_date || null,
|
||||||
|
user_id: collectionToEdit?.user_id || '',
|
||||||
|
is_public: collectionToEdit?.is_public || false,
|
||||||
|
adventures: collectionToEdit?.adventures || [],
|
||||||
|
link: collectionToEdit?.link || '',
|
||||||
|
shared_with: collectionToEdit?.shared_with || []
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log(collection);
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
modal = document.getElementById('my_modal_1') as HTMLDialogElement;
|
||||||
|
if (modal) {
|
||||||
|
modal.showModal();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function close() {
|
||||||
|
dispatch('close');
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleKeydown(event: KeyboardEvent) {
|
||||||
|
if (event.key === 'Escape') {
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleSubmit(event: Event) {
|
||||||
|
event.preventDefault();
|
||||||
|
console.log(collection);
|
||||||
|
|
||||||
|
if (collection.id === '') {
|
||||||
|
let res = await fetch('/api/collections', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(collection)
|
||||||
|
});
|
||||||
|
let data = await res.json();
|
||||||
|
if (data.id) {
|
||||||
|
collection = data as Collection;
|
||||||
|
addToast('success', $t('collection.collection_created'));
|
||||||
|
dispatch('save', collection);
|
||||||
|
} else {
|
||||||
|
console.error(data);
|
||||||
|
addToast('error', $t('collection.error_creating_collection'));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let res = await fetch(`/api/collections/${collection.id}`, {
|
||||||
|
method: 'PATCH',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(collection)
|
||||||
|
});
|
||||||
|
let data = await res.json();
|
||||||
|
if (data.id) {
|
||||||
|
collection = data as Collection;
|
||||||
|
addToast('success', $t('collection.collection_edit_success'));
|
||||||
|
dispatch('save', collection);
|
||||||
|
} else {
|
||||||
|
addToast('error', $t('collection.error_editing_collection'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<dialog id="my_modal_1" class="modal">
|
||||||
|
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
||||||
|
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
|
||||||
|
<div class="modal-box w-11/12 max-w-3xl" role="dialog" on:keydown={handleKeydown} tabindex="0">
|
||||||
|
<h3 class="font-bold text-2xl">
|
||||||
|
{collectionToEdit ? $t('adventures.edit_collection') : $t('collection.new_collection')}
|
||||||
|
</h3>
|
||||||
|
<div class="modal-action items-center">
|
||||||
|
<form method="post" style="width: 100%;" on:submit={handleSubmit}>
|
||||||
|
<!-- Basic Information Section -->
|
||||||
|
<div class="collapse collapse-plus bg-base-200 mb-4">
|
||||||
|
<input type="checkbox" checked />
|
||||||
|
<div class="collapse-title text-xl font-medium">
|
||||||
|
{$t('adventures.basic_information')}
|
||||||
|
</div>
|
||||||
|
<div class="collapse-content">
|
||||||
|
<!-- Name -->
|
||||||
|
<div>
|
||||||
|
<label for="name">
|
||||||
|
{$t('adventures.name')}<span class="text-red-500">*</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="name"
|
||||||
|
name="name"
|
||||||
|
bind:value={collection.name}
|
||||||
|
class="input input-bordered w-full"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<!-- Description -->
|
||||||
|
<div>
|
||||||
|
<label for="description">{$t('adventures.description')}</label><br />
|
||||||
|
<MarkdownEditor bind:text={collection.description} editor_height={'h-32'} />
|
||||||
|
</div>
|
||||||
|
<!-- Start Date -->
|
||||||
|
<div>
|
||||||
|
<label for="start_date">{$t('adventures.start_date')}</label>
|
||||||
|
<input
|
||||||
|
type="date"
|
||||||
|
id="start_date"
|
||||||
|
name="start_date"
|
||||||
|
bind:value={collection.start_date}
|
||||||
|
class="input input-bordered w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<!-- End Date -->
|
||||||
|
<div>
|
||||||
|
<label for="end_date">{$t('adventures.end_date')}</label>
|
||||||
|
<input
|
||||||
|
type="date"
|
||||||
|
id="end_date"
|
||||||
|
name="end_date"
|
||||||
|
bind:value={collection.end_date}
|
||||||
|
class="input input-bordered w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<!-- Public -->
|
||||||
|
<div>
|
||||||
|
<label class="label cursor-pointer flex items-start space-x-2">
|
||||||
|
<span class="label-text">{$t('collection.public_collection')}</span>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
class="toggle toggle-primary"
|
||||||
|
id="is_public"
|
||||||
|
name="is_public"
|
||||||
|
bind:checked={collection.is_public}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<!-- Link -->
|
||||||
|
<div>
|
||||||
|
<label for="link">{$t('adventures.link')}</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="link"
|
||||||
|
name="link"
|
||||||
|
bind:value={collection.link}
|
||||||
|
class="input input-bordered w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Form Actions -->
|
||||||
|
<div class="mt-4">
|
||||||
|
<button type="submit" class="btn btn-primary">
|
||||||
|
{$t('adventures.save_next')}
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn" on:click={close}>
|
||||||
|
{$t('about.close')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</dialog>
|
|
@ -1,209 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
export let collectionToEdit: Collection;
|
|
||||||
import { createEventDispatcher } from 'svelte';
|
|
||||||
import type { Collection } from '$lib/types';
|
|
||||||
import { t } from 'svelte-i18n';
|
|
||||||
const dispatch = createEventDispatcher();
|
|
||||||
import { onMount } from 'svelte';
|
|
||||||
import { addToast } from '$lib/toasts';
|
|
||||||
let modal: HTMLDialogElement;
|
|
||||||
|
|
||||||
console.log(collectionToEdit.id);
|
|
||||||
|
|
||||||
let originalName = collectionToEdit.name;
|
|
||||||
|
|
||||||
import Calendar from '~icons/mdi/calendar';
|
|
||||||
import Notebook from '~icons/mdi/notebook';
|
|
||||||
import Earth from '~icons/mdi/earth';
|
|
||||||
|
|
||||||
onMount(async () => {
|
|
||||||
modal = document.getElementById('my_modal_1') as HTMLDialogElement;
|
|
||||||
if (modal) {
|
|
||||||
modal.showModal();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function submit() {}
|
|
||||||
|
|
||||||
function close() {
|
|
||||||
dispatch('close');
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleKeydown(event: KeyboardEvent) {
|
|
||||||
if (event.key === 'Escape') {
|
|
||||||
close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleSubmit(event: Event) {
|
|
||||||
event.preventDefault();
|
|
||||||
const form = event.target as HTMLFormElement;
|
|
||||||
const formData = new FormData(form);
|
|
||||||
|
|
||||||
if (collectionToEdit.end_date && collectionToEdit.start_date) {
|
|
||||||
if (new Date(collectionToEdit.start_date) > new Date(collectionToEdit.end_date)) {
|
|
||||||
addToast('error', $t('adventures.start_before_end_error'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (collectionToEdit.end_date && !collectionToEdit.start_date) {
|
|
||||||
addToast('error', $t('adventures.no_start_date'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (collectionToEdit.start_date && !collectionToEdit.end_date) {
|
|
||||||
addToast('error', $t('adventures.no_end_date'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await fetch(form.action, {
|
|
||||||
method: form.method,
|
|
||||||
body: formData
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
const result = await response.json();
|
|
||||||
const data = JSON.parse(result.data);
|
|
||||||
console.log(data);
|
|
||||||
|
|
||||||
if (data) {
|
|
||||||
addToast('success', $t('collection.collection_edit_success'));
|
|
||||||
dispatch('saveEdit', collectionToEdit);
|
|
||||||
close();
|
|
||||||
} else {
|
|
||||||
addToast('warning', $t('collection.error_editing_collection'));
|
|
||||||
console.log('Error editing collection');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<dialog id="my_modal_1" class="modal">
|
|
||||||
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
|
|
||||||
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
|
||||||
<div class="modal-box" role="dialog" on:keydown={handleKeydown} tabindex="0">
|
|
||||||
<h3 class="font-bold text-lg">{$t('adventures.edit_collection')}: {originalName}</h3>
|
|
||||||
<div
|
|
||||||
class="modal-action items-center"
|
|
||||||
style="display: flex; flex-direction: column; align-items: center; width: 100%;"
|
|
||||||
>
|
|
||||||
<form method="post" style="width: 100%;" on:submit={handleSubmit} action="/collections?/edit">
|
|
||||||
<div class="mb-2">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="adventureId"
|
|
||||||
name="adventureId"
|
|
||||||
hidden
|
|
||||||
readonly
|
|
||||||
bind:value={collectionToEdit.id}
|
|
||||||
class="input input-bordered w-full max-w-xs mt-1"
|
|
||||||
/>
|
|
||||||
<label for="name">{$t('adventures.name')}</label><br />
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
name="name"
|
|
||||||
id="name"
|
|
||||||
bind:value={collectionToEdit.name}
|
|
||||||
class="input input-bordered w-full max-w-xs mt-1"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="mb-2">
|
|
||||||
<label for="date"
|
|
||||||
>{$t('adventures.description')}
|
|
||||||
<Notebook class="inline-block -mt-1 mb-1 w-6 h-6" /></label
|
|
||||||
><br />
|
|
||||||
<div class="flex">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="description"
|
|
||||||
name="description"
|
|
||||||
bind:value={collectionToEdit.description}
|
|
||||||
class="input input-bordered w-full max-w-xs mt-1 mb-2"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- <button
|
|
||||||
class="btn btn-neutral ml-2"
|
|
||||||
type="button"
|
|
||||||
on:click={generate}
|
|
||||||
><iconify-icon icon="mdi:wikipedia" class="text-xl -mb-1"
|
|
||||||
></iconify-icon>Generate Description</button
|
|
||||||
> -->
|
|
||||||
</div>
|
|
||||||
<div class="mb-2">
|
|
||||||
<label for="start_date"
|
|
||||||
>{$t('adventures.start_date')} <Calendar class="inline-block mb-1 w-6 h-6" /></label
|
|
||||||
><br />
|
|
||||||
<input
|
|
||||||
type="date"
|
|
||||||
id="start_date"
|
|
||||||
name="start_date"
|
|
||||||
bind:value={collectionToEdit.start_date}
|
|
||||||
class="input input-bordered w-full max-w-xs mt-1"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="mb-2">
|
|
||||||
<label for="end_date"
|
|
||||||
>{$t('adventures.end_date')} <Calendar class="inline-block mb-1 w-6 h-6" /></label
|
|
||||||
><br />
|
|
||||||
<input
|
|
||||||
type="date"
|
|
||||||
id="end_date"
|
|
||||||
name="end_date"
|
|
||||||
bind:value={collectionToEdit.end_date}
|
|
||||||
class="input input-bordered w-full max-w-xs mt-1"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="mb-2">
|
|
||||||
<label for="end_date">{$t('adventures.link')} </label><br />
|
|
||||||
<input
|
|
||||||
type="url"
|
|
||||||
id="link"
|
|
||||||
name="link"
|
|
||||||
bind:value={collectionToEdit.link}
|
|
||||||
class="input input-bordered w-full max-w-xs mt-1"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="mb-2">
|
|
||||||
<label for="is_public"
|
|
||||||
>{$t('adventures.public')} <Earth class="inline-block -mt-1 mb-1 w-6 h-6" /></label
|
|
||||||
><br />
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
class="toggle toggle-primary"
|
|
||||||
id="is_public"
|
|
||||||
name="is_public"
|
|
||||||
bind:checked={collectionToEdit.is_public}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{#if collectionToEdit.is_public}
|
|
||||||
<div class="bg-neutral p-4 rounded-md shadow-sm">
|
|
||||||
<p class=" font-semibold">{$t('adventures.share_adventure')}</p>
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<p class="text-card-foreground font-mono">
|
|
||||||
{window.location.origin}/collections/{collectionToEdit.id}
|
|
||||||
</p>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
on:click={() => {
|
|
||||||
navigator.clipboard.writeText(
|
|
||||||
`${window.location.origin}/collections/${collectionToEdit.id}`
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
class="inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 h-10 px-4 py-2"
|
|
||||||
>
|
|
||||||
{$t('adventures.copy_link')}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<button type="submit" class="btn btn-primary mr-4 mt-4" on:click={submit}>Edit</button>
|
|
||||||
<!-- if there is a button in form, it will close the modal -->
|
|
||||||
<button class="btn mt-4" on:click={close}>{$t('about.close')}</button>
|
|
||||||
</form>
|
|
||||||
<div class="flex items-center justify-center flex-wrap gap-4 mt-4"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</dialog>
|
|
|
@ -1,180 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import { createEventDispatcher } from 'svelte';
|
|
||||||
import type { Adventure, Collection } from '$lib/types';
|
|
||||||
import { onMount } from 'svelte';
|
|
||||||
import { t } from 'svelte-i18n';
|
|
||||||
import { addToast } from '$lib/toasts';
|
|
||||||
|
|
||||||
import Calendar from '~icons/mdi/calendar';
|
|
||||||
|
|
||||||
let newCollection: Collection = {
|
|
||||||
user_id: '',
|
|
||||||
id: '',
|
|
||||||
name: '',
|
|
||||||
description: '',
|
|
||||||
adventures: [] as Adventure[],
|
|
||||||
is_public: false,
|
|
||||||
shared_with: [],
|
|
||||||
link: ''
|
|
||||||
};
|
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
|
||||||
let modal: HTMLDialogElement;
|
|
||||||
|
|
||||||
onMount(async () => {
|
|
||||||
modal = document.getElementById('my_modal_1') as HTMLDialogElement;
|
|
||||||
if (modal) {
|
|
||||||
modal.showModal();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function close() {
|
|
||||||
dispatch('close');
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleKeydown(event: KeyboardEvent) {
|
|
||||||
if (event.key === 'Escape') {
|
|
||||||
close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleSubmit(event: Event) {
|
|
||||||
event.preventDefault();
|
|
||||||
const form = event.target as HTMLFormElement;
|
|
||||||
const formData = new FormData(form);
|
|
||||||
|
|
||||||
// make sure that start_date is before end_date
|
|
||||||
if (new Date(newCollection.start_date ?? '') > new Date(newCollection.end_date ?? '')) {
|
|
||||||
addToast('error', $t('adventures.start_before_end_error'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// make sure end date has a start date
|
|
||||||
if (newCollection.end_date && !newCollection.start_date) {
|
|
||||||
addToast('error', $t('adventures.no_start_date'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newCollection.start_date && !newCollection.end_date) {
|
|
||||||
addToast('error', $t('adventures.no_end_date'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await fetch(form.action, {
|
|
||||||
method: form.method,
|
|
||||||
body: formData
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
const result = await response.json();
|
|
||||||
const data = JSON.parse(result.data); // Parsing the JSON string in the data field
|
|
||||||
|
|
||||||
if (data[1] !== undefined) {
|
|
||||||
// these two lines here are wierd, because the data[1] is the id of the new adventure and data[2] is the user_id of the new adventure
|
|
||||||
console.log(data);
|
|
||||||
let id = data[1];
|
|
||||||
let user_id = data[2];
|
|
||||||
|
|
||||||
if (id !== undefined && user_id !== undefined) {
|
|
||||||
newCollection.id = id;
|
|
||||||
newCollection.user_id = user_id;
|
|
||||||
console.log(newCollection);
|
|
||||||
dispatch('create', newCollection);
|
|
||||||
addToast('success', $t('collection.collection_created'));
|
|
||||||
close();
|
|
||||||
} else {
|
|
||||||
addToast('error', $t('collection.error_creating_collection'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
|
||||||
<dialog id="my_modal_1" class="modal">
|
|
||||||
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
|
||||||
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
|
|
||||||
<div class="modal-box" role="dialog" on:keydown={handleKeydown} tabindex="0">
|
|
||||||
<h3 class="font-bold text-lg">{$t('collection.new_collection')}</h3>
|
|
||||||
<div
|
|
||||||
class="modal-action items-center"
|
|
||||||
style="display: flex; flex-direction: column; align-items: center; width: 100%;"
|
|
||||||
>
|
|
||||||
<form
|
|
||||||
method="post"
|
|
||||||
style="width: 100%;"
|
|
||||||
on:submit={handleSubmit}
|
|
||||||
action="/collections?/create"
|
|
||||||
>
|
|
||||||
<div class="mb-2">
|
|
||||||
<label for="name">{$t('adventures.name')}</label><br />
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="name"
|
|
||||||
name="name"
|
|
||||||
bind:value={newCollection.name}
|
|
||||||
class="input input-bordered w-full max-w-xs mt-1"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="mb-2">
|
|
||||||
<label for="description"
|
|
||||||
>{$t('adventures.description')}<iconify-icon
|
|
||||||
icon="mdi:notebook"
|
|
||||||
class="text-lg ml-1 -mb-0.5"
|
|
||||||
></iconify-icon></label
|
|
||||||
><br />
|
|
||||||
<div class="flex">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="description"
|
|
||||||
name="description"
|
|
||||||
bind:value={newCollection.description}
|
|
||||||
class="input input-bordered w-full max-w-xs mt-1 mb-2"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="mb-2">
|
|
||||||
<label for="start_date"
|
|
||||||
>{$t('adventures.start_date')} <Calendar class="inline-block mb-1 w-6 h-6" /></label
|
|
||||||
><br />
|
|
||||||
<input
|
|
||||||
type="date"
|
|
||||||
id="start_date"
|
|
||||||
name="start_date"
|
|
||||||
bind:value={newCollection.start_date}
|
|
||||||
class="input input-bordered w-full max-w-xs mt-1"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="mb-2">
|
|
||||||
<label for="end_date"
|
|
||||||
>{$t('adventures.end_date')} <Calendar class="inline-block mb-1 w-6 h-6" /></label
|
|
||||||
><br />
|
|
||||||
<input
|
|
||||||
type="date"
|
|
||||||
id="end_date"
|
|
||||||
name="end_date"
|
|
||||||
bind:value={newCollection.end_date}
|
|
||||||
class="input input-bordered w-full max-w-xs mt-1"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="mb-2">
|
|
||||||
<label for="end_date">{$t('adventures.link')} </label><br />
|
|
||||||
<input
|
|
||||||
type="url"
|
|
||||||
id="link"
|
|
||||||
name="link"
|
|
||||||
bind:value={newCollection.link}
|
|
||||||
class="input input-bordered w-full max-w-xs mt-1"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="mb-2">
|
|
||||||
<button type="submit" class="btn btn-primary mr-4 mt-4">
|
|
||||||
{$t('collection.create')}
|
|
||||||
</button>
|
|
||||||
<button type="button" class="btn mt-4" on:click={close}>{$t('about.close')}</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</dialog>
|
|
|
@ -73,22 +73,22 @@
|
||||||
<p class="break-words">{transportation.from_location}</p>
|
<p class="break-words">{transportation.from_location}</p>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if transportation.to_location}
|
{#if transportation.date}
|
||||||
<!-- <ArrowDownThick class="w-4 h-4" /> -->
|
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<span class="font-medium text-sm">{$t('adventures.to')}:</span>
|
<span class="font-medium text-sm">{$t('adventures.start')}:</span>
|
||||||
|
<p>{new Date(transportation.date).toLocaleString(undefined, { timeZone: 'UTC' })}</p>
|
||||||
<p class="break-words">{transportation.to_location}</p>
|
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Dates -->
|
<!-- Dates -->
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
{#if transportation.date}
|
{#if transportation.to_location}
|
||||||
|
<!-- <ArrowDownThick class="w-4 h-4" /> -->
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<span class="font-medium text-sm">{$t('adventures.start')}:</span>
|
<span class="font-medium text-sm">{$t('adventures.to')}:</span>
|
||||||
<p>{new Date(transportation.date).toLocaleString(undefined, { timeZone: 'UTC' })}</p>
|
|
||||||
|
<p class="break-words">{transportation.to_location}</p>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if transportation.end_date}
|
{#if transportation.end_date}
|
||||||
|
|
|
@ -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"
|
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"
|
||||||
>
|
>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div>
|
<!-- Profile Picture and User Info -->
|
||||||
|
<div class="flex flex-col items-center">
|
||||||
{#if user.profile_pic}
|
{#if user.profile_pic}
|
||||||
<div class="avatar">
|
<div class="avatar mb-4">
|
||||||
<div class="w-24 rounded-full">
|
<div class="w-24 rounded-full ring ring-primary ring-offset-neutral ring-offset-2">
|
||||||
<img src={user.profile_pic} alt={user.username} />
|
<img src={user.profile_pic} alt={user.username} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<h2 class="card-title overflow-ellipsis">{user.first_name} {user.last_name}</h2>
|
|
||||||
</div>
|
<h2 class="card-title text-center text-lg font-bold">
|
||||||
<p class="text-sm text-neutral-content">{user.username}</p>
|
{user.first_name}
|
||||||
|
{user.last_name}
|
||||||
|
</h2>
|
||||||
|
<p class="text-sm text-center">{user.username}</p>
|
||||||
|
|
||||||
|
<!-- Admin Badge -->
|
||||||
{#if user.is_staff}
|
{#if user.is_staff}
|
||||||
<div class="badge badge-primary">Admin</div>
|
<div class="badge badge-primary mt-2">Admin</div>
|
||||||
{/if}
|
{/if}
|
||||||
<!-- member since -->
|
</div>
|
||||||
<div class="flex items-center space-x-2">
|
|
||||||
<Calendar class="w-4 h-4 mr-1" />
|
<!-- Member Since -->
|
||||||
<p class="text-sm text-neutral-content">
|
<div class="flex items-center justify-center mt-4 space-x-2 text-sm">
|
||||||
|
<Calendar class="w-5 h-5 text-primary" />
|
||||||
|
<p>
|
||||||
{user.date_joined ? 'Joined ' + new Date(user.date_joined).toLocaleDateString() : ''}
|
{user.date_joined ? 'Joined ' + new Date(user.date_joined).toLocaleDateString() : ''}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-actions justify-end">
|
|
||||||
|
<!-- Card Actions -->
|
||||||
|
<div class="card-actions justify-center mt-6">
|
||||||
{#if !sharing}
|
{#if !sharing}
|
||||||
<button class="btn btn-primary" on:click={() => goto(`/user/${user.uuid}`)}>View</button>
|
<button class="btn btn-primary" on:click={() => goto(`/user/${user.uuid}`)}>
|
||||||
|
View Profile
|
||||||
|
</button>
|
||||||
{:else if shared_with && !shared_with.includes(user.uuid)}
|
{:else if shared_with && !shared_with.includes(user.uuid)}
|
||||||
<button class="btn btn-primary" on:click={() => dispatch('share', user)}>Share</button>
|
<button class="btn btn-success" on:click={() => dispatch('share', user)}> Share </button>
|
||||||
{:else}
|
{:else}
|
||||||
<button class="btn btn-primary" on:click={() => dispatch('unshare', user)}>Unshare</button>
|
<button class="btn btn-error" on:click={() => dispatch('unshare', user)}> Unshare </button>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -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 versionChangelog = 'https://github.com/seanmorley15/AdventureLog/releases/tag/v0.7.1';
|
||||||
export let appTitle = 'AdventureLog';
|
export let appTitle = 'AdventureLog';
|
||||||
export let copyrightYear = '2024';
|
export let copyrightYear = '2023-2025';
|
||||||
|
|
|
@ -289,6 +289,37 @@ export function getAdventureTypeLabel(type: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getRandomBackground() {
|
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);
|
const randomIndex = Math.floor(Math.random() * randomBackgrounds.backgrounds.length);
|
||||||
return randomBackgrounds.backgrounds[randomIndex] as Background;
|
return randomBackgrounds.backgrounds[randomIndex] as Background;
|
||||||
}
|
}
|
||||||
|
|
|
@ -86,9 +86,9 @@ export type Collection = {
|
||||||
description: string;
|
description: string;
|
||||||
is_public: boolean;
|
is_public: boolean;
|
||||||
adventures: Adventure[];
|
adventures: Adventure[];
|
||||||
created_at?: string;
|
created_at?: string | null;
|
||||||
start_date?: string;
|
start_date: string | null;
|
||||||
end_date?: string;
|
end_date: string | null;
|
||||||
transportations?: Transportation[];
|
transportations?: Transportation[];
|
||||||
notes?: Note[];
|
notes?: Note[];
|
||||||
checklists?: Checklist[];
|
checklists?: Checklist[];
|
||||||
|
|
|
@ -290,7 +290,7 @@
|
||||||
"public_profile": "Public Profile",
|
"public_profile": "Public Profile",
|
||||||
"public_tooltip": "With a public profile, users can share collections with you and view your profile on the users page.",
|
"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",
|
"email_required": "Email is required",
|
||||||
"new_password": "New Password",
|
"new_password": "New Password (6+ characters)",
|
||||||
"both_passwords_required": "Both passwords are required",
|
"both_passwords_required": "Both passwords are required",
|
||||||
"reset_failed": "Failed to reset password"
|
"reset_failed": "Failed to reset password"
|
||||||
},
|
},
|
||||||
|
@ -375,7 +375,8 @@
|
||||||
"create": "Create",
|
"create": "Create",
|
||||||
"collection_edit_success": "Collection edited successfully!",
|
"collection_edit_success": "Collection edited successfully!",
|
||||||
"error_editing_collection": "Error editing collection",
|
"error_editing_collection": "Error editing collection",
|
||||||
"edit_collection": "Edit Collection"
|
"edit_collection": "Edit Collection",
|
||||||
|
"public_collection": "Public Collection"
|
||||||
},
|
},
|
||||||
"notes": {
|
"notes": {
|
||||||
"note_deleted": "Note deleted successfully!",
|
"note_deleted": "Note deleted successfully!",
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
view: 'dayGridMonth',
|
view: 'dayGridMonth',
|
||||||
events: [...dates]
|
events: [...dates]
|
||||||
};
|
};
|
||||||
|
console.log(dates);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<h1 class="text-center text-2xl font-bold">{$t('adventures.adventure_calendar')}</h1>
|
<h1 class="text-center text-2xl font-bold">{$t('adventures.adventure_calendar')}</h1>
|
||||||
|
|
|
@ -2,8 +2,7 @@
|
||||||
import { enhance } from '$app/forms';
|
import { enhance } from '$app/forms';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import CollectionCard from '$lib/components/CollectionCard.svelte';
|
import CollectionCard from '$lib/components/CollectionCard.svelte';
|
||||||
import EditCollection from '$lib/components/EditCollection.svelte';
|
import CollectionModal from '$lib/components/CollectionModal.svelte';
|
||||||
import NewCollection from '$lib/components/NewCollection.svelte';
|
|
||||||
import NotFound from '$lib/components/NotFound.svelte';
|
import NotFound from '$lib/components/NotFound.svelte';
|
||||||
import type { Collection } from '$lib/types';
|
import type { Collection } from '$lib/types';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
|
@ -17,10 +16,10 @@
|
||||||
|
|
||||||
let currentSort = { attribute: 'name', order: 'asc' };
|
let currentSort = { attribute: 'name', order: 'asc' };
|
||||||
|
|
||||||
let isShowingCreateModal: boolean = false;
|
|
||||||
let newType: string = '';
|
let newType: string = '';
|
||||||
|
|
||||||
let resultsPerPage: number = 25;
|
let resultsPerPage: number = 25;
|
||||||
|
let isShowingCollectionModal: boolean = false;
|
||||||
|
|
||||||
let next: string | null = data.props.next || null;
|
let next: string | null = data.props.next || null;
|
||||||
let previous: string | null = data.props.previous || null;
|
let previous: string | null = data.props.previous || null;
|
||||||
|
@ -86,8 +85,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let collectionToEdit: Collection;
|
let collectionToEdit: Collection | null = null;
|
||||||
let isEditModalOpen: boolean = false;
|
|
||||||
|
|
||||||
function deleteAdventure(event: CustomEvent<string>) {
|
function deleteAdventure(event: CustomEvent<string>) {
|
||||||
collections = collections.filter((adventure) => adventure.id !== event.detail);
|
collections = collections.filter((adventure) => adventure.id !== event.detail);
|
||||||
|
@ -95,12 +93,12 @@
|
||||||
|
|
||||||
function createAdventure(event: CustomEvent<Collection>) {
|
function createAdventure(event: CustomEvent<Collection>) {
|
||||||
collections = [event.detail, ...collections];
|
collections = [event.detail, ...collections];
|
||||||
isShowingCreateModal = false;
|
isShowingCollectionModal = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function editCollection(event: CustomEvent<Collection>) {
|
function editCollection(event: CustomEvent<Collection>) {
|
||||||
collectionToEdit = event.detail;
|
collectionToEdit = event.detail;
|
||||||
isEditModalOpen = true;
|
isShowingCollectionModal = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveEdit(event: CustomEvent<Collection>) {
|
function saveEdit(event: CustomEvent<Collection>) {
|
||||||
|
@ -110,7 +108,7 @@
|
||||||
}
|
}
|
||||||
return adventure;
|
return adventure;
|
||||||
});
|
});
|
||||||
isEditModalOpen = false;
|
isShowingCollectionModal = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let sidebarOpen = false;
|
let sidebarOpen = false;
|
||||||
|
@ -120,18 +118,13 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if isShowingCreateModal}
|
{#if isShowingCollectionModal}
|
||||||
<NewCollection on:create={createAdventure} on:close={() => (isShowingCreateModal = false)} />
|
<CollectionModal
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if isEditModalOpen}
|
|
||||||
<EditCollection
|
|
||||||
{collectionToEdit}
|
{collectionToEdit}
|
||||||
on:close={() => (isEditModalOpen = false)}
|
on:close={() => (isShowingCollectionModal = false)}
|
||||||
on:saveEdit={saveEdit}
|
on:saveEdit={saveEdit}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div class="fixed bottom-4 right-4 z-[999]">
|
<div class="fixed bottom-4 right-4 z-[999]">
|
||||||
<div class="flex flex-row items-center justify-center gap-4">
|
<div class="flex flex-row items-center justify-center gap-4">
|
||||||
<div class="dropdown dropdown-top dropdown-end">
|
<div class="dropdown dropdown-top dropdown-end">
|
||||||
|
@ -147,17 +140,13 @@
|
||||||
<button
|
<button
|
||||||
class="btn btn-primary"
|
class="btn btn-primary"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
isShowingCreateModal = true;
|
collectionToEdit = null;
|
||||||
|
isShowingCollectionModal = true;
|
||||||
newType = 'visited';
|
newType = 'visited';
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{$t(`adventures.collection`)}</button
|
{$t(`adventures.collection`)}</button
|
||||||
>
|
>
|
||||||
|
|
||||||
<!-- <button
|
|
||||||
class="btn btn-primary"
|
|
||||||
on:click={() => (isShowingNewTrip = true)}>Trip Planner</button
|
|
||||||
> -->
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -2,10 +2,17 @@
|
||||||
import type { Adventure, Checklist, Collection, Note, Transportation } from '$lib/types';
|
import type { Adventure, Checklist, Collection, Note, Transportation } from '$lib/types';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import type { PageData } from './$types';
|
import type { PageData } from './$types';
|
||||||
import { goto } from '$app/navigation';
|
import { marked } from 'marked'; // Import the markdown parser
|
||||||
import Lost from '$lib/assets/undraw_lost.svg';
|
|
||||||
import { t } from 'svelte-i18n';
|
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 Plus from '~icons/mdi/plus';
|
||||||
import AdventureCard from '$lib/components/AdventureCard.svelte';
|
import AdventureCard from '$lib/components/AdventureCard.svelte';
|
||||||
import AdventureLink from '$lib/components/AdventureLink.svelte';
|
import AdventureLink from '$lib/components/AdventureLink.svelte';
|
||||||
|
@ -29,8 +36,58 @@
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
console.log(data);
|
console.log(data);
|
||||||
|
|
||||||
|
const renderMarkdown = (markdown: string) => {
|
||||||
|
return marked(markdown);
|
||||||
|
};
|
||||||
|
|
||||||
let collection: Collection;
|
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
|
||||||
|
$: {
|
||||||
|
if (adventures) {
|
||||||
|
dates = adventures.flatMap((adventure) =>
|
||||||
|
adventure.visits.map((visit) => ({
|
||||||
|
id: adventure.id,
|
||||||
|
start: visit.start_date, // Convert to ISO format if needed
|
||||||
|
end: visit.end_date || visit.start_date,
|
||||||
|
title: adventure.name + (adventure.category?.icon ? ' ' + adventure.category.icon : '')
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Update `options.events` when `dates` changes
|
||||||
|
options = { ...options, events: dates };
|
||||||
|
}
|
||||||
|
|
||||||
|
let currentView: string = 'itinerary';
|
||||||
|
|
||||||
let adventures: Adventure[] = [];
|
let adventures: Adventure[] = [];
|
||||||
|
|
||||||
let numVisited: number = 0;
|
let numVisited: number = 0;
|
||||||
|
@ -364,9 +421,20 @@
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if collection.description}
|
{#if collection && !collection.start_date && adventures.length == 0 && transportations.length == 0 && notes.length == 0 && checklists.length == 0}
|
||||||
<p class="text-center text-lg mb-2">{collection.description}</p>
|
<NotFound error={undefined} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
{#if collection.description}
|
||||||
|
<div class="flex justify-center mt-4">
|
||||||
|
<article
|
||||||
|
class="prose overflow-auto h-96 max-w-full p-4 border border-base-300 rounded-lg bg-base-300"
|
||||||
|
>
|
||||||
|
{@html renderMarkdown(collection.description)}
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if adventures.length > 0}
|
{#if adventures.length > 0}
|
||||||
<div class="flex items-center justify-center mb-4">
|
<div class="flex items-center justify-center mb-4">
|
||||||
<div class="stats shadow bg-base-300">
|
<div class="stats shadow bg-base-300">
|
||||||
|
@ -383,6 +451,42 @@
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
<div class="flex justify-center mx-auto">
|
||||||
|
<!-- svelte-ignore a11y-missing-attribute -->
|
||||||
|
<div role="tablist" class="tabs tabs-boxed tabs-lg max-w-xl">
|
||||||
|
<!-- svelte-ignore a11y-missing-attribute -->
|
||||||
|
<a
|
||||||
|
role="tab"
|
||||||
|
class="tab {currentView === 'itinerary' ? 'tab-active' : ''}"
|
||||||
|
tabindex="0"
|
||||||
|
on:click={() => (currentView = 'itinerary')}
|
||||||
|
on:keydown={(e) => e.key === 'Enter' && (currentView = 'itinerary')}>Itinerary</a
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
role="tab"
|
||||||
|
class="tab {currentView === 'all' ? 'tab-active' : ''}"
|
||||||
|
tabindex="0"
|
||||||
|
on:click={() => (currentView = 'all')}
|
||||||
|
on:keydown={(e) => e.key === 'Enter' && (currentView = 'all')}>All Linked Items</a
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
role="tab"
|
||||||
|
class="tab {currentView === 'calendar' ? 'tab-active' : ''}"
|
||||||
|
tabindex="0"
|
||||||
|
on:click={() => (currentView = 'calendar')}
|
||||||
|
on:keydown={(e) => e.key === 'Enter' && (currentView = 'calendar')}>Calendar</a
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
role="tab"
|
||||||
|
class="tab {currentView === 'map' ? 'tab-active' : ''}"
|
||||||
|
tabindex="0"
|
||||||
|
on:click={() => (currentView = 'map')}
|
||||||
|
on:keydown={(e) => e.key === 'Enter' && (currentView = 'map')}>Map</a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if currentView == 'all'}
|
||||||
{#if adventures.length > 0}
|
{#if adventures.length > 0}
|
||||||
<h1 class="text-center font-bold text-4xl mt-4 mb-2">{$t('adventures.linked_adventures')}</h1>
|
<h1 class="text-center font-bold text-4xl mt-4 mb-2">{$t('adventures.linked_adventures')}</h1>
|
||||||
|
|
||||||
|
@ -455,8 +559,10 @@
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if collection.start_date && collection.end_date}
|
{#if collection.start_date && collection.end_date}
|
||||||
|
{#if currentView == 'itinerary'}
|
||||||
<div class="hero bg-base-200 py-8 mt-8">
|
<div class="hero bg-base-200 py-8 mt-8">
|
||||||
<div class="hero-content text-center">
|
<div class="hero-content text-center">
|
||||||
<div class="max-w-md">
|
<div class="max-w-md">
|
||||||
|
@ -469,7 +575,9 @@
|
||||||
{/if}
|
{/if}
|
||||||
<p class="text-lg">
|
<p class="text-lg">
|
||||||
Dates: <span class="font-semibold"
|
Dates: <span class="font-semibold"
|
||||||
>{new Date(collection.start_date).toLocaleDateString(undefined, { timeZone: 'UTC' })} -
|
>{new Date(collection.start_date).toLocaleDateString(undefined, {
|
||||||
|
timeZone: 'UTC'
|
||||||
|
})} -
|
||||||
{new Date(collection.end_date).toLocaleDateString(undefined, {
|
{new Date(collection.end_date).toLocaleDateString(undefined, {
|
||||||
timeZone: 'UTC'
|
timeZone: 'UTC'
|
||||||
})}</span
|
})}</span
|
||||||
|
@ -497,7 +605,8 @@
|
||||||
numberOfDays
|
numberOfDays
|
||||||
)[dateString] || []}
|
)[dateString] || []}
|
||||||
{@const dayNotes =
|
{@const dayNotes =
|
||||||
groupNotesByDate(notes, new Date(collection.start_date), numberOfDays)[dateString] || []}
|
groupNotesByDate(notes, new Date(collection.start_date), numberOfDays)[dateString] ||
|
||||||
|
[]}
|
||||||
{@const dayChecklists =
|
{@const dayChecklists =
|
||||||
groupChecklistsByDate(checklists, new Date(collection.start_date), numberOfDays)[
|
groupChecklistsByDate(checklists, new Date(collection.start_date), numberOfDays)[
|
||||||
dateString
|
dateString
|
||||||
|
@ -580,7 +689,9 @@
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if currentView == 'map'}
|
||||||
<div class="card bg-base-200 shadow-xl my-8 mx-auto w-10/12">
|
<div class="card bg-base-200 shadow-xl my-8 mx-auto w-10/12">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h2 class="card-title text-3xl justify-center mb-4">Trip Map</h2>
|
<h2 class="card-title text-3xl justify-center mb-4">Trip Map</h2>
|
||||||
|
@ -647,6 +758,17 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if currentView == 'calendar'}
|
||||||
|
<div class="card bg-base-200 shadow-xl my-8 mx-auto w-10/12">
|
||||||
|
<div class="card-body">
|
||||||
|
<h2 class="card-title text-3xl justify-center mb-4">
|
||||||
|
{$t('adventures.adventure_calendar')}
|
||||||
|
</h2>
|
||||||
|
<Calendar {plugins} {options} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
|
|
|
@ -11,58 +11,63 @@
|
||||||
total_countries: number;
|
total_countries: number;
|
||||||
} | null;
|
} | null;
|
||||||
|
|
||||||
if (data.stats) {
|
stats = data.stats || null;
|
||||||
stats = data.stats;
|
|
||||||
} else {
|
|
||||||
stats = null;
|
|
||||||
}
|
|
||||||
console.log(stats);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<section class="min-h-screen bg-base-100 py-8 px-4">
|
||||||
|
<div class="flex flex-col items-center">
|
||||||
|
<!-- Profile Picture -->
|
||||||
{#if data.user.profile_pic}
|
{#if data.user.profile_pic}
|
||||||
<div class="avatar flex items-center justify-center">
|
<div class="avatar">
|
||||||
<div class="w-24 rounded">
|
<div
|
||||||
<!-- svelte-ignore a11y-missing-attribute -->
|
class="w-24 rounded-full ring ring-primary ring-offset-base-100 ring-offset-2 shadow-md"
|
||||||
<img src={data.user.profile_pic} class="w-24 rounded-full" />
|
>
|
||||||
|
<img src={data.user.profile_pic} alt="Profile" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
<!-- User Name -->
|
||||||
{#if data.user && data.user.first_name && data.user.last_name}
|
{#if data.user && data.user.first_name && data.user.last_name}
|
||||||
<h1 class="text-center text-4xl font-bold">
|
<h1 class="text-4xl font-bold text-primary mt-4">
|
||||||
{data.user.first_name}
|
{data.user.first_name}
|
||||||
{data.user.last_name}
|
{data.user.last_name}
|
||||||
</h1>
|
</h1>
|
||||||
{/if}
|
{/if}
|
||||||
<p class="text-center text-lg mt-2">{data.user.username}</p>
|
<p class="text-lg text-base-content mt-2">{data.user.username}</p>
|
||||||
|
|
||||||
|
<!-- Member Since -->
|
||||||
{#if data.user && data.user.date_joined}
|
{#if data.user && data.user.date_joined}
|
||||||
<p class="ml-1 text-lg text-center mt-4">{$t('profile.member_since')}</p>
|
<div class="mt-4 flex items-center text-center text-base-content">
|
||||||
<div class="flex items-center justify-center text-center">
|
<p class="text-lg font-medium">{$t('profile.member_since')}</p>
|
||||||
<iconify-icon icon="mdi:calendar" class="text-2xl"></iconify-icon>
|
<div class="flex items-center ml-2">
|
||||||
<p class="ml-1 text-xl">
|
<iconify-icon icon="mdi:calendar" class="text-2xl text-primary"></iconify-icon>
|
||||||
|
<p class="ml-2 text-lg">
|
||||||
{new Date(data.user.date_joined).toLocaleDateString(undefined, { timeZone: 'UTC' })}
|
{new Date(data.user.date_joined).toLocaleDateString(undefined, { timeZone: 'UTC' })}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Stats Section -->
|
||||||
{#if stats}
|
{#if stats}
|
||||||
<!-- divider -->
|
<div class="divider my-8"></div>
|
||||||
<div class="divider pr-8 pl-8"></div>
|
|
||||||
|
|
||||||
<h1 class="text-center text-2xl font-bold mt-8 mb-2">{$t('profile.user_stats')}</h1>
|
<h2 class="text-2xl font-bold text-center mb-6 text-primary">
|
||||||
|
{$t('profile.user_stats')}
|
||||||
|
</h2>
|
||||||
|
|
||||||
<div class="flex justify-center items-center">
|
<div class="flex justify-center">
|
||||||
<div class="stats stats-vertical lg:stats-horizontal shadow bg-base-200">
|
<div class="stats stats-vertical lg:stats-horizontal shadow bg-base-200">
|
||||||
<div class="stat">
|
<div class="stat">
|
||||||
<div class="stat-title">{$t('navbar.adventures')}</div>
|
<div class="stat-title">{$t('navbar.adventures')}</div>
|
||||||
<div class="stat-value text-center">{stats.adventure_count}</div>
|
<div class="stat-value text-center">{stats.adventure_count}</div>
|
||||||
<!-- <div class="stat-desc">Jan 1st - Feb 1st</div> -->
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="stat">
|
<div class="stat">
|
||||||
<div class="stat-title">{$t('navbar.collections')}</div>
|
<div class="stat-title">{$t('navbar.collections')}</div>
|
||||||
<div class="stat-value text-center">{stats.trips_count}</div>
|
<div class="stat-value text-center">{stats.trips_count}</div>
|
||||||
<!-- <div class="stat-desc">↘︎ 90 (14%)</div> -->
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="stat">
|
<div class="stat">
|
||||||
|
@ -70,7 +75,7 @@
|
||||||
<div class="stat-value text-center">
|
<div class="stat-value text-center">
|
||||||
{Math.round((stats.country_count / stats.total_countries) * 100)}%
|
{Math.round((stats.country_count / stats.total_countries) * 100)}%
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-desc">
|
<div class="stat-desc text-center">
|
||||||
{stats.country_count}/{stats.total_countries}
|
{stats.country_count}/{stats.total_countries}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -80,13 +85,14 @@
|
||||||
<div class="stat-value text-center">
|
<div class="stat-value text-center">
|
||||||
{Math.round((stats.visited_region_count / stats.total_regions) * 100)}%
|
{Math.round((stats.visited_region_count / stats.total_regions) * 100)}%
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-desc">
|
<div class="stat-desc text-center">
|
||||||
{stats.visited_region_count}/{stats.total_regions}
|
{stats.visited_region_count}/{stats.total_regions}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
</section>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
<title>Profile | AdventureLog</title>
|
<title>Profile | AdventureLog</title>
|
||||||
|
|
|
@ -4,32 +4,47 @@
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<h1 class="text-center font-extrabold text-4xl mb-6">{$t('settings.reset_password')}</h1>
|
<section class="flex flex-col items-center justify-center min-h-screen px-4 py-8 bg-base-100">
|
||||||
|
<h1 class="text-4xl font-bold text-center mb-6 text-primary">{$t('settings.reset_password')}</h1>
|
||||||
|
|
||||||
<div class="flex justify-center">
|
<div class="w-full max-w-md p-6 shadow-lg rounded-lg bg-base-200">
|
||||||
<form method="post" action="?/forgotPassword" class="w-full max-w-xs" use:enhance>
|
<form method="post" action="?/forgotPassword" class="flex flex-col space-y-4" use:enhance>
|
||||||
<label for="email">{$t('auth.email')}</label>
|
<div class="form-control">
|
||||||
|
<label for="email" class="label">
|
||||||
|
<span class="label-text">{$t('auth.email')}</span>
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
name="email"
|
name="email"
|
||||||
type="email"
|
type="email"
|
||||||
id="email"
|
id="email"
|
||||||
class="block mb-2 input input-bordered w-full max-w-xs"
|
placeholder="Enter your email"
|
||||||
/><br />
|
class="input input-bordered w-full"
|
||||||
<button class="py-2 px-4 btn btn-primary mr-2">{$t('settings.reset_password')}</button>
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-control mt-4">
|
||||||
|
<button type="submit" class="btn btn-primary w-full">
|
||||||
|
{$t('settings.reset_password')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
{#if $page.form?.message}
|
{#if $page.form?.message}
|
||||||
<div class="text-center text-error mt-4">
|
<div class="mt-4 text-center text-error">
|
||||||
{$t(`settings.${$page.form?.message}`)}
|
{$t(`settings.${$page.form?.message}`)}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if $page.form?.success}
|
{#if $page.form?.success}
|
||||||
<div class="text-center text-success mt-4">
|
<div class="mt-4 text-center text-success">
|
||||||
{$t('settings.possible_reset')}
|
{$t('settings.possible_reset')}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
<title>Forgot Password</title>
|
<title>Reset Password</title>
|
||||||
<meta name="description" content="Reset your password for AdventureLog." />
|
<meta name="description" content="Reset your password for AdventureLog." />
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
|
@ -1,16 +1,20 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { enhance } from '$app/forms';
|
import { enhance } from '$app/forms';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import type { PageData } from '../../../$types';
|
// import type { PageData } from '../../../$types';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
|
|
||||||
export let data: PageData;
|
// export let data: PageData;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<h1 class="text-center font-bold text-4xl mb-4">{$t('settings.change_password')}</h1>
|
<section class="flex flex-col items-center justify-center min-h-screen px-4 py-8 bg-base-100">
|
||||||
|
<h1 class="text-4xl font-bold text-center mb-6 text-primary">
|
||||||
|
{$t('settings.change_password')}
|
||||||
|
</h1>
|
||||||
|
|
||||||
<form method="POST" use:enhance class="flex flex-col items-center justify-center space-y-4">
|
<div class="w-full max-w-md p-6 shadow-lg rounded-lg bg-base-200">
|
||||||
<div class="w-full max-w-xs">
|
<form method="POST" use:enhance class="flex flex-col space-y-6">
|
||||||
|
<div class="form-control">
|
||||||
<label for="password" class="label">
|
<label for="password" class="label">
|
||||||
<span class="label-text">{$t('auth.new_password')}</span>
|
<span class="label-text">{$t('auth.new_password')}</span>
|
||||||
</label>
|
</label>
|
||||||
|
@ -18,12 +22,13 @@
|
||||||
type="password"
|
type="password"
|
||||||
id="password"
|
id="password"
|
||||||
name="password"
|
name="password"
|
||||||
|
placeholder="Enter new password"
|
||||||
required
|
required
|
||||||
class="input input-bordered w-full"
|
class="input input-bordered w-full"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="w-full max-w-xs">
|
<div class="form-control">
|
||||||
<label for="confirm_password" class="label">
|
<label for="confirm_password" class="label">
|
||||||
<span class="label-text">{$t('auth.confirm_password')}</span>
|
<span class="label-text">{$t('auth.confirm_password')}</span>
|
||||||
</label>
|
</label>
|
||||||
|
@ -31,23 +36,31 @@
|
||||||
type="password"
|
type="password"
|
||||||
id="confirm_password"
|
id="confirm_password"
|
||||||
name="confirm_password"
|
name="confirm_password"
|
||||||
|
placeholder="Confirm new password"
|
||||||
required
|
required
|
||||||
class="input input-bordered w-full"
|
class="input input-bordered w-full"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="submit" class="btn btn-primary">
|
<div class="form-control mt-4">
|
||||||
|
<button type="submit" class="btn btn-primary w-full">
|
||||||
{$t('settings.reset_password')}
|
{$t('settings.reset_password')}
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
{#if $page.form?.message}
|
{#if $page.form?.message}
|
||||||
<div class="text-error">
|
<div class="mt-4 text-center text-error">
|
||||||
{$t($page.form?.message)}
|
{$t($page.form?.message)}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</form>
|
</form>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
<title>Password Reset Confirm</title>
|
<title>Change Password</title>
|
||||||
<meta name="description" content="Confirm your password reset and make a new password." />
|
<meta
|
||||||
|
name="description"
|
||||||
|
content="Confirm your password reset and create a new password for AdventureLog."
|
||||||
|
/>
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
|
@ -5,10 +5,27 @@
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<section class="flex flex-col items-center justify-center min-h-screen px-4 py-8 bg-base-100">
|
||||||
|
<div class="w-full max-w-lg p-6 shadow-lg rounded-lg bg-base-200 text-center">
|
||||||
{#if data.verified}
|
{#if data.verified}
|
||||||
<h1>{$t('settings.email_verified')}</h1>
|
<h1 class="text-4xl font-bold text-success mb-4">
|
||||||
<p>{$t('settings.email_verified_success')}</p>
|
{$t('settings.email_verified')}
|
||||||
|
</h1>
|
||||||
|
<p class="text-lg text-base-content">
|
||||||
|
{$t('settings.email_verified_success')}
|
||||||
|
</p>
|
||||||
{:else}
|
{:else}
|
||||||
<h1>{$t('settings.email_verified_error')}</h1>
|
<h1 class="text-4xl font-bold text-error mb-4">
|
||||||
<p>{$t('settings.email_verified_erorr_desc')}</p>
|
{$t('settings.email_verified_error')}
|
||||||
|
</h1>
|
||||||
|
<p class="text-lg text-base-content">
|
||||||
|
{$t('settings.email_verified_erorr_desc')}
|
||||||
|
</p>
|
||||||
{/if}
|
{/if}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<title>Email Verification</title>
|
||||||
|
<meta name="description" content="View your email verification status for AdventureLog." />
|
||||||
|
</svelte:head>
|
||||||
|
|
|
@ -6,7 +6,7 @@ import type { PageServerLoad } from './$types';
|
||||||
const endpoint = PUBLIC_SERVER_URL || 'http://localhost:8000';
|
const endpoint = PUBLIC_SERVER_URL || 'http://localhost:8000';
|
||||||
|
|
||||||
export const load = (async (event) => {
|
export const load = (async (event) => {
|
||||||
const id = event.params.id;
|
const id = event.params.id.toUpperCase();
|
||||||
|
|
||||||
let regions: Region[] = [];
|
let regions: Region[] = [];
|
||||||
let visitedRegions: VisitedRegion[] = [];
|
let visitedRegions: VisitedRegion[] = [];
|
||||||
|
|
BIN
frontend/static/backgrounds/adventurelog_christmas.webp
Normal file
BIN
frontend/static/backgrounds/adventurelog_christmas.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 179 KiB |
BIN
frontend/static/backgrounds/adventurelog_new_year.webp
Normal file
BIN
frontend/static/backgrounds/adventurelog_new_year.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 340 KiB |
Loading…
Add table
Add a link
Reference in a new issue