1
0
Fork 0
mirror of https://github.com/seanmorley15/AdventureLog.git synced 2025-07-25 15:59:38 +02:00

migration to new backend

This commit is contained in:
Sean Morley 2024-07-08 11:44:39 -04:00
parent 28a5d423c2
commit 9abe9fb315
309 changed files with 21476 additions and 24132 deletions

View file

@ -0,0 +1,64 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
import { onMount } from 'svelte';
let modal: HTMLDialogElement;
import { appVersion, copyrightYear, versionChangelog } from '$lib/config';
onMount(() => {
modal = document.getElementById('my_modal_1') as HTMLDialogElement;
if (modal) {
modal.showModal();
}
});
function close() {
dispatch('close');
}
function handleKeydown(event: KeyboardEvent) {
if (event.key === 'Escape') {
dispatch('close');
}
}
</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">About AdventureLog</h3>
<p class="py-1">
AdventureLog <a
target="_blank"
rel="noopener noreferrer"
class="text-primary-500 underline"
href={versionChangelog}>{appVersion}</a
>
</p>
<p class="py-1">
© {copyrightYear}
<a
href="https://github.com/seanmorley15"
target="_blank"
rel="noopener noreferrer"
class="text-primary-500 underline">Sean Morley</a
>
</p>
<p class="py-1">Liscensed under the GPL-3.0 License.</p>
<p class="py-1">
<a
href="https://github.com/seanmorley15/AdventureLog"
target="_blank"
rel="noopener noreferrer"
class="text-primary-500 underline">Source Code</a
>
</p>
<p class="py-1">Made with ❤️ in the United States.</p>
<div
class="modal-action items-center"
style="display: flex; flex-direction: column; align-items: center; width: 100%;"
></div>
<button class="btn btn-primary" on:click={close}>Close</button>
</div>
</dialog>

View file

@ -0,0 +1,116 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte';
import { goto } from '$app/navigation';
import type { Adventure } from '$lib/types';
const dispatch = createEventDispatcher();
import Launch from '~icons/mdi/launch';
import FileDocumentEdit from '~icons/mdi/file-document-edit';
import TrashCan from '~icons/mdi/trash-can-outline';
import Calendar from '~icons/mdi/calendar';
import MapMarker from '~icons/mdi/map-marker';
import { addToast } from '$lib/toasts';
export let type: string;
export let adventure: Adventure;
async function deleteAdventure() {
let res = await fetch(`/adventures/${adventure.id}?/delete`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
});
if (res.ok) {
console.log('Adventure deleted');
addToast('info', 'Adventure deleted successfully!');
dispatch('delete', adventure.id);
} else {
console.log('Error deleting adventure');
}
}
function editAdventure() {
dispatch('edit', adventure);
}
</script>
<div
class="card w-full max-w-xs sm:max-w-sm md:max-w-md lg:max-w-md xl:max-w-md bg-primary-content shadow-xl overflow-hidden text-base-content"
>
<figure>
<!-- svelte-ignore a11y-img-redundant-alt -->
{#if adventure.image && adventure.image !== ''}
<img
src={adventure.image}
alt="Adventure Image"
class="w-full h-48 object-cover"
crossorigin="anonymous"
/>
{:else}
<img
src={'https://placehold.co/300?text=No%20Image%20Found&font=roboto'}
alt="No image available"
class="w-full h-48 object-cover"
/>
{/if}
</figure>
<div class="card-body">
<h2 class="card-title break-words text-wrap">
{adventure.name}
</h2>
{#if adventure.location && adventure.location !== ''}
<div class="inline-flex items-center">
<MapMarker class="w-5 h-5 mr-1" />
<p class="ml-.5">{adventure.location}</p>
</div>
{/if}
{#if adventure.date && adventure.date !== ''}
<div class="inline-flex items-center">
<Calendar class="w-5 h-5 mr-1" />
<p>{new Date(adventure.date).toLocaleDateString()}</p>
</div>
{/if}
{#if adventure.activity_types && adventure.activity_types.length > 0}
<ul class="flex flex-wrap">
{#each adventure.activity_types as activity}
<div class="badge badge-primary mr-1 text-md font-semibold pb-2 pt-1 mb-1">
{activity}
</div>
{/each}
</ul>
{/if}
<div class="card-actions justify-end mt-2">
{#if type == 'visited'}
<button class="btn btn-primary" on:click={() => goto(`/adventures/${adventure.id}`)}
><Launch class="w-6 h-6" /></button
>
<button class="btn btn-primary" on:click={editAdventure}>
<FileDocumentEdit class="w-6 h-6" />
</button>
<button class="btn btn-secondary" on:click={deleteAdventure}
><TrashCan class="w-6 h-6" /></button
>
{/if}
{#if type == 'planned'}
<button class="btn btn-primary" on:click={() => goto(`/adventures/${adventure.id}`)}
><Launch class="w-6 h-6" /></button
>
<button class="btn btn-primary" on:click={editAdventure}>
<FileDocumentEdit class="w-6 h-6" />
</button>
<button class="btn btn-secondary" on:click={deleteAdventure}
><TrashCan class="w-6 h-6" /></button
>
{/if}
{#if type == 'featured'}
<!-- TODO: option to add to visited or featured -->
<button class="btn btn-primary" on:click={() => goto(`/adventures/${adventure.id}`)}
><Launch class="w-6 h-6" /></button
>
{/if}
</div>
</div>
</div>

View file

@ -0,0 +1,39 @@
<script lang="ts">
import { goto } from '$app/navigation';
export let user: any;
let letter: string = user.first_name[0];
if (user && !user.first_name && user.username) {
letter = user.username[0];
}
</script>
<div class="dropdown dropdown-bottom dropdown-end" tabindex="0" role="button">
<div class="avatar placeholder">
<div class="bg-neutral text-neutral-content rounded-full w-10 ml-4">
{#if user.profile_pic}
<img src={user.profile_pic} alt="" />
{:else}
<span class="text-2xl -mt-1">{letter}</span>
{/if}
</div>
</div>
<!-- svelte-ignore a11y-missing-attribute -->
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
<ul
tabindex="0"
class="dropdown-content z-[1] menu p-2 shadow bg-primary-content mt-2 rounded-box w-52"
>
<!-- svelte-ignore a11y-missing-attribute -->
<!-- svelte-ignore a11y-missing-attribute -->
<p class="text-lg ml-4 font-bold">Hi, {user.first_name} {user.last_name}</p>
<li><button on:click={() => goto('/profile')}>Profile</button></li>
<li><button on:click={() => goto('/visited')}>My Log</button></li>
<li><button on:click={() => goto('/settings')}>User Settings</button></li>
<form method="post">
<li><button formaction="/?/logout">Logout</button></li>
</form>
</ul>
</div>

View file

@ -0,0 +1,34 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { getFlag } from '$lib';
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
export let countryName: string | undefined = undefined;
export let countryCode: string;
async function nav() {
goto(`/worldtravel/${countryCode}`);
}
</script>
<div
class="card w-full max-w-xs sm:max-w-sm md:max-w-md lg:max-w-md xl:max-w-md bg-primary-content shadow-xl overflow-hidden text-base-content"
>
<figure>
<!-- svelte-ignore a11y-img-redundant-alt -->
<img
src={getFlag(240, countryCode) ||
'https://placehold.co/300?text=No%20Image%20Found&font=roboto'}
alt="No image available"
class="w-full h-48 object-cover"
/>
</figure>
<div class="card-body">
<h2 class="card-title overflow-ellipsis">{countryName}</h2>
<div class="card-actions justify-end">
<!-- <button class="btn btn-info" on:click={moreInfo}>More Info</button> -->
<button class="btn btn-primary" on:click={nav}>Open</button>
</div>
</div>
</div>

View file

@ -0,0 +1,256 @@
<script lang="ts">
export let adventureToEdit: Adventure;
import { createEventDispatcher } from 'svelte';
import type { Adventure } from '$lib/types';
const dispatch = createEventDispatcher();
import { onMount } from 'svelte';
import { addToast } from '$lib/toasts';
let modal: HTMLDialogElement;
console.log(adventureToEdit.id);
let originalName = adventureToEdit.name;
let isPointModalOpen: boolean = false;
import MapMarker from '~icons/mdi/map-marker';
import Calendar from '~icons/mdi/calendar';
import Notebook from '~icons/mdi/notebook';
import ClipboardList from '~icons/mdi/clipboard-list';
import Image from '~icons/mdi/image';
import Star from '~icons/mdi/star';
import Attachment from '~icons/mdi/attachment';
import PointSelectionModal from './PointSelectionModal.svelte';
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);
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) {
if (typeof adventureToEdit.activity_types === 'string') {
adventureToEdit.activity_types = (adventureToEdit.activity_types as string)
.split(',')
.map((activity_type) => activity_type.trim())
.filter((activity_type) => activity_type !== '' && activity_type !== ',');
// Remove duplicates
adventureToEdit.activity_types = Array.from(new Set(adventureToEdit.activity_types));
}
adventureToEdit.image = data[1];
adventureToEdit.link = data[2];
addToast('success', 'Adventure edited successfully!');
dispatch('saveEdit', adventureToEdit);
close();
} else {
addToast('warning', 'Error editing adventure');
console.log('Error editing adventure');
}
}
}
function setLongLat(event: CustomEvent<[number, number]>) {
console.log(event.detail);
adventureToEdit.latitude = event.detail[1];
adventureToEdit.longitude = event.detail[0];
isPointModalOpen = false;
}
</script>
{#if isPointModalOpen}
<PointSelectionModal
longitude={adventureToEdit.longitude}
latitude={adventureToEdit.latitude}
on:close={() => (isPointModalOpen = false)}
on:submit={setLongLat}
/>
{/if}
<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">Edit Adventure: {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="/adventures?/edit">
<div class="mb-2">
<input
type="text"
id="adventureId"
name="adventureId"
hidden
readonly
bind:value={adventureToEdit.id}
class="input input-bordered w-full max-w-xs mt-1"
/>
<input
type="text"
id="type"
name="type"
hidden
readonly
bind:value={adventureToEdit.type}
class="input input-bordered w-full max-w-xs mt-1"
/>
<label for="name">Name</label><br />
<input
type="text"
name="name"
id="name"
bind:value={adventureToEdit.name}
class="input input-bordered w-full max-w-xs mt-1"
/>
</div>
<div class="mb-2">
<label for="location">Location<MapMarker class="inline-block -mt-1 mb-1 w-6 h-6" /></label
><br />
<input
type="text"
id="location"
name="location"
bind:value={adventureToEdit.location}
class="input input-bordered w-full max-w-xs mt-1"
/>
</div>
<div class="mb-2">
<label for="date">Date <Calendar class="inline-block mb-1 w-6 h-6" /></label><br />
<input
type="date"
id="date"
name="date"
bind:value={adventureToEdit.date}
class="input input-bordered w-full max-w-xs mt-1"
/>
</div>
<div class="mb-2">
<label for="date">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={adventureToEdit.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>
<div class="mb-2">
<label for="activityTypes"
>Activity Types <ClipboardList class="inline-block -mt-1 mb-1 w-6 h-6" /></label
><br />
<input
type="text"
id="activity_types"
name="activity_types"
bind:value={adventureToEdit.activity_types}
class="input input-bordered w-full max-w-xs mt-1"
/>
</div>
<div class="mb-2">
<label for="image">Image <Image class="inline-block -mt-1 mb-1 w-6 h-6" /></label><br />
<input
type="file"
id="image"
name="image"
bind:value={adventureToEdit.image}
class="file-input file-input-bordered w-full max-w-xs mt-1"
/>
</div>
<div class="mb-2">
<label for="link">Link <Attachment class="inline-block -mt-1 mb-1 w-6 h-6" /></label><br
/>
<input
type="url"
id="link"
name="link"
bind:value={adventureToEdit.link}
class="input input-bordered w-full max-w-xs mt-1"
/>
</div>
<div class="mb-2">
<label for="rating">Rating <Star class="inline-block -mt-1 mb-1 w-6 h-6" /></label><br />
<input
type="number"
min="0"
max="5"
name="rating"
id="rating"
bind:value={adventureToEdit.rating}
class="input input-bordered w-full max-w-xs mt-1"
/>
</div>
<input
type="text"
id="latitude"
hidden
name="latitude"
bind:value={adventureToEdit.latitude}
class="input input-bordered w-full max-w-xs mt-1"
/>
<input
type="text"
id="longitude"
hidden
name="longitude"
bind:value={adventureToEdit.longitude}
class="input input-bordered w-full max-w-xs mt-1"
/>
<div class="mb-2">
<button
type="button"
class="btn btn-secondary"
on:click={() => (isPointModalOpen = true)}
>
{adventureToEdit.latitude && adventureToEdit.longitude ? 'Change' : 'Select'}
Location</button
>
</div>
<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}>Close</button>
</form>
<div class="flex items-center justify-center flex-wrap gap-4 mt-4"></div>
</div>
</div>
</dialog>

View file

@ -0,0 +1,147 @@
<script lang="ts">
import { enhance } from '$app/forms';
import { goto } from '$app/navigation';
export let data: any;
import type { SubmitFunction } from '@sveltejs/kit';
import DotsHorizontal from '~icons/mdi/dots-horizontal';
import WeatherSunny from '~icons/mdi/weather-sunny';
import WeatherNight from '~icons/mdi/weather-night';
import Forest from '~icons/mdi/forest';
import Flower from '~icons/mdi/flower';
import Water from '~icons/mdi/water';
import AboutModal from './AboutModal.svelte';
import Avatar from './Avatar.svelte';
let isAboutModalOpen: boolean = false;
const submitUpdateTheme: SubmitFunction = ({ action }) => {
const theme = action.searchParams.get('theme');
console.log('theme', theme);
if (theme) {
document.documentElement.setAttribute('data-theme', theme);
}
};
</script>
{#if isAboutModalOpen}
<AboutModal on:close={() => (isAboutModalOpen = false)} />
{/if}
<div class="navbar bg-base-100">
<div class="navbar-start">
<div class="dropdown">
<div tabindex="0" role="button" class="btn btn-ghost lg:hidden">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
><path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M4 6h16M4 12h8m-8 6h16"
/></svg
>
</div>
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
<ul
tabindex="0"
class="menu menu-sm dropdown-content mt-3 z-[1] p-2 shadow bg-base-100 rounded-box w-52 gap-2"
>
{#if data.user}
<li>
<button on:click={() => goto('/visited')}>Visited</button>
</li>
<li>
<button on:click={() => goto('/planner')}>Planner</button>
</li>
<li>
<button on:click={() => goto('/worldtravel')}>World Travel</button>
</li>
<li>
<button on:click={() => goto('/featured')}>Featured</button>
</li>
{/if}
{#if !data.user}
<li>
<button class="btn btn-primary" on:click={() => goto('/login')}>Login</button>
</li>
<li>
<button class="btn btn-primary" on:click={() => goto('/signup')}>Signup</button>
</li>
{/if}
</ul>
</div>
<a class="btn btn-ghost text-xl" href="/"
>AdventureLog <img src="/favicon.png" alt="Map Logo" class="w-8" /></a
>
</div>
<div class="navbar-center hidden lg:flex">
<ul class="menu menu-horizontal px-1 gap-2">
{#if data.user}
<li>
<button class="btn btn-neutral" on:click={() => goto('/visited')}>Visited</button>
</li>
<li>
<button class="btn btn-neutral" on:click={() => goto('/planner')}>Planner</button>
</li>
<li>
<button class="btn btn-neutral" on:click={() => goto('/worldtravel')}>World Travel</button
>
</li>
<li>
<button class="btn btn-neutral" on:click={() => goto('/featured')}>Featured</button>
</li>
{/if}
{#if !data.user}
<li>
<button class="btn btn-primary" on:click={() => goto('/login')}>Login</button>
</li>
<li>
<button class="btn btn-primary" on:click={() => goto('/signup')}>Signup</button>
</li>
{/if}
</ul>
</div>
<div class="navbar-end">
{#if data.user}
<Avatar user={data.user} />
{/if}
<div class="dropdown dropdown-bottom dropdown-end">
<div tabindex="0" role="button" class="btn m-1 ml-4">
<DotsHorizontal class="w-6 h-6" />
</div>
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
<ul tabindex="0" class="dropdown-content z-[1] menu p-2 shadow bg-base-100 rounded-box w-52">
<button class="btn" on:click={() => (isAboutModalOpen = true)}>About AdventureLog</button>
<p class="font-bold m-4 text-lg">Theme Selection</p>
<form method="POST" use:enhance={submitUpdateTheme}>
<li>
<button formaction="/?/setTheme&theme=light"
>Light<WeatherSunny class="w-6 h-6" />
</button>
</li>
<li>
<button formaction="/?/setTheme&theme=dark">Dark<WeatherNight class="w-6 h-6" /></button
>
</li>
<li>
<button formaction="/?/setTheme&theme=night"
>Night<WeatherNight class="w-6 h-6" /></button
>
</li>
<li>
<button formaction="/?/setTheme&theme=forest">Forest<Forest class="w-6 h-6" /></button>
<button formaction="/?/setTheme&theme=garden">Garden<Flower class="w-6 h-6" /></button>
<button formaction="/?/setTheme&theme=aqua">Aqua<Water class="w-6 h-6" /></button>
</li>
</form>
</ul>
</div>
</div>
</div>

View file

@ -0,0 +1,267 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte';
import type { Adventure, Point } from '$lib/types';
import { onMount } from 'svelte';
import { enhance } from '$app/forms';
import { addToast } from '$lib/toasts';
import PointSelectionModal from './PointSelectionModal.svelte';
export let type: string = 'visited';
let newAdventure: Adventure = {
id: NaN,
type: type,
name: '',
location: '',
date: '',
description: '',
activity_types: [],
rating: NaN,
link: '',
image: '',
user_id: NaN,
latitude: null,
longitude: null,
is_public: false
};
let image: File;
let isPointModalOpen: boolean = false;
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);
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];
let image_url = data[3];
let link = data[4];
newAdventure.image = image_url;
newAdventure.id = id;
newAdventure.user_id = user_id;
newAdventure.link = link;
// turn the activity_types string into an array by splitting it at the commas
if (typeof newAdventure.activity_types === 'string') {
newAdventure.activity_types = (newAdventure.activity_types as string)
.split(',')
.map((activity_type) => activity_type.trim())
.filter((activity_type) => activity_type !== '' && activity_type !== ',');
// Remove duplicates
newAdventure.activity_types = Array.from(new Set(newAdventure.activity_types));
}
console.log(newAdventure);
dispatch('create', newAdventure);
addToast('success', 'Adventure created successfully!');
close();
}
}
}
function setLongLat(event: CustomEvent<[number, number]>) {
console.log(event.detail);
newAdventure.latitude = event.detail[1];
newAdventure.longitude = event.detail[0];
isPointModalOpen = false;
}
</script>
{#if isPointModalOpen}
<PointSelectionModal on:close={() => (isPointModalOpen = false)} on:submit={setLongLat} />
{/if}
<!-- 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">New {type} Adventure</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="/adventures?/create"
>
<input
type="text"
name="type"
id="type"
value={type}
hidden
readonly
class="input input-bordered w-full max-w-xs mt-1"
/>
<div class="mb-2">
<label for="name">Name</label><br />
<input
type="text"
id="name"
name="name"
bind:value={newAdventure.name}
class="input input-bordered w-full max-w-xs mt-1"
required
/>
</div>
<div class="mb-2">
<label for="location"
>Location<iconify-icon icon="mdi:map-marker" class="text-lg ml-0.5 -mb-0.5"
></iconify-icon></label
><br />
<input
type="text"
id="location"
name="location"
bind:value={newAdventure.location}
class="input input-bordered w-full max-w-xs mt-1"
/>
</div>
<div class="mb-2">
<label for="date"
>Date<iconify-icon icon="mdi:calendar" class="text-lg ml-1 -mb-0.5"
></iconify-icon></label
><br />
<input
type="date"
id="date"
name="date"
bind:value={newAdventure.date}
class="input input-bordered w-full max-w-xs mt-1"
/>
</div>
<div class="mb-2">
<label for="description"
>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={newAdventure.description}
class="input input-bordered w-full max-w-xs mt-1 mb-2"
/>
</div>
</div>
<div class="mb-2">
<label for="activity_types"
>Activity Types <iconify-icon icon="mdi:clipboard-list" class="text-xl -mb-1"
></iconify-icon></label
><br />
<input
type="text"
name="activity_types"
id="activity_types"
bind:value={newAdventure.activity_types}
class="input input-bordered w-full max-w-xs mt-1"
/>
</div>
<div class="mb-2">
<label for="rating"
>Rating <iconify-icon icon="mdi:star" class="text-xl -mb-1"></iconify-icon></label
><br />
<input
type="number"
min="0"
max="5"
bind:value={newAdventure.rating}
id="rating"
name="rating"
class="input input-bordered w-full max-w-xs mt-1"
/>
</div>
<div class="mb-2">
<label for="link"
>Link <iconify-icon icon="mdi:link" class="text-xl -mb-1"></iconify-icon></label
><br />
<input
type="text"
id="link"
name="link"
bind:value={newAdventure.link}
class="input input-bordered w-full max-w-xs mt-1"
/>
</div>
<div class="mb-2">
<label for="image"
>Image <iconify-icon icon="mdi:image" class="text-xl -mb-1"></iconify-icon></label
><br />
<input
type="file"
id="image"
name="image"
bind:value={image}
class="file-input file-input-bordered w-full max-w-xs mt-1"
/>
</div>
<div class="mb-2">
<input
type="text"
id="latitude"
hidden
name="latitude"
bind:value={newAdventure.latitude}
class="input input-bordered w-full max-w-xs mt-1"
/>
</div>
<div class="mb-2">
<input
type="text"
id="longitude"
name="longitude"
hidden
bind:value={newAdventure.longitude}
class="input input-bordered w-full max-w-xs mt-1"
/>
<div class="mb-2">
<button
type="button"
class="btn btn-secondary"
on:click={() => (isPointModalOpen = true)}>Define Location</button
>
</div>
<button type="submit" class="btn btn-primary mr-4 mt-4">Create</button>
<button type="button" class="btn mt-4" on:click={close}>Close</button>
</div>
</form>
</div>
</div>
</dialog>

View file

@ -0,0 +1,77 @@
<script lang="ts">
// @ts-nocheck
import type { Point } from '$lib/types';
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
import { onMount } from 'svelte';
let modal: HTMLDialogElement;
import { DefaultMarker, MapEvents, MapLibre, Popup } from 'svelte-maplibre';
let markers: Point[] = [];
export let longitude: number | null = null;
export let latitude: number | null = null;
function addMarker(e: CustomEvent<MouseEvent>) {
markers = [];
markers = [...markers, { lngLat: e.detail.lngLat, name: 'Marker 1' }];
console.log(markers);
}
onMount(() => {
modal = document.getElementById('my_modal_1') as HTMLDialogElement;
if (modal) {
modal.showModal();
}
if (longitude && latitude) {
markers = [{ lngLat: { lng: longitude, lat: latitude }, name: 'Marker 1' }];
}
});
function close() {
dispatch('close');
}
function handleKeydown(event: KeyboardEvent) {
if (event.key === 'Escape') {
dispatch('close');
}
}
function submit() {
if (markers.length === 0) {
alert('Please select a point on the map');
return;
}
let coordArray: [number, number] = [markers[0].lngLat.lng, markers[0].lngLat.lat];
console.log(coordArray);
dispatch('submit', coordArray);
}
</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 mb-4">Choose a Point</h3>
<MapLibre
style="https://basemaps.cartocdn.com/gl/positron-gl-style/style.json"
class="relative aspect-[9/16] max-h-[70vh] w-full sm:aspect-video sm:max-h-full"
standardControls
>
<!-- MapEvents gives you access to map events even from other components inside the map,
where you might not have access to the top-level `MapLibre` component. In this case
it would also work to just use on:click on the MapLibre component itself. -->
<MapEvents on:click={addMarker} />
{#each markers as marker}
<DefaultMarker lngLat={marker.lngLat} />
{/each}
</MapLibre>
<div class="mb-4 mt-4"></div>
<button class="btn btn-primary" on:click={submit}>Submit</button>
<button class="btn btn-neutral" on:click={close}>Close</button>
</div>
</dialog>

View file

@ -0,0 +1,72 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { addToast } from '$lib/toasts';
import type { Region, VisitedRegion } from '$lib/types';
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
export let region: Region;
export let visited: boolean;
export let visit_id: number | undefined | null;
console.log(visit_id);
async function markVisited() {
let res = await fetch(`/worldtravel?/markVisited`, {
method: 'POST',
body: JSON.stringify({ regionId: region.id })
});
if (res.ok) {
// visited = true;
const result = await res.json();
const data = JSON.parse(result.data);
if (data[1] !== undefined) {
console.log('New adventure created with id:', data[3]);
let visit_id = data[3];
let region_id = data[5];
let user_id = data[4];
let newVisit: VisitedRegion = {
id: visit_id,
region: region_id,
user_id: user_id
};
addToast('success', `Visit to ${region.name} marked`);
dispatch('visit', newVisit);
}
} else {
console.error('Failed to mark region as visited');
}
}
async function removeVisit() {
let res = await fetch(`/worldtravel?/removeVisited`, {
method: 'POST',
body: JSON.stringify({ visitId: visit_id })
});
if (res.ok) {
visited = false;
addToast('info', `Visit to ${region.name} removed`);
} else {
console.error('Failed to remove visit');
}
}
</script>
<div
class="card w-full max-w-xs sm:max-w-sm md:max-w-md lg:max-w-md xl:max-w-md bg-primary-content shadow-xl overflow-hidden text-base-content"
>
<div class="card-body">
<h2 class="card-title overflow-ellipsis">{region.name}</h2>
<p>{region.id}</p>
<div class="card-actions justify-end">
<!-- <button class="btn btn-info" on:click={moreInfo}>More Info</button> -->
{#if !visited}
<button class="btn btn-primary" on:click={markVisited}>Mark Visited</button>
{/if}
{#if visited}
<button class="btn btn-warning" on:click={removeVisit}>Remove</button>
{/if}
</div>
</div>
</div>

View file

@ -0,0 +1,18 @@
<script lang="ts">
import { toasts } from '$lib/toasts';
let toastList: any[] = [];
toasts.subscribe((value) => {
toastList = value;
console.log(toastList);
});
</script>
<div class="toast toast-top toast-end z-50 min-w-20">
{#each toastList as { type, message, id, duration }}
<div class="alert alert-{type}">
<span>{message}</span>
</div>
{/each}
</div>