1
0
Fork 0
mirror of https://github.com/seanmorley15/AdventureLog.git synced 2025-08-04 20:55:19 +02:00

feat: Implement location details page with server-side loading and deletion functionality

- Added +page.server.ts to handle server-side loading of additional location info.
- Created +page.svelte for displaying location details, including images, visits, and maps.
- Integrated GPX file handling and rendering on the map.
- Updated map route to link to locations instead of adventures.
- Refactored profile and search routes to use LocationCard instead of AdventureCard.
This commit is contained in:
Sean Morley 2025-06-23 22:29:37 -04:00
parent 8a5d7665df
commit 30c1e2deb6
33 changed files with 966 additions and 934 deletions

View file

@ -156,9 +156,17 @@
</footer>
<!-- Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<script
src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"
integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz"
crossorigin="anonymous"
></script>
<!-- jQuery (optional, used here for legacy script) -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script
src="https://code.jquery.com/jquery-3.6.0.min.js"
integrity="sha384-vtXRMe3mGCbOeY7l30aIg8H9p3GdeSe4IFlP6G8JMa7o7lXvnz3GFKzPxzJdPfGK"
crossorigin="anonymous"
></script>
<script>
const error_response = (data) => {

View file

@ -31,7 +31,7 @@
section: 'main'
},
{
path: '/adventures',
path: '/locations',
icon: MapMarker,
label: 'locations.my_locations',
section: 'main'

View file

@ -24,7 +24,7 @@
import Filter from '~icons/mdi/filter-variant';
// Component imports
import AdventureCard from './AdventureCard.svelte';
import AdventureCard from './LocationCard.svelte';
import TransportationCard from './TransportationCard.svelte';
import LodgingCard from './LodgingCard.svelte';
import NoteCard from './NoteCard.svelte';

View file

@ -228,7 +228,7 @@
<!-- Header Section -->
<div class="space-y-3">
<button
on:click={() => goto(`/adventures/${adventure.id}`)}
on:click={() => goto(`/locations/${adventure.id}`)}
class="text-xl font-bold text-left hover:text-primary transition-colors duration-200 line-clamp-2 group-hover:underline"
>
{adventure.name}
@ -274,7 +274,7 @@
<div class="flex justify-between items-center">
<button
class="btn btn-neutral btn-sm flex-1 mr-2"
on:click={() => goto(`/adventures/${adventure.id}`)}
on:click={() => goto(`/locations/${adventure.id}`)}
>
<Launch class="w-4 h-4" />
{$t('adventures.open_details')}

View file

@ -4,7 +4,7 @@
const dispatch = createEventDispatcher();
import { t } from 'svelte-i18n';
import { onMount } from 'svelte';
import AdventureCard from './AdventureCard.svelte';
import AdventureCard from './LocationCard.svelte';
let modal: HTMLDialogElement;
// Icons - following the worldtravel pattern
@ -252,7 +252,7 @@
</div>
{#if searchQuery || filterOption !== 'all'}
<h3 class="text-xl font-semibold text-base-content/70 mb-2">
{$t('adventures.no_adventures_found')}
{$t('adventures.no_locations_found')}
</h3>
<p class="text-base-content/50 text-center max-w-md mb-6">
{$t('collection.try_different_search')}

View file

@ -249,7 +249,7 @@
formData.append('name', attachmentName);
try {
const res = await fetch('/adventures?/attachment', {
const res = await fetch('/locations?/attachment', {
method: 'POST',
body: formData
});
@ -326,9 +326,9 @@
async function uploadImage(file: File) {
let formData = new FormData();
formData.append('image', file);
formData.append('adventure', adventure.id);
formData.append('location', adventure.id);
let res = await fetch(`/adventures?/image`, {
let res = await fetch(`/locations?/image`, {
method: 'POST',
body: formData
});
@ -383,8 +383,8 @@
wikiImageError = '';
let formData = new FormData();
formData.append('image', file);
formData.append('adventure', adventure.id);
let res2 = await fetch(`/adventures?/image`, {
formData.append('location', adventure.id);
let res2 = await fetch(`/locations?/image`, {
method: 'POST',
body: formData
});
@ -922,12 +922,12 @@
<p class=" font-semibold">{$t('adventures.share_location')}</p>
<div class="flex items-center justify-between">
<p class="text-card-foreground font-mono">
{window.location.origin}/adventures/{adventure.id}
{window.location.origin}/locations/{adventure.id}
</p>
<button
type="button"
on:click={() => {
navigator.clipboard.writeText(`${window.location.origin}/adventures/${adventure.id}`);
navigator.clipboard.writeText(`${window.location.origin}/locations/${adventure.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"
>

View file

@ -103,7 +103,7 @@
// Navigation items for better organization
const navigationItems = [
{ path: '/adventures', icon: MapMarker, label: 'locations.locations' },
{ path: '/locations', icon: MapMarker, label: 'locations.locations' },
{ path: '/collections', icon: FormatListBulletedSquare, label: 'navbar.collections' },
{ path: '/worldtravel', icon: Earth, label: 'navbar.worldtravel' },
{ path: '/map', icon: Map, label: 'navbar.map' },

View file

@ -12,7 +12,7 @@
<img src={Lost} alt="Lost" class="w-1/2" />
</div>
<h1 class="mt-4 text-3xl font-bold tracking-tight text-foreground sm:text-4xl">
{$t('adventures.no_adventures_found')}
{$t('adventures.no_locations_found')}
</h1>
{#if !error}
<p class="mt-4 text-muted-foreground">

View file

@ -249,7 +249,8 @@
"share_location": "Teilen Sie diesen Ort!",
"visit_calendar": "Besuchen Sie den Kalender",
"wiki_location_desc": "Zieht Auszug aus dem Wikipedia -Artikel, der dem Namen des Standorts entspricht.",
"will_be_marked_location": "wird als besucht markiert, sobald der Standort gespeichert ist."
"will_be_marked_location": "wird als besucht markiert, sobald der Standort gespeichert ist.",
"no_locations_found": "Keine Standorte gefunden"
},
"home": {
"desc_1": "Entdecken, planen und erkunden Sie mühelos",

View file

@ -226,6 +226,7 @@
"adventure_not_found": "There are no adventures to display. Add some using the plus button at the bottom right or try changing filters!",
"collection_contents": "Collection Contents",
"no_adventures_found": "No adventures found",
"no_locations_found": "No locations found",
"no_adventures_message": "Start documenting your adventures and planning new ones. Every journey has a story worth telling.",
"mark_visited": "Mark Visited",
"error_updating_regions": "Error updating regions",

File diff suppressed because it is too large Load diff

View file

@ -249,7 +249,8 @@
"share_location": "Partagez cet emplacement!",
"visit_calendar": "Visiter le calendrier",
"wiki_location_desc": "Tire un extrait de l'article de Wikipedia correspondant au nom de l'emplacement.",
"will_be_marked_location": "sera marqué comme visité une fois l'emplacement enregistré."
"will_be_marked_location": "sera marqué comme visité une fois l'emplacement enregistré.",
"no_locations_found": "Aucun emplacement trouvé"
},
"home": {
"desc_1": "Découvrez, planifiez et explorez en toute simplicité",

View file

@ -249,7 +249,8 @@
"share_location": "Condividi questa posizione!",
"visit_calendar": "Visita il calendario",
"wiki_location_desc": "Estratto dall'articolo di Wikipedia che corrisponde al nome della posizione.",
"will_be_marked_location": "sarà contrassegnato come visitato una volta salvata la posizione."
"will_be_marked_location": "sarà contrassegnato come visitato una volta salvata la posizione.",
"no_locations_found": "Nessuna posizione trovata"
},
"home": {
"desc_1": "Scopri, pianifica ed esplora con facilità",

View file

@ -249,7 +249,8 @@
"share_location": "이 위치를 공유하십시오!",
"visit_calendar": "캘린더를 방문하십시오",
"wiki_location_desc": "위치 이름과 일치하는 Wikipedia 기사에서 발췌 한 내용을 가져옵니다.",
"will_be_marked_location": "위치가 저장되면 방문한대로 표시됩니다."
"will_be_marked_location": "위치가 저장되면 방문한대로 표시됩니다.",
"no_locations_found": "발견 된 위치는 없습니다"
},
"auth": {
"confirm_password": "비밀번호 확인",

View file

@ -249,7 +249,8 @@
"share_location": "Deel deze locatie!",
"visit_calendar": "Bezoek de agenda",
"wiki_location_desc": "Haalt fragment uit het Wikipedia -artikel dat overeenkomt met de naam van de locatie.",
"will_be_marked_location": "wordt gemarkeerd als bezocht zodra de locatie is opgeslagen."
"will_be_marked_location": "wordt gemarkeerd als bezocht zodra de locatie is opgeslagen.",
"no_locations_found": "Geen locaties gevonden"
},
"home": {
"desc_1": "Ontdek, plan en verken met gemak",

View file

@ -301,7 +301,8 @@
"share_location": "Del dette stedet!",
"visit_calendar": "Besøk kalenderen",
"wiki_location_desc": "Trekker utdrag fra Wikipedia -artikkelen som samsvarer med navnet på stedet.",
"will_be_marked_location": "vil bli merket som besøkt når stedet er lagret."
"will_be_marked_location": "vil bli merket som besøkt når stedet er lagret.",
"no_locations_found": "Ingen steder funnet"
},
"worldtravel": {
"country_list": "Liste over land",

View file

@ -301,7 +301,8 @@
"share_location": "Udostępnij tę lokalizację!",
"visit_calendar": "Odwiedź kalendarz",
"wiki_location_desc": "Wyciąga fragment artykułu Wikipedii pasujący do nazwy lokalizacji.",
"will_be_marked_location": "zostanie oznaczone jako odwiedzone po zapisaniu lokalizacji."
"will_be_marked_location": "zostanie oznaczone jako odwiedzone po zapisaniu lokalizacji.",
"no_locations_found": "Nie znaleziono żadnych lokalizacji"
},
"worldtravel": {
"country_list": "Lista krajów",

View file

@ -301,7 +301,8 @@
"share_location": "Поделитесь этим расположением!",
"visit_calendar": "Посетите календарь",
"wiki_location_desc": "Вытягивает отрывок из статьи Википедии, соответствующей названию места.",
"will_be_marked_location": "будет отмечен по посещению после сохранения местоположения."
"will_be_marked_location": "будет отмечен по посещению после сохранения местоположения.",
"no_locations_found": "Никаких мест не найдено"
},
"worldtravel": {
"country_list": "Список стран",

View file

@ -249,7 +249,8 @@
"share_location": "Dela den här platsen!",
"visit_calendar": "Besök kalendern",
"wiki_location_desc": "Drar utdrag från Wikipedia -artikeln som matchar namnet på platsen.",
"will_be_marked_location": "kommer att markeras som besöks när platsen har sparats."
"will_be_marked_location": "kommer att markeras som besöks när platsen har sparats.",
"no_locations_found": "Inga platser hittades"
},
"home": {
"desc_1": "Upptäck, planera och utforska med lätthet",

View file

@ -301,7 +301,8 @@
"share_location": "分享这个位置!",
"visit_calendar": "访问日历",
"wiki_location_desc": "从Wikipedia文章中提取摘录符合该位置的名称。",
"will_be_marked_location": "保存位置后,将被标记为访问。"
"will_be_marked_location": "保存位置后,将被标记为访问。",
"no_locations_found": "找不到位置"
},
"auth": {
"forgot_password": "忘记密码?",

View file

@ -102,7 +102,7 @@
<div class="flex flex-col sm:flex-row gap-4 pt-4">
{#if data.user}
<button
on:click={() => goto('/adventures')}
on:click={() => goto('/locations')}
class="btn btn-primary btn-lg gap-3 shadow-lg hover:shadow-xl transition-all duration-300 group"
>
<PlayIcon class="w-5 h-5 group-hover:scale-110 transition-transform" />

View file

@ -1,99 +1,5 @@
import { redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
const PUBLIC_SERVER_URL = process.env['PUBLIC_SERVER_URL'];
import type { Location } from '$lib/types';
import type { Actions } from '@sveltejs/kit';
import { fetchCSRFToken } from '$lib/index.server';
const serverEndpoint = PUBLIC_SERVER_URL || 'http://localhost:8000';
export const load = (async (event) => {
if (!event.locals.user) {
return redirect(302, '/login');
} else {
let count = 0;
let adventures: Location[] = [];
let typeString = event.url.searchParams.get('types');
// If no type is specified, default to 'all'
if (!typeString) {
typeString = 'all';
}
const include_collections = event.url.searchParams.get('include_collections') || 'false';
const order_by = event.url.searchParams.get('order_by') || 'updated_at';
const order_direction = event.url.searchParams.get('order_direction') || 'asc';
const page = event.url.searchParams.get('page') || '1';
const is_visited = event.url.searchParams.get('is_visited') || 'all';
let initialFetch = await event.fetch(
`${serverEndpoint}/api/locations/filtered?types=${typeString}&order_by=${order_by}&order_direction=${order_direction}&include_collections=${include_collections}&page=${page}&is_visited=${is_visited}`,
{
headers: {
Cookie: `sessionid=${event.cookies.get('sessionid')}`
},
credentials: 'include'
}
);
if (!initialFetch.ok) {
let error_message = await initialFetch.json();
console.error(error_message);
console.error('Failed to fetch visited adventures');
return redirect(302, '/login');
} else {
let res = await initialFetch.json();
let visited = res.results as Location[];
count = res.count;
adventures = [...adventures, ...visited];
}
return {
props: {
adventures,
count
}
};
}
}) satisfies PageServerLoad;
export const actions: Actions = {
image: async (event) => {
let formData = await event.request.formData();
let csrfToken = await fetchCSRFToken();
let sessionId = event.cookies.get('sessionid');
let res = await fetch(`${serverEndpoint}/api/images/`, {
method: 'POST',
headers: {
Cookie: `csrftoken=${csrfToken}; sessionid=${sessionId}`,
'X-CSRFToken': csrfToken,
Referer: event.url.origin // Include Referer header
},
body: formData
});
let data = await res.json();
return data;
},
attachment: async (event) => {
let formData = await event.request.formData();
let csrfToken = await fetchCSRFToken();
let sessionId = event.cookies.get('sessionid');
let res = await fetch(`${serverEndpoint}/api/attachments/`, {
method: 'POST',
headers: {
Cookie: `csrftoken=${csrfToken}; sessionid=${sessionId}`,
'X-CSRFToken': csrfToken,
Referer: event.url.origin // Include Referer header
},
body: formData
});
let data = await res.json();
console.log(res);
console.log(data);
return data;
}
};
export const load = (async (_event) => {
return redirect(301, '/locations');
}) satisfies import('./$types').PageServerLoad;

View file

@ -1,76 +1,7 @@
import type { PageServerLoad } from './$types';
const PUBLIC_SERVER_URL = process.env['PUBLIC_SERVER_URL'];
import type { AdditionalLocation, Location, Collection } from '$lib/types';
const endpoint = PUBLIC_SERVER_URL || 'http://localhost:8000';
import { redirect } from '@sveltejs/kit';
export const load = (async (event) => {
const id = event.params as { id: string };
let request = await fetch(`${endpoint}/api/locations/${id.id}/additional-info/`, {
headers: {
Cookie: `sessionid=${event.cookies.get('sessionid')}`
},
credentials: 'include'
});
if (!request.ok) {
console.error('Failed to fetch adventure ' + id.id);
return {
props: {
adventure: null
}
};
} else {
let adventure = (await request.json()) as AdditionalLocation;
return {
props: {
adventure
}
};
}
return redirect(301, `/locations/${id.id}`);
}) satisfies PageServerLoad;
import { redirect, type Actions } from '@sveltejs/kit';
import { fetchCSRFToken } from '$lib/index.server';
const serverEndpoint = PUBLIC_SERVER_URL || 'http://localhost:8000';
export const actions: Actions = {
delete: async (event) => {
const id = event.params as { id: string };
const adventureId = id.id;
if (!event.locals.user) {
return redirect(302, '/login');
}
if (!adventureId) {
return {
status: 400,
error: new Error('Bad request')
};
}
let csrfToken = await fetchCSRFToken();
let res = await fetch(`${serverEndpoint}/api/locations/${event.params.id}`, {
method: 'DELETE',
headers: {
Referer: event.url.origin, // Include Referer header
Cookie: `sessionid=${event.cookies.get('sessionid')};
csrftoken=${csrfToken}`,
'X-CSRFToken': csrfToken
},
credentials: 'include'
});
console.log(res);
if (!res.ok) {
return {
status: res.status,
error: new Error('Failed to delete adventure')
};
} else {
return {
status: 204
};
}
}
};

View file

@ -26,7 +26,7 @@
return marked(markdown);
};
let adventures = data.props.adventures;
let locations = data.props.adventures;
let allDates = data.props.dates;
let filteredDates = [...allDates];
@ -215,7 +215,7 @@
</div>
<div class="stat py-2 px-4">
<div class="stat-title text-xs">{$t('locations.locations')}</div>
<div class="stat-value text-lg text-secondary">{adventures.length}</div>
<div class="stat-value text-lg text-secondary">{locations.length}</div>
</div>
</div>
</div>
@ -229,7 +229,7 @@
/>
<input
type="text"
placeholder="Search adventures or locations..."
placeholder={$t('adventures.search_for_location')}
class="input input-bordered w-full pl-10 pr-10 bg-base-100/80"
bind:value={searchFilter}
/>
@ -299,7 +299,7 @@
<div class="grid grid-cols-2 gap-4">
<div class="stat p-0">
<div class="stat-title text-xs">{$t('locations.locations')}</div>
<div class="stat-value text-lg text-primary">{adventures.length}</div>
<div class="stat-value text-lg text-primary">{locations.length}</div>
</div>
</div>
@ -418,7 +418,7 @@
{#if selectedEvent.extendedProps.adventureId}
<a
href={`/adventures/${selectedEvent.extendedProps.adventureId}`}
href={`/locations/${selectedEvent.extendedProps.adventureId}`}
class="btn btn-neutral btn-block mt-4"
>
{$t('map.view_details')}

View file

@ -15,8 +15,8 @@
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 AdventureCard from '$lib/components/LocationCard.svelte';
import AdventureLink from '$lib/components/LocationLink.svelte';
import { MapLibre, Marker, Popup, LineLayer, GeoJSON } from 'svelte-maplibre';
import TransportationCard from '$lib/components/TransportationCard.svelte';
import NoteCard from '$lib/components/NoteCard.svelte';
@ -39,7 +39,7 @@
import ChecklistCard from '$lib/components/ChecklistCard.svelte';
import ChecklistModal from '$lib/components/ChecklistModal.svelte';
import AdventureModal from '$lib/components/AdventureModal.svelte';
import AdventureModal from '$lib/components/LocationModal.svelte';
import TransportationModal from '$lib/components/TransportationModal.svelte';
import CardCarousel from '$lib/components/CardCarousel.svelte';
import { goto } from '$app/navigation';
@ -1323,7 +1323,7 @@
{/if}
<button
class="btn btn-neutral btn-wide btn-sm mt-4"
on:click={() => goto(`/adventures/${adventure.id}`)}
on:click={() => goto(`/locations/${adventure.id}`)}
>{$t('map.view_details')}</button
>
</Popup>

View file

@ -1,5 +1,5 @@
<script lang="ts">
import AdventureCard from '$lib/components/AdventureCard.svelte';
import AdventureCard from '$lib/components/LocationCard.svelte';
import type { PageData } from './$types';
import { t } from 'svelte-i18n';
import { onMount } from 'svelte';
@ -66,7 +66,7 @@
<!-- Quick Action -->
<div class="flex flex-col sm:flex-row gap-3">
<a
href="/adventures"
href="/locations"
class="btn btn-primary btn-lg gap-2 shadow-lg hover:shadow-xl transition-all duration-300"
>
<Plus class="w-5 h-5" />
@ -169,7 +169,7 @@
<p class="text-base-content/60">{$t('home.latest_travel_experiences')}</p>
</div>
</div>
<a href="/adventures" class="btn btn-ghost gap-2">
<a href="/locations" class="btn btn-ghost gap-2">
{$t('dashboard.view_all')}
<span class="badge badge-primary">{stats.adventure_count}</span>
</a>
@ -208,7 +208,7 @@
<div class="flex flex-col sm:flex-row gap-4 justify-center">
<a
href="/adventures"
href="/locations"
class="btn btn-primary btn-lg gap-2 shadow-lg hover:shadow-xl transition-all duration-300"
>
<Plus class="w-5 h-5" />

View file

@ -0,0 +1,99 @@
import { redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
const PUBLIC_SERVER_URL = process.env['PUBLIC_SERVER_URL'];
import type { Location } from '$lib/types';
import type { Actions } from '@sveltejs/kit';
import { fetchCSRFToken } from '$lib/index.server';
const serverEndpoint = PUBLIC_SERVER_URL || 'http://localhost:8000';
export const load = (async (event) => {
if (!event.locals.user) {
return redirect(302, '/login');
} else {
let count = 0;
let adventures: Location[] = [];
let typeString = event.url.searchParams.get('types');
// If no type is specified, default to 'all'
if (!typeString) {
typeString = 'all';
}
const include_collections = event.url.searchParams.get('include_collections') || 'false';
const order_by = event.url.searchParams.get('order_by') || 'updated_at';
const order_direction = event.url.searchParams.get('order_direction') || 'asc';
const page = event.url.searchParams.get('page') || '1';
const is_visited = event.url.searchParams.get('is_visited') || 'all';
let initialFetch = await event.fetch(
`${serverEndpoint}/api/locations/filtered?types=${typeString}&order_by=${order_by}&order_direction=${order_direction}&include_collections=${include_collections}&page=${page}&is_visited=${is_visited}`,
{
headers: {
Cookie: `sessionid=${event.cookies.get('sessionid')}`
},
credentials: 'include'
}
);
if (!initialFetch.ok) {
let error_message = await initialFetch.json();
console.error(error_message);
console.error('Failed to fetch visited adventures');
return redirect(302, '/login');
} else {
let res = await initialFetch.json();
let visited = res.results as Location[];
count = res.count;
adventures = [...adventures, ...visited];
}
return {
props: {
adventures,
count
}
};
}
}) satisfies PageServerLoad;
export const actions: Actions = {
image: async (event) => {
let formData = await event.request.formData();
let csrfToken = await fetchCSRFToken();
let sessionId = event.cookies.get('sessionid');
let res = await fetch(`${serverEndpoint}/api/images/`, {
method: 'POST',
headers: {
Cookie: `csrftoken=${csrfToken}; sessionid=${sessionId}`,
'X-CSRFToken': csrfToken,
Referer: event.url.origin // Include Referer header
},
body: formData
});
let data = await res.json();
return data;
},
attachment: async (event) => {
let formData = await event.request.formData();
let csrfToken = await fetchCSRFToken();
let sessionId = event.cookies.get('sessionid');
let res = await fetch(`${serverEndpoint}/api/attachments/`, {
method: 'POST',
headers: {
Cookie: `csrftoken=${csrfToken}; sessionid=${sessionId}`,
'X-CSRFToken': csrfToken,
Referer: event.url.origin // Include Referer header
},
body: formData
});
let data = await res.json();
console.log(res);
console.log(data);
return data;
}
};

View file

@ -2,8 +2,8 @@
import { enhance, deserialize } from '$app/forms';
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import AdventureCard from '$lib/components/AdventureCard.svelte';
import AdventureModal from '$lib/components/AdventureModal.svelte';
import AdventureCard from '$lib/components/LocationCard.svelte';
import AdventureModal from '$lib/components/LocationModal.svelte';
import CategoryFilterDropdown from '$lib/components/CategoryFilterDropdown.svelte';
import CategoryModal from '$lib/components/CategoryModal.svelte';
import NotFound from '$lib/components/NotFound.svelte';
@ -241,7 +241,7 @@
<Compass class="w-16 h-16 text-base-content/30" />
</div>
<h3 class="text-xl font-semibold text-base-content/70 mb-2">
{$t('adventures.no_adventures_found')}
{$t('adventures.no_locations_found')}
</h3>
<p class="text-base-content/50 text-center max-w-md">
{$t('adventures.no_adventures_message')}

View file

@ -0,0 +1,76 @@
import type { PageServerLoad } from './$types';
const PUBLIC_SERVER_URL = process.env['PUBLIC_SERVER_URL'];
import type { AdditionalLocation, Location, Collection } from '$lib/types';
const endpoint = PUBLIC_SERVER_URL || 'http://localhost:8000';
export const load = (async (event) => {
const id = event.params as { id: string };
let request = await fetch(`${endpoint}/api/locations/${id.id}/additional-info/`, {
headers: {
Cookie: `sessionid=${event.cookies.get('sessionid')}`
},
credentials: 'include'
});
if (!request.ok) {
console.error('Failed to fetch adventure ' + id.id);
return {
props: {
adventure: null
}
};
} else {
let adventure = (await request.json()) as AdditionalLocation;
return {
props: {
adventure
}
};
}
}) satisfies PageServerLoad;
import { redirect, type Actions } from '@sveltejs/kit';
import { fetchCSRFToken } from '$lib/index.server';
const serverEndpoint = PUBLIC_SERVER_URL || 'http://localhost:8000';
export const actions: Actions = {
delete: async (event) => {
const id = event.params as { id: string };
const adventureId = id.id;
if (!event.locals.user) {
return redirect(302, '/login');
}
if (!adventureId) {
return {
status: 400,
error: new Error('Bad request')
};
}
let csrfToken = await fetchCSRFToken();
let res = await fetch(`${serverEndpoint}/api/locations/${event.params.id}`, {
method: 'DELETE',
headers: {
Referer: event.url.origin, // Include Referer header
Cookie: `sessionid=${event.cookies.get('sessionid')};
csrftoken=${csrfToken}`,
'X-CSRFToken': csrfToken
},
credentials: 'include'
});
console.log(res);
if (!res.ok) {
return {
status: res.status,
error: new Error('Failed to delete adventure')
};
} else {
return {
status: 204
};
}
}
};

View file

@ -16,7 +16,7 @@
import LightbulbOn from '~icons/mdi/lightbulb-on';
import WeatherSunset from '~icons/mdi/weather-sunset';
import ClipboardList from '~icons/mdi/clipboard-list';
import AdventureModal from '$lib/components/AdventureModal.svelte';
import AdventureModal from '$lib/components/LocationModal.svelte';
import ImageDisplayModal from '$lib/components/ImageDisplayModal.svelte';
import AttachmentCard from '$lib/components/AttachmentCard.svelte';
import { getBasemapUrl, isAllDay } from '$lib';

View file

@ -1,5 +1,5 @@
<script lang="ts">
import AdventureModal from '$lib/components/AdventureModal.svelte';
import AdventureModal from '$lib/components/LocationModal.svelte';
import { DefaultMarker, MapEvents, MapLibre, Popup, Marker } from 'svelte-maplibre';
import { t } from 'svelte-i18n';
import type { Location, VisitedRegion } from '$lib/types.js';
@ -268,7 +268,7 @@
{/if}
<button
class="btn btn-primary btn-sm gap-2"
on:click={() => goto(`/adventures/${adventure.id}`)}
on:click={() => goto(`/locations/${adventure.id}`)}
>
<Eye class="w-4 h-4" />
{$t('map.view_details')}

View file

@ -1,6 +1,6 @@
<script lang="ts">
export let data;
import AdventureCard from '$lib/components/AdventureCard.svelte';
import AdventureCard from '$lib/components/LocationCard.svelte';
import CollectionCard from '$lib/components/CollectionCard.svelte';
import type { Location, Collection, User } from '$lib/types.js';
import { t } from 'svelte-i18n';

View file

@ -1,5 +1,5 @@
<script lang="ts">
import AdventureCard from '$lib/components/AdventureCard.svelte';
import AdventureCard from '$lib/components/LocationCard.svelte';
import RegionCard from '$lib/components/RegionCard.svelte';
import CityCard from '$lib/components/CityCard.svelte';
import CountryCard from '$lib/components/CountryCard.svelte';