adds new default state images
This commit is contained in:
@@ -46,7 +46,7 @@ services:
|
|||||||
build: ./web
|
build: ./web
|
||||||
environment:
|
environment:
|
||||||
<<: *cenv
|
<<: *cenv
|
||||||
MEILI_API_TOKEN:
|
MEILI_API_TOKEN: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhcGlLZXlVaWQiOiIwZDA0YTAzYy1iNzBjLTQzZTctOGUzMC00NjNiODFhM2UwY2YiLCJzZWFyY2hSdWxlcyI6eyJ0cmFpbHMiOnsiZmlsdGVyIjoicHVibGljPXRydWUifSwiY2l0aWVzNTAwIjp7fX0sImV4cCI6bnVsbH0.XhHwyRqtKjij2-oPvcYAL3uIFmB4lxUBUgVzhQqL1To
|
||||||
ORIGIN: http://localhost:8080
|
ORIGIN: http://localhost:8080
|
||||||
PUBLIC_POCKETBASE_URL: http://db:8090
|
PUBLIC_POCKETBASE_URL: http://db:8090
|
||||||
ports:
|
ports:
|
||||||
|
|||||||
@@ -43,8 +43,8 @@ def init_indices():
|
|||||||
|
|
||||||
|
|
||||||
def generate_public_token():
|
def generate_public_token():
|
||||||
search_key = client.get_keys().results[0]
|
search_key = next(filter(lambda r: r.name == "Default Search API Key", client.get_keys().results))
|
||||||
|
|
||||||
search_rules = {
|
search_rules = {
|
||||||
'trails': {
|
'trails': {
|
||||||
'filter': 'public=true'
|
'filter': 'public=true'
|
||||||
@@ -60,6 +60,6 @@ init_indices()
|
|||||||
print("Indices initialized!")
|
print("Indices initialized!")
|
||||||
token = generate_public_token()
|
token = generate_public_token()
|
||||||
print("Generating public token...")
|
print("Generating public token...")
|
||||||
print(f"PUBLIC_MEILISEARCH_API_TOKEN:{token}")
|
print(f"MEILI_API_TOKEN: {token}")
|
||||||
|
|
||||||
print("Bootstrapping complete!")
|
print("Bootstrapping complete!")
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export const handle: Handle = async ({ event, resolve }) => {
|
|||||||
|
|
||||||
// validate the user existence and if the path is acceesible
|
// validate the user existence and if the path is acceesible
|
||||||
if (!pb.authStore.model && isRouteProtected(url.pathname)) {
|
if (!pb.authStore.model && isRouteProtected(url.pathname)) {
|
||||||
throw redirect(302, '/login');
|
throw redirect(302, '/login?r='+url.pathname);
|
||||||
} else if (pb.authStore.model && url.pathname === "/login") {
|
} else if (pb.authStore.model && url.pathname === "/login") {
|
||||||
throw redirect(302, '/');
|
throw redirect(302, '/');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<img
|
<img
|
||||||
style="width: {width}px"
|
style="width: {width}px"
|
||||||
class="rounded-full aspect-square"
|
class="rounded-full aspect-square"
|
||||||
src="/imgs/empty_state.webp"
|
src="/imgs/empty_state.png"
|
||||||
alt="Empty State showing a wanderer going into the distance"
|
alt="Empty State showing a wanderer going into the distance"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -78,7 +78,7 @@
|
|||||||
function handleDropdownClick(item: { text: string; value: any }) {
|
function handleDropdownClick(item: { text: string; value: any }) {
|
||||||
if (item.value == "logout") {
|
if (item.value == "logout") {
|
||||||
logout();
|
logout();
|
||||||
goto("/");
|
window.location.href = "/";
|
||||||
} else if (item.value == "profile") {
|
} else if (item.value == "profile") {
|
||||||
goto("/profile");
|
goto("/profile");
|
||||||
}
|
}
|
||||||
@@ -117,7 +117,7 @@
|
|||||||
<hr class="my-6 border-input-border" />
|
<hr class="my-6 border-input-border" />
|
||||||
<div class="flex flex-col basis-full">
|
<div class="flex flex-col basis-full">
|
||||||
<a class="btn-primary btn-large text-center mx-4" href="/trail/edit/new"
|
<a class="btn-primary btn-large text-center mx-4" href="/trail/edit/new"
|
||||||
><i class="fa fa-plus mr-2"></i>{$_('new-trail')}</a
|
><i class="fa fa-plus mr-2"></i>{$_("new-trail")}</a
|
||||||
>
|
>
|
||||||
{#if $currentUser}
|
{#if $currentUser}
|
||||||
<div class="basis-full"></div>
|
<div class="basis-full"></div>
|
||||||
@@ -180,7 +180,7 @@
|
|||||||
on:click={() => toggleTheme()}
|
on:click={() => toggleTheme()}
|
||||||
></button>
|
></button>
|
||||||
<a class="btn-primary btn-large" href="/trail/edit/new"
|
<a class="btn-primary btn-large" href="/trail/edit/new"
|
||||||
><i class="fa fa-plus mr-2"></i>{$_('new-trail')}</a
|
><i class="fa fa-plus mr-2"></i>{$_("new-trail")}</a
|
||||||
>
|
>
|
||||||
<Dropdown
|
<Dropdown
|
||||||
items={dropdownItems}
|
items={dropdownItems}
|
||||||
@@ -208,7 +208,7 @@
|
|||||||
: 'moon'}"
|
: 'moon'}"
|
||||||
on:click={() => toggleTheme()}
|
on:click={() => toggleTheme()}
|
||||||
></button>
|
></button>
|
||||||
<a class="btn-primary btn-large" href="/login">{$_('login')}</a>
|
<a class="btn-primary btn-large" href="/login">{$_("login")}</a>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -8,6 +8,10 @@
|
|||||||
} from "$lib/util/format_util";
|
} from "$lib/util/format_util";
|
||||||
|
|
||||||
export let trail: Trail;
|
export let trail: Trail;
|
||||||
|
|
||||||
|
$: thumbnail = trail.photos.length
|
||||||
|
? getFileURL(trail, trail.photos[trail.thumbnail])
|
||||||
|
: "/imgs/default_thumbnail.webp";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@@ -17,7 +21,7 @@
|
|||||||
role="listitem"
|
role="listitem"
|
||||||
>
|
>
|
||||||
<div class="w-full min-h-40 max-h-48 overflow-hidden rounded-t-2xl">
|
<div class="w-full min-h-40 max-h-48 overflow-hidden rounded-t-2xl">
|
||||||
<img src={getFileURL(trail, trail.photos[trail.thumbnail])} alt="" />
|
<img src={thumbnail} alt="" />
|
||||||
</div>
|
</div>
|
||||||
<div class="p-4">
|
<div class="p-4">
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -97,7 +97,7 @@
|
|||||||
<div id="trails" class="flex items-start flex-wrap gap-8 py-8 max-w-full">
|
<div id="trails" class="flex items-start flex-wrap gap-8 py-8 max-w-full">
|
||||||
{#if trails.length == 0}
|
{#if trails.length == 0}
|
||||||
<div class="flex flex-col basis-full items-center">
|
<div class="flex flex-col basis-full items-center">
|
||||||
<EmptyStateSearch></EmptyStateSearch>
|
<EmptyStateSearch width={356}></EmptyStateSearch>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#each trails as trail}
|
{#each trails as trail}
|
||||||
|
|||||||
@@ -1,20 +1,24 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Trail } from "$lib/models/trail";
|
import type { Trail } from "$lib/models/trail";
|
||||||
import { getFileURL } from "$lib/util/file_util";
|
import { getFileURL } from "$lib/util/file_util";
|
||||||
import { formatDistance, formatElevation, formatTimeHHMM } from "$lib/util/format_util";
|
import {
|
||||||
|
formatDistance,
|
||||||
|
formatElevation,
|
||||||
|
formatTimeHHMM,
|
||||||
|
} from "$lib/util/format_util";
|
||||||
|
|
||||||
export let trail: Trail;
|
export let trail: Trail;
|
||||||
|
|
||||||
|
$: thumbnail = trail.photos.length
|
||||||
|
? getFileURL(trail, trail.photos[trail.thumbnail])
|
||||||
|
: "/imgs/default_thumbnail.webp";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<li
|
<li
|
||||||
class="flex gap-8 p-4 rounded-xl border border-input-border cursor-pointer hover:bg-secondary-hover transition-colors"
|
class="flex gap-8 p-4 rounded-xl border border-input-border cursor-pointer hover:bg-secondary-hover transition-colors"
|
||||||
>
|
>
|
||||||
<div class="shrink-0">
|
<div class="shrink-0">
|
||||||
<img
|
<img class="h-28 w-28 object-cover rounded-xl" src={thumbnail} alt="" />
|
||||||
class="h-28 w-28 object-cover rounded-xl"
|
|
||||||
src={getFileURL(trail, trail.photos[trail.thumbnail])}
|
|
||||||
alt=""
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="min-w-0 basis-full">
|
<div class="min-w-0 basis-full">
|
||||||
<h4 class="font-semibold text-lg">{trail.name}</h4>
|
<h4 class="font-semibold text-lg">{trail.name}</h4>
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
"delete-trail-confirm": "Möchtest du diese Route wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden.",
|
"delete-trail-confirm": "Möchtest du diese Route wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden.",
|
||||||
"describe-your-trail": "Beschreibe deine Route",
|
"describe-your-trail": "Beschreibe deine Route",
|
||||||
"description": "Beschreibung",
|
"description": "Beschreibung",
|
||||||
"difficult": "Schwierig",
|
"difficult": "Schwer",
|
||||||
"difficulty": "Schwierigkeit",
|
"difficulty": "Schwierigkeit",
|
||||||
"directions": "Wegbeschreibung",
|
"directions": "Wegbeschreibung",
|
||||||
"display-as": "Anzeigen als",
|
"display-as": "Anzeigen als",
|
||||||
@@ -42,16 +42,21 @@
|
|||||||
"elevation-gain": "Höhenunterschied",
|
"elevation-gain": "Höhenunterschied",
|
||||||
"email": "Email",
|
"email": "Email",
|
||||||
"english": "Englisch",
|
"english": "Englisch",
|
||||||
|
"error-creating-user": "Fehler beim Erstellen des Nutzers",
|
||||||
|
"error-during-login": "Fehler beim Login",
|
||||||
"est-duration": "Gesch. Dauer",
|
"est-duration": "Gesch. Dauer",
|
||||||
"explore": "Erkunden",
|
"explore": "Erkunden",
|
||||||
"explore-some-trails": "Erkunden Sie einige Routen",
|
"explore-some-trails": "Erkunden Sie einige Routen",
|
||||||
"features": "Feature",
|
"features": "Feature",
|
||||||
"german": "Deutsch",
|
"german": "Deutsch",
|
||||||
"hero_section_0_text": "Entdecke spannende Routen, speichere deine Favoriten und erlebe die Schönheit der Natur. Finde dein nächstes Abenteuer!",
|
"hero_section_0_text": "Entdecke spannende Routen, speichere deine Favoriten und erlebe die Schönheit der Natur. Finde dein nächstes Abenteuer!",
|
||||||
|
"hero_section_1_heading": "Hier gibt es noch keine Routen",
|
||||||
"hero_section_1_text": "Hier sind einige Routen, die dir gefallen könnten. Oder du wirfst einen Blick auf die vollständige Liste.",
|
"hero_section_1_text": "Hier sind einige Routen, die dir gefallen könnten. Oder du wirfst einen Blick auf die vollständige Liste.",
|
||||||
|
"hero_section_1_text_alternative": "Speichere dein letztes Abenteuer, um loszulegen.",
|
||||||
"hero_section_2_text": "Wusstest du schon? Du kannst nicht nur deine Routen speichern. Es gibt viele Kategorien für alle deine Abenteuer.",
|
"hero_section_2_text": "Wusstest du schon? Du kannst nicht nur deine Routen speichern. Es gibt viele Kategorien für alle deine Abenteuer.",
|
||||||
"icon": "Icon",
|
"icon": "Icon",
|
||||||
"imperial": "Amerikanisch",
|
"imperial": "Amerikanisch",
|
||||||
|
"invalid-username": "Ungültiger Nutzername",
|
||||||
"language": "Sprache",
|
"language": "Sprache",
|
||||||
"latitude": "Breitengrad",
|
"latitude": "Breitengrad",
|
||||||
"license": "Lizenz",
|
"license": "Lizenz",
|
||||||
@@ -65,6 +70,7 @@
|
|||||||
"map": "Karte",
|
"map": "Karte",
|
||||||
"metric": "Metrisch",
|
"metric": "Metrisch",
|
||||||
"moderate": "Mittel",
|
"moderate": "Mittel",
|
||||||
|
"must-be-at-least-8-characters-long": "Muss mindestens 8 Zeichen lang sein",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
"near": "Nahe",
|
"near": "Nahe",
|
||||||
"new-list": "Neue Liste",
|
"new-list": "Neue Liste",
|
||||||
@@ -72,6 +78,7 @@
|
|||||||
"no-account": "Du hast noch kein Konto?",
|
"no-account": "Du hast noch kein Konto?",
|
||||||
"no-preference": "Keine Präferenz",
|
"no-preference": "Keine Präferenz",
|
||||||
"no-results": "Keine Ergebnisse gefunden",
|
"no-results": "Keine Ergebnisse gefunden",
|
||||||
|
"not-a-valid-email-address": "Keine gültige Email-Adresse",
|
||||||
"not-completed": "Nicht abgeschlossen",
|
"not-completed": "Nicht abgeschlossen",
|
||||||
"password": "Passwort",
|
"password": "Passwort",
|
||||||
"photos": "Fotos",
|
"photos": "Fotos",
|
||||||
@@ -81,6 +88,7 @@
|
|||||||
"radius": "Radius",
|
"radius": "Radius",
|
||||||
"register": "Registrieren",
|
"register": "Registrieren",
|
||||||
"removed-trail-from": "Route entfernt aus",
|
"removed-trail-from": "Route entfernt aus",
|
||||||
|
"required": "Pflichtfeld",
|
||||||
"save": "Speichern",
|
"save": "Speichern",
|
||||||
"save-trail": "Route speichern",
|
"save-trail": "Route speichern",
|
||||||
"search-cities": "Städte suchen",
|
"search-cities": "Städte suchen",
|
||||||
@@ -100,5 +108,6 @@
|
|||||||
"upload-gpx": "GPX hochladen",
|
"upload-gpx": "GPX hochladen",
|
||||||
"username": "Nutzername",
|
"username": "Nutzername",
|
||||||
"waypoints": "Wegpunkte",
|
"waypoints": "Wegpunkte",
|
||||||
"welcome_to": "Willkommen bei"
|
"welcome_to": "Willkommen bei",
|
||||||
|
"wrong-username-or-password": "Falscher Nutzername oder falsches Passwort"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,16 +42,21 @@
|
|||||||
"elevation-gain": "Elevation Gain",
|
"elevation-gain": "Elevation Gain",
|
||||||
"email": "Email",
|
"email": "Email",
|
||||||
"english": "English",
|
"english": "English",
|
||||||
|
"error-creating-user": "Error creating user",
|
||||||
|
"error-during-login": "Error during login",
|
||||||
"est-duration": "Est. duration",
|
"est-duration": "Est. duration",
|
||||||
"explore": "Explore",
|
"explore": "Explore",
|
||||||
"explore-some-trails": "Explore some trails",
|
"explore-some-trails": "Explore some trails",
|
||||||
"features": "Features",
|
"features": "Features",
|
||||||
"german": "German",
|
"german": "German",
|
||||||
"hero_section_0_text": "Explore exciting trails, save your favorites, and experience the beauty of nature. Find your next adventure!",
|
"hero_section_0_text": "Explore exciting trails, save your favorites, and experience the beauty of nature. Find your next adventure!",
|
||||||
|
"hero_section_1_heading": "It seems there are no trails here yet.",
|
||||||
"hero_section_1_text": "Here are some trails you might like. Or you can just go to the full list right now.",
|
"hero_section_1_text": "Here are some trails you might like. Or you can just go to the full list right now.",
|
||||||
|
"hero_section_1_text_alternative": "Get started by saving your latest adventure.",
|
||||||
"hero_section_2_text": "Did you know? You cannot only save you hiking trails. There are many categories for all your adventures.",
|
"hero_section_2_text": "Did you know? You cannot only save you hiking trails. There are many categories for all your adventures.",
|
||||||
"icon": "Icon",
|
"icon": "Icon",
|
||||||
"imperial": "Imperial",
|
"imperial": "Imperial",
|
||||||
|
"invalid-username": "Invalid username",
|
||||||
"language": "Language",
|
"language": "Language",
|
||||||
"latitude": "Latitude",
|
"latitude": "Latitude",
|
||||||
"license": "License",
|
"license": "License",
|
||||||
@@ -65,6 +70,7 @@
|
|||||||
"map": "Map",
|
"map": "Map",
|
||||||
"metric": "Metric",
|
"metric": "Metric",
|
||||||
"moderate": "Moderate",
|
"moderate": "Moderate",
|
||||||
|
"must-be-at-least-8-characters-long": "Must be at least 8 characters long",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
"near": "Near",
|
"near": "Near",
|
||||||
"new-list": "New List",
|
"new-list": "New List",
|
||||||
@@ -72,6 +78,7 @@
|
|||||||
"no-account": "Don't have an account?",
|
"no-account": "Don't have an account?",
|
||||||
"no-preference": "No preference",
|
"no-preference": "No preference",
|
||||||
"no-results": "No results found",
|
"no-results": "No results found",
|
||||||
|
"not-a-valid-email-address": "Not a valid email address",
|
||||||
"not-completed": "Not completed",
|
"not-completed": "Not completed",
|
||||||
"password": "Password",
|
"password": "Password",
|
||||||
"photos": "Photos",
|
"photos": "Photos",
|
||||||
@@ -81,6 +88,7 @@
|
|||||||
"radius": "Radius",
|
"radius": "Radius",
|
||||||
"register": "Register",
|
"register": "Register",
|
||||||
"removed-trail-from": "Removed trail from",
|
"removed-trail-from": "Removed trail from",
|
||||||
|
"required": "Required",
|
||||||
"save": "Save",
|
"save": "Save",
|
||||||
"save-trail": "Save Trail",
|
"save-trail": "Save Trail",
|
||||||
"search-cities": "Search cities",
|
"search-cities": "Search cities",
|
||||||
@@ -100,5 +108,6 @@
|
|||||||
"upload-gpx": "Upload GPX",
|
"upload-gpx": "Upload GPX",
|
||||||
"username": "Username",
|
"username": "Username",
|
||||||
"waypoints": "Waypoints",
|
"waypoints": "Waypoints",
|
||||||
"welcome_to": "Welcome to"
|
"welcome_to": "Welcome to",
|
||||||
|
"wrong-username-or-password": "Wrong username or password"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { array, number, object, string } from "yup";
|
|
||||||
import type { Category } from "./category";
|
import type { Category } from "./category";
|
||||||
import type { SummitLog } from "./summit_log";
|
import type { SummitLog } from "./summit_log";
|
||||||
import type { Waypoint } from "./waypoint";
|
import type { Waypoint } from "./waypoint";
|
||||||
@@ -11,7 +10,7 @@ class Trail {
|
|||||||
distance?: number;
|
distance?: number;
|
||||||
elevation_gain?: number;
|
elevation_gain?: number;
|
||||||
duration?: number;
|
duration?: number;
|
||||||
difficulty?: "easy"|"moderate"|"difficult"
|
difficulty?: "easy" | "moderate" | "difficult"
|
||||||
lat?: number;
|
lat?: number;
|
||||||
lon?: number;
|
lon?: number;
|
||||||
thumbnail: number;
|
thumbnail: number;
|
||||||
@@ -39,8 +38,8 @@ class Trail {
|
|||||||
distance?: number,
|
distance?: number,
|
||||||
elevation_gain?: number,
|
elevation_gain?: number,
|
||||||
duration?: number,
|
duration?: number,
|
||||||
difficulty?: "easy"|"moderate"|"difficult",
|
difficulty?: "easy" | "moderate" | "difficult",
|
||||||
lat?:number,
|
lat?: number,
|
||||||
lon?: number,
|
lon?: number,
|
||||||
thumbnail?: number,
|
thumbnail?: number,
|
||||||
photos?: string[],
|
photos?: string[],
|
||||||
@@ -66,7 +65,7 @@ class Trail {
|
|||||||
this.lon = params?.lon;
|
this.lon = params?.lon;
|
||||||
this.thumbnail = params?.thumbnail ?? 0;
|
this.thumbnail = params?.thumbnail ?? 0;
|
||||||
this.photos = params?.photos ?? [];
|
this.photos = params?.photos ?? [];
|
||||||
this.waypoints = [];
|
this.waypoints = [];
|
||||||
this.summit_logs = [];
|
this.summit_logs = [];
|
||||||
this.gpx = params?.gpx;
|
this.gpx = params?.gpx;
|
||||||
this.expand = {
|
this.expand = {
|
||||||
@@ -80,19 +79,6 @@ class Trail {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const trailSchema = object<Trail>({
|
|
||||||
id: string().optional(),
|
|
||||||
name: string().required("Required"),
|
|
||||||
location: string().optional(),
|
|
||||||
distance: number().optional(),
|
|
||||||
elevation_gain: number().optional(),
|
|
||||||
duration: number().optional(),
|
|
||||||
thumbnail: string().optional(),
|
|
||||||
photos: array(string()).optional(),
|
|
||||||
gpx: string().optional(),
|
|
||||||
description: string().optional()
|
|
||||||
});
|
|
||||||
|
|
||||||
interface TrailFilter {
|
interface TrailFilter {
|
||||||
q: string,
|
q: string,
|
||||||
category: string[],
|
category: string[],
|
||||||
@@ -108,10 +94,9 @@ interface TrailFilter {
|
|||||||
elevationGainMax: number;
|
elevationGainMax: number;
|
||||||
completed?: boolean;
|
completed?: boolean;
|
||||||
sort: "name" | "distance" | "elevation_gain" | "created";
|
sort: "name" | "distance" | "elevation_gain" | "created";
|
||||||
sortOrder: "+" | "-"
|
sortOrder: "+" | "-"
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Trail, trailSchema };
|
export { Trail };
|
||||||
|
|
||||||
export type { TrailFilter };
|
|
||||||
|
|
||||||
|
export type { TrailFilter };
|
||||||
@@ -60,7 +60,7 @@ export async function trails_search_filter(filter: TrailFilter, page: number = 1
|
|||||||
if (!r.ok) {
|
if (!r.ok) {
|
||||||
throw new ClientResponseError(result)
|
throw new ClientResponseError(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
const trailIds = result.hits.map((h: Record<string, any>) => h.id);
|
const trailIds = result.hits.map((h: Record<string, any>) => h.id);
|
||||||
|
|
||||||
if (trailIds.length == 0) {
|
if (trailIds.length == 0) {
|
||||||
@@ -92,6 +92,8 @@ export async function trails_search_bounding_box(northEast: LatLng, southWest: L
|
|||||||
if (filter) {
|
if (filter) {
|
||||||
filterText += `distance >= ${filter.distanceMin} AND distance <= ${filter.distanceMax} AND elevation_gain >= ${filter.elevationGainMin} AND elevation_gain <= ${filter.elevationGainMax}`;
|
filterText += `distance >= ${filter.distanceMin} AND distance <= ${filter.distanceMax} AND elevation_gain >= ${filter.elevationGainMin} AND elevation_gain <= ${filter.elevationGainMax}`;
|
||||||
|
|
||||||
|
filterText += ` AND difficulty IN [${filter.difficulty.join(",")}]`
|
||||||
|
|
||||||
if (filter.category.length > 0) {
|
if (filter.category.length > 0) {
|
||||||
filterText += ` AND category IN [${filter.category.join(",")}]`;
|
filterText += ` AND category IN [${filter.category.join(",")}]`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
const privateRoutes = [
|
const privateRoutes = [
|
||||||
"/profile",
|
"/profile",
|
||||||
"/lists"
|
"/lists",
|
||||||
|
"/trail",
|
||||||
]
|
]
|
||||||
|
|
||||||
export function isRouteProtected(path: string) {
|
export function isRouteProtected(path: string) {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
beforeNavigate((n) => {
|
beforeNavigate((n) => {
|
||||||
if (!$currentUser && isRouteProtected(n.to?.url?.pathname ?? "")) {
|
if (!$currentUser && isRouteProtected(n.to?.url?.pathname ?? "")) {
|
||||||
n.cancel();
|
n.cancel();
|
||||||
goto("/login");
|
goto("/login?r="+n.to?.url?.pathname);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -108,6 +108,16 @@
|
|||||||
id="trails"
|
id="trails"
|
||||||
class="flex flex-wrap justify-items-center gap-8 py-8 order-1 md:order-none"
|
class="flex flex-wrap justify-items-center gap-8 py-8 order-1 md:order-none"
|
||||||
>
|
>
|
||||||
|
{#if $trails.length == 0}
|
||||||
|
<div>
|
||||||
|
<img
|
||||||
|
style="max-width: 450px"
|
||||||
|
class="rounded-full aspect-square"
|
||||||
|
src="/imgs/default_thumbnail.webp"
|
||||||
|
alt="Empty State showing a wanderer going into the distance"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
{#each { length: Math.min($trails.length, 4) } as _, i}
|
{#each { length: Math.min($trails.length, 4) } as _, i}
|
||||||
<a href="/trail/view/{$trails[i].id}">
|
<a href="/trail/view/{$trails[i].id}">
|
||||||
<TrailCard trail={$trails[i]}></TrailCard></a
|
<TrailCard trail={$trails[i]}></TrailCard></a
|
||||||
@@ -115,18 +125,33 @@
|
|||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
<div class="max-w-md md:mx-auto space-y-8">
|
<div class="max-w-md md:mx-auto space-y-8">
|
||||||
<h2 class="text-4xl md:text-5xl font-bold">
|
{#if $trails.length == 0}
|
||||||
{$currentUser ? $_("trails-for-you") : $_("explore-some-trails")}
|
<h2 class="text-4xl md:text-5xl font-bold">
|
||||||
</h2>
|
{$_("hero_section_1_heading")}
|
||||||
<h5>
|
</h2>
|
||||||
{$_("hero_section_1_text")}
|
<h5>{$_("hero_section_1_text_alternative")}</h5>
|
||||||
</h5>
|
<a
|
||||||
<a
|
class="inline-block btn-primary btn-large"
|
||||||
class="inline-block btn-primary btn-large"
|
href="/trail/edit/new"
|
||||||
href="/trails"
|
data-sveltekit-preload-data="off"
|
||||||
data-sveltekit-preload-data="off"
|
role="button">{$_("new-trail")}</a
|
||||||
role="button">{$_("explore")}</a
|
>
|
||||||
>
|
{:else}
|
||||||
|
<h2 class="text-4xl md:text-5xl font-bold">
|
||||||
|
{$currentUser
|
||||||
|
? $_("trails-for-you")
|
||||||
|
: $_("explore-some-trails")}
|
||||||
|
</h2>
|
||||||
|
<h5>
|
||||||
|
{$_("hero_section_1_text")}
|
||||||
|
</h5>
|
||||||
|
<a
|
||||||
|
class="inline-block btn-primary btn-large"
|
||||||
|
href="/trails"
|
||||||
|
data-sveltekit-preload-data="off"
|
||||||
|
role="button">{$_("explore")}</a
|
||||||
|
>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section
|
<section
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
import { object, string } from "yup";
|
import { object, string } from "yup";
|
||||||
import { theme } from "$lib/stores/theme_store";
|
import { theme } from "$lib/stores/theme_store";
|
||||||
import { _ } from "svelte-i18n";
|
import { _ } from "svelte-i18n";
|
||||||
|
import { page } from "$app/stores";
|
||||||
|
|
||||||
let loading: boolean = false;
|
let loading: boolean = false;
|
||||||
const { form, errors, handleChange, handleSubmit } = createForm<User>({
|
const { form, errors, handleChange, handleSubmit } = createForm<User>({
|
||||||
@@ -20,14 +21,14 @@
|
|||||||
password: "",
|
password: "",
|
||||||
},
|
},
|
||||||
validationSchema: object<User>({
|
validationSchema: object<User>({
|
||||||
username: string().required("Required"),
|
username: string().required($_("required")),
|
||||||
password: string().required("Required"),
|
password: string().required($_("required")),
|
||||||
}),
|
}),
|
||||||
onSubmit: async (newUser) => {
|
onSubmit: async (newUser) => {
|
||||||
loading = true;
|
loading = true;
|
||||||
try {
|
try {
|
||||||
await login(newUser);
|
await login(newUser);
|
||||||
goto(`/`);
|
goto($page.url.searchParams.get("r") ?? "/");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (
|
if (
|
||||||
e instanceof ClientResponseError &&
|
e instanceof ClientResponseError &&
|
||||||
@@ -36,13 +37,13 @@
|
|||||||
show_toast({
|
show_toast({
|
||||||
icon: "close",
|
icon: "close",
|
||||||
type: "error",
|
type: "error",
|
||||||
text: "Wrong username or password.",
|
text: $_('wrong-username-or-password'),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
show_toast({
|
show_toast({
|
||||||
icon: "close",
|
icon: "close",
|
||||||
type: "error",
|
type: "error",
|
||||||
text: "Error during login.",
|
text: $_('error-during-login'),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
@@ -90,9 +91,9 @@
|
|||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<span
|
<span
|
||||||
>{$_('no-account')} <a
|
>{$_("no-account")}
|
||||||
class="text-blue-500 underline"
|
<a class="text-blue-500 underline" href="/register"
|
||||||
href="/register">{$_('make-one')}</a
|
>{$_("make-one")}</a
|
||||||
></span
|
></span
|
||||||
>
|
>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -47,7 +47,7 @@
|
|||||||
let showFilter: boolean = false;
|
let showFilter: boolean = false;
|
||||||
let showMap: boolean = true;
|
let showMap: boolean = true;
|
||||||
|
|
||||||
let filter: TrailFilter;
|
const filter: TrailFilter = $page.data.filter;
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
L = (await import("leaflet")).default;
|
L = (await import("leaflet")).default;
|
||||||
@@ -130,20 +130,26 @@
|
|||||||
|
|
||||||
const response = await r.json();
|
const response = await r.json();
|
||||||
|
|
||||||
const trailItems = response.results[0].hits.map((t: Record<string, any>) => ({
|
const trailItems = response.results[0].hits.map(
|
||||||
text: t.name,
|
(t: Record<string, any>) => ({
|
||||||
description: `Trail | ${t.location}`,
|
text: t.name,
|
||||||
value: t,
|
description: `Trail | ${t.location}`,
|
||||||
icon: "route",
|
value: t,
|
||||||
}));
|
icon: "route",
|
||||||
const cityItems = response.results[1].hits.map((c: Record<string, any>) => ({
|
}),
|
||||||
text: c.name,
|
);
|
||||||
description: `City | ${
|
const cityItems = response.results[1].hits.map(
|
||||||
country_codes[c["country code"] as keyof typeof country_codes]
|
(c: Record<string, any>) => ({
|
||||||
}`,
|
text: c.name,
|
||||||
value: c,
|
description: `City | ${
|
||||||
icon: "city",
|
country_codes[
|
||||||
}));
|
c["country code"] as keyof typeof country_codes
|
||||||
|
]
|
||||||
|
}`,
|
||||||
|
value: c,
|
||||||
|
icon: "city",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
searchDropdownItems = [...trailItems, ...cityItems];
|
searchDropdownItems = [...trailItems, ...cityItems];
|
||||||
}
|
}
|
||||||
@@ -291,7 +297,7 @@
|
|||||||
categories={$categories}
|
categories={$categories}
|
||||||
showTrailSearch={false}
|
showTrailSearch={false}
|
||||||
showCitySearch={false}
|
showCitySearch={false}
|
||||||
bind:filter
|
{filter}
|
||||||
on:update={(e) => handleFilterUpdate(e.detail)}
|
on:update={(e) => handleFilterUpdate(e.detail)}
|
||||||
></TrailFilterPanel>
|
></TrailFilterPanel>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,9 +1,27 @@
|
|||||||
|
import type { TrailFilter } from "$lib/models/trail";
|
||||||
import { categories_index } from "$lib/stores/category_store";
|
import { categories_index } from "$lib/stores/category_store";
|
||||||
import { trails } from "$lib/stores/trail_store";
|
import { trails } from "$lib/stores/trail_store";
|
||||||
import type { ServerLoad } from "@sveltejs/kit";
|
import type { ServerLoad } from "@sveltejs/kit";
|
||||||
|
|
||||||
export const load: ServerLoad = async ({ params, locals, fetch }) => {
|
export const load: ServerLoad = async ({ params, locals, fetch }) => {
|
||||||
|
const filter: TrailFilter = {
|
||||||
|
q: "",
|
||||||
|
category: [],
|
||||||
|
difficulty: ["easy", "moderate", "difficult"],
|
||||||
|
near: {
|
||||||
|
radius: 2000,
|
||||||
|
},
|
||||||
|
distanceMin: 0,
|
||||||
|
distanceMax: 20000,
|
||||||
|
elevationGainMin: 0,
|
||||||
|
elevationGainMax: 4000,
|
||||||
|
sort: "created",
|
||||||
|
sortOrder: "+",
|
||||||
|
};
|
||||||
|
|
||||||
await categories_index(fetch)
|
await categories_index(fetch)
|
||||||
|
|
||||||
trails.set([])
|
trails.set([])
|
||||||
|
|
||||||
|
return { filter: filter }
|
||||||
};
|
};
|
||||||
@@ -35,6 +35,10 @@
|
|||||||
];
|
];
|
||||||
let activeTab = 0;
|
let activeTab = 0;
|
||||||
|
|
||||||
|
const thumbnail = $trail.photos.length
|
||||||
|
? getFileURL(trail, $trail.photos[$trail.thumbnail])
|
||||||
|
: "/imgs/default_thumbnail.webp";
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
lightboxDataSource = $trail.photos.map((p) => ({
|
lightboxDataSource = $trail.photos.map((p) => ({
|
||||||
src: getFileURL($trail, p),
|
src: getFileURL($trail, p),
|
||||||
@@ -83,7 +87,7 @@
|
|||||||
<section class="relative h-80">
|
<section class="relative h-80">
|
||||||
<img
|
<img
|
||||||
class="w-full h-80 object-cover"
|
class="w-full h-80 object-cover"
|
||||||
src={getFileURL($trail, $trail.photos[$trail.thumbnail])}
|
src={thumbnail}
|
||||||
alt=""
|
alt=""
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -6,11 +6,10 @@
|
|||||||
import LogoTextTwoLineLight from "$lib/components/logo/logo_text_two_line_light.svelte";
|
import LogoTextTwoLineLight from "$lib/components/logo/logo_text_two_line_light.svelte";
|
||||||
import { theme } from "$lib/stores/theme_store";
|
import { theme } from "$lib/stores/theme_store";
|
||||||
import { show_toast } from "$lib/stores/toast_store";
|
import { show_toast } from "$lib/stores/toast_store";
|
||||||
import { users_create, type User, login } from "$lib/stores/user_store";
|
import { login, users_create, type User } from "$lib/stores/user_store";
|
||||||
import { createForm } from "$lib/vendor/svelte-form-lib";
|
import { createForm } from "$lib/vendor/svelte-form-lib";
|
||||||
import { object, string } from "yup";
|
|
||||||
import { _ } from "svelte-i18n";
|
import { _ } from "svelte-i18n";
|
||||||
|
import { object, string } from "yup";
|
||||||
let loading: boolean = false;
|
let loading: boolean = false;
|
||||||
const { form, errors, handleChange, handleSubmit } = createForm<User>({
|
const { form, errors, handleChange, handleSubmit } = createForm<User>({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
@@ -20,11 +19,15 @@
|
|||||||
password: "",
|
password: "",
|
||||||
},
|
},
|
||||||
validationSchema: object<User>({
|
validationSchema: object<User>({
|
||||||
username: string().required("Required"),
|
username: string()
|
||||||
email: string().email().required("Required"),
|
.required($_("required"))
|
||||||
|
.matches(/^[\w][\w\.]*$/, { message: $_("invalid-username") }),
|
||||||
|
email: string()
|
||||||
|
.email($_("not-a-valid-email-address"))
|
||||||
|
.required($_("required")),
|
||||||
password: string()
|
password: string()
|
||||||
.min(8, "Must be at least 8 characters long")
|
.min(8, $_("must-be-at-least-8-characters-long"))
|
||||||
.required("Required"),
|
.required($_("required")),
|
||||||
}),
|
}),
|
||||||
onSubmit: async (newUser) => {
|
onSubmit: async (newUser) => {
|
||||||
loading = true;
|
loading = true;
|
||||||
@@ -34,8 +37,9 @@
|
|||||||
show_toast({
|
show_toast({
|
||||||
icon: "close",
|
icon: "close",
|
||||||
type: "error",
|
type: "error",
|
||||||
text: "Error creating user.",
|
text: $_("error-creating-user"),
|
||||||
});
|
});
|
||||||
|
return;
|
||||||
} finally {
|
} finally {
|
||||||
loading = false;
|
loading = false;
|
||||||
}
|
}
|
||||||
@@ -46,7 +50,7 @@
|
|||||||
show_toast({
|
show_toast({
|
||||||
icon: "close",
|
icon: "close",
|
||||||
type: "error",
|
type: "error",
|
||||||
text: "Error during login.",
|
text: $_("error-during-login"),
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
loading = false;
|
loading = false;
|
||||||
@@ -56,7 +60,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
<title>{$_('register')} | wanderer</title>
|
<title>{$_("register")} | wanderer</title>
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
<main class="flex justify-center">
|
<main class="flex justify-center">
|
||||||
<form
|
<form
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
import WaypointCard from "$lib/components/waypoint/waypoint_card.svelte";
|
import WaypointCard from "$lib/components/waypoint/waypoint_card.svelte";
|
||||||
import WaypointModal from "$lib/components/waypoint/waypoint_modal.svelte";
|
import WaypointModal from "$lib/components/waypoint/waypoint_modal.svelte";
|
||||||
import { SummitLog } from "$lib/models/summit_log";
|
import { SummitLog } from "$lib/models/summit_log";
|
||||||
import { Trail, trailSchema } from "$lib/models/trail";
|
import { Trail } from "$lib/models/trail";
|
||||||
import { Waypoint } from "$lib/models/waypoint";
|
import { Waypoint } from "$lib/models/waypoint";
|
||||||
import { categories } from "$lib/stores/category_store";
|
import { categories } from "$lib/stores/category_store";
|
||||||
import { summitLog } from "$lib/stores/summit_log_store";
|
import { summitLog } from "$lib/stores/summit_log_store";
|
||||||
@@ -37,6 +37,7 @@
|
|||||||
import "leaflet/dist/leaflet.css";
|
import "leaflet/dist/leaflet.css";
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import { _ } from "svelte-i18n";
|
import { _ } from "svelte-i18n";
|
||||||
|
import { array, number, object, string } from "yup";
|
||||||
|
|
||||||
export let data: { trail: Trail };
|
export let data: { trail: Trail };
|
||||||
|
|
||||||
@@ -55,6 +56,22 @@
|
|||||||
|
|
||||||
let gpxFile: File | null = null;
|
let gpxFile: File | null = null;
|
||||||
|
|
||||||
|
const trailSchema = object<Trail>({
|
||||||
|
id: string().optional(),
|
||||||
|
name: string().required($_("required")),
|
||||||
|
location: string().optional(),
|
||||||
|
distance: number().optional(),
|
||||||
|
difficulty: string()
|
||||||
|
.oneOf(["easy", "moderate", "difficult"])
|
||||||
|
.optional(),
|
||||||
|
elevation_gain: number().optional(),
|
||||||
|
duration: number().optional(),
|
||||||
|
thumbnail: string().optional(),
|
||||||
|
photos: array(string()).optional(),
|
||||||
|
gpx: string().optional(),
|
||||||
|
description: string().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
L = (await import("leaflet")).default;
|
L = (await import("leaflet")).default;
|
||||||
await import("leaflet-gpx");
|
await import("leaflet-gpx");
|
||||||
@@ -457,9 +474,9 @@
|
|||||||
label={$_("difficulty")}
|
label={$_("difficulty")}
|
||||||
bind:value={$form.difficulty}
|
bind:value={$form.difficulty}
|
||||||
items={[
|
items={[
|
||||||
{ text: $_('easy'), value: "easy" },
|
{ text: $_("easy"), value: "easy" },
|
||||||
{ text: $_('moderate'), value: "moderate" },
|
{ text: $_("moderate"), value: "moderate" },
|
||||||
{ text: $_('difficult'), value: "difficult" },
|
{ text: $_("difficult"), value: "difficult" },
|
||||||
]}
|
]}
|
||||||
></Select>
|
></Select>
|
||||||
{#if $form.expand.category}
|
{#if $form.expand.category}
|
||||||
|
|||||||
@@ -39,6 +39,10 @@
|
|||||||
let lightbox: PhotoSwipeLightbox;
|
let lightbox: PhotoSwipeLightbox;
|
||||||
let lightboxDataSource: DataSource;
|
let lightboxDataSource: DataSource;
|
||||||
|
|
||||||
|
const thumbnail = $trail.photos.length
|
||||||
|
? getFileURL(trail, $trail.photos[$trail.thumbnail])
|
||||||
|
: "/imgs/default_thumbnail.webp";
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
const L = (await import("leaflet")).default;
|
const L = (await import("leaflet")).default;
|
||||||
await import("leaflet-gpx");
|
await import("leaflet-gpx");
|
||||||
@@ -134,7 +138,7 @@
|
|||||||
<section class="relative h-80">
|
<section class="relative h-80">
|
||||||
<img
|
<img
|
||||||
class="w-full h-80"
|
class="w-full h-80"
|
||||||
src={getFileURL($trail, $trail.photos[$trail.thumbnail])}
|
src={thumbnail}
|
||||||
alt=""
|
alt=""
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
|
|||||||
BIN
web/static/imgs/default_thumbnail.webp
Normal file
BIN
web/static/imgs/default_thumbnail.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 154 KiB |
BIN
web/static/imgs/empty_state.png
Normal file
BIN
web/static/imgs/empty_state.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 51 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 284 KiB |
Reference in New Issue
Block a user