adds new default state images
This commit is contained in:
@@ -46,7 +46,7 @@ services:
|
||||
build: ./web
|
||||
environment:
|
||||
<<: *cenv
|
||||
MEILI_API_TOKEN:
|
||||
MEILI_API_TOKEN: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhcGlLZXlVaWQiOiIwZDA0YTAzYy1iNzBjLTQzZTctOGUzMC00NjNiODFhM2UwY2YiLCJzZWFyY2hSdWxlcyI6eyJ0cmFpbHMiOnsiZmlsdGVyIjoicHVibGljPXRydWUifSwiY2l0aWVzNTAwIjp7fX0sImV4cCI6bnVsbH0.XhHwyRqtKjij2-oPvcYAL3uIFmB4lxUBUgVzhQqL1To
|
||||
ORIGIN: http://localhost:8080
|
||||
PUBLIC_POCKETBASE_URL: http://db:8090
|
||||
ports:
|
||||
|
||||
@@ -43,8 +43,8 @@ def init_indices():
|
||||
|
||||
|
||||
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 = {
|
||||
'trails': {
|
||||
'filter': 'public=true'
|
||||
@@ -60,6 +60,6 @@ init_indices()
|
||||
print("Indices initialized!")
|
||||
token = generate_public_token()
|
||||
print("Generating public token...")
|
||||
print(f"PUBLIC_MEILISEARCH_API_TOKEN:{token}")
|
||||
print(f"MEILI_API_TOKEN: {token}")
|
||||
|
||||
print("Bootstrapping complete!")
|
||||
|
||||
@@ -12,7 +12,7 @@ export const handle: Handle = async ({ event, resolve }) => {
|
||||
|
||||
// validate the user existence and if the path is acceesible
|
||||
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") {
|
||||
throw redirect(302, '/');
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<img
|
||||
style="width: {width}px"
|
||||
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"
|
||||
/>
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
function handleDropdownClick(item: { text: string; value: any }) {
|
||||
if (item.value == "logout") {
|
||||
logout();
|
||||
goto("/");
|
||||
window.location.href = "/";
|
||||
} else if (item.value == "profile") {
|
||||
goto("/profile");
|
||||
}
|
||||
@@ -117,7 +117,7 @@
|
||||
<hr class="my-6 border-input-border" />
|
||||
<div class="flex flex-col basis-full">
|
||||
<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}
|
||||
<div class="basis-full"></div>
|
||||
@@ -180,7 +180,7 @@
|
||||
on:click={() => toggleTheme()}
|
||||
></button>
|
||||
<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
|
||||
items={dropdownItems}
|
||||
@@ -208,7 +208,7 @@
|
||||
: 'moon'}"
|
||||
on:click={() => toggleTheme()}
|
||||
></button>
|
||||
<a class="btn-primary btn-large" href="/login">{$_('login')}</a>
|
||||
<a class="btn-primary btn-large" href="/login">{$_("login")}</a>
|
||||
</div>
|
||||
{/if}
|
||||
<button
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
} from "$lib/util/format_util";
|
||||
|
||||
export let trail: Trail;
|
||||
|
||||
$: thumbnail = trail.photos.length
|
||||
? getFileURL(trail, trail.photos[trail.thumbnail])
|
||||
: "/imgs/default_thumbnail.webp";
|
||||
</script>
|
||||
|
||||
<div
|
||||
@@ -17,7 +21,7 @@
|
||||
role="listitem"
|
||||
>
|
||||
<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 class="p-4">
|
||||
<div>
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
<div id="trails" class="flex items-start flex-wrap gap-8 py-8 max-w-full">
|
||||
{#if trails.length == 0}
|
||||
<div class="flex flex-col basis-full items-center">
|
||||
<EmptyStateSearch></EmptyStateSearch>
|
||||
<EmptyStateSearch width={356}></EmptyStateSearch>
|
||||
</div>
|
||||
{/if}
|
||||
{#each trails as trail}
|
||||
|
||||
@@ -1,20 +1,24 @@
|
||||
<script lang="ts">
|
||||
import type { Trail } from "$lib/models/trail";
|
||||
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;
|
||||
|
||||
$: thumbnail = trail.photos.length
|
||||
? getFileURL(trail, trail.photos[trail.thumbnail])
|
||||
: "/imgs/default_thumbnail.webp";
|
||||
</script>
|
||||
|
||||
<li
|
||||
class="flex gap-8 p-4 rounded-xl border border-input-border cursor-pointer hover:bg-secondary-hover transition-colors"
|
||||
>
|
||||
<div class="shrink-0">
|
||||
<img
|
||||
class="h-28 w-28 object-cover rounded-xl"
|
||||
src={getFileURL(trail, trail.photos[trail.thumbnail])}
|
||||
alt=""
|
||||
/>
|
||||
<img class="h-28 w-28 object-cover rounded-xl" src={thumbnail} alt="" />
|
||||
</div>
|
||||
<div class="min-w-0 basis-full">
|
||||
<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.",
|
||||
"describe-your-trail": "Beschreibe deine Route",
|
||||
"description": "Beschreibung",
|
||||
"difficult": "Schwierig",
|
||||
"difficult": "Schwer",
|
||||
"difficulty": "Schwierigkeit",
|
||||
"directions": "Wegbeschreibung",
|
||||
"display-as": "Anzeigen als",
|
||||
@@ -42,16 +42,21 @@
|
||||
"elevation-gain": "Höhenunterschied",
|
||||
"email": "Email",
|
||||
"english": "Englisch",
|
||||
"error-creating-user": "Fehler beim Erstellen des Nutzers",
|
||||
"error-during-login": "Fehler beim Login",
|
||||
"est-duration": "Gesch. Dauer",
|
||||
"explore": "Erkunden",
|
||||
"explore-some-trails": "Erkunden Sie einige Routen",
|
||||
"features": "Feature",
|
||||
"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_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_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.",
|
||||
"icon": "Icon",
|
||||
"imperial": "Amerikanisch",
|
||||
"invalid-username": "Ungültiger Nutzername",
|
||||
"language": "Sprache",
|
||||
"latitude": "Breitengrad",
|
||||
"license": "Lizenz",
|
||||
@@ -65,6 +70,7 @@
|
||||
"map": "Karte",
|
||||
"metric": "Metrisch",
|
||||
"moderate": "Mittel",
|
||||
"must-be-at-least-8-characters-long": "Muss mindestens 8 Zeichen lang sein",
|
||||
"name": "Name",
|
||||
"near": "Nahe",
|
||||
"new-list": "Neue Liste",
|
||||
@@ -72,6 +78,7 @@
|
||||
"no-account": "Du hast noch kein Konto?",
|
||||
"no-preference": "Keine Präferenz",
|
||||
"no-results": "Keine Ergebnisse gefunden",
|
||||
"not-a-valid-email-address": "Keine gültige Email-Adresse",
|
||||
"not-completed": "Nicht abgeschlossen",
|
||||
"password": "Passwort",
|
||||
"photos": "Fotos",
|
||||
@@ -81,6 +88,7 @@
|
||||
"radius": "Radius",
|
||||
"register": "Registrieren",
|
||||
"removed-trail-from": "Route entfernt aus",
|
||||
"required": "Pflichtfeld",
|
||||
"save": "Speichern",
|
||||
"save-trail": "Route speichern",
|
||||
"search-cities": "Städte suchen",
|
||||
@@ -100,5 +108,6 @@
|
||||
"upload-gpx": "GPX hochladen",
|
||||
"username": "Nutzername",
|
||||
"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",
|
||||
"email": "Email",
|
||||
"english": "English",
|
||||
"error-creating-user": "Error creating user",
|
||||
"error-during-login": "Error during login",
|
||||
"est-duration": "Est. duration",
|
||||
"explore": "Explore",
|
||||
"explore-some-trails": "Explore some trails",
|
||||
"features": "Features",
|
||||
"german": "German",
|
||||
"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_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.",
|
||||
"icon": "Icon",
|
||||
"imperial": "Imperial",
|
||||
"invalid-username": "Invalid username",
|
||||
"language": "Language",
|
||||
"latitude": "Latitude",
|
||||
"license": "License",
|
||||
@@ -65,6 +70,7 @@
|
||||
"map": "Map",
|
||||
"metric": "Metric",
|
||||
"moderate": "Moderate",
|
||||
"must-be-at-least-8-characters-long": "Must be at least 8 characters long",
|
||||
"name": "Name",
|
||||
"near": "Near",
|
||||
"new-list": "New List",
|
||||
@@ -72,6 +78,7 @@
|
||||
"no-account": "Don't have an account?",
|
||||
"no-preference": "No preference",
|
||||
"no-results": "No results found",
|
||||
"not-a-valid-email-address": "Not a valid email address",
|
||||
"not-completed": "Not completed",
|
||||
"password": "Password",
|
||||
"photos": "Photos",
|
||||
@@ -81,6 +88,7 @@
|
||||
"radius": "Radius",
|
||||
"register": "Register",
|
||||
"removed-trail-from": "Removed trail from",
|
||||
"required": "Required",
|
||||
"save": "Save",
|
||||
"save-trail": "Save Trail",
|
||||
"search-cities": "Search cities",
|
||||
@@ -100,5 +108,6 @@
|
||||
"upload-gpx": "Upload GPX",
|
||||
"username": "Username",
|
||||
"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 { SummitLog } from "./summit_log";
|
||||
import type { Waypoint } from "./waypoint";
|
||||
@@ -11,7 +10,7 @@ class Trail {
|
||||
distance?: number;
|
||||
elevation_gain?: number;
|
||||
duration?: number;
|
||||
difficulty?: "easy"|"moderate"|"difficult"
|
||||
difficulty?: "easy" | "moderate" | "difficult"
|
||||
lat?: number;
|
||||
lon?: number;
|
||||
thumbnail: number;
|
||||
@@ -39,8 +38,8 @@ class Trail {
|
||||
distance?: number,
|
||||
elevation_gain?: number,
|
||||
duration?: number,
|
||||
difficulty?: "easy"|"moderate"|"difficult",
|
||||
lat?:number,
|
||||
difficulty?: "easy" | "moderate" | "difficult",
|
||||
lat?: number,
|
||||
lon?: number,
|
||||
thumbnail?: number,
|
||||
photos?: string[],
|
||||
@@ -66,7 +65,7 @@ class Trail {
|
||||
this.lon = params?.lon;
|
||||
this.thumbnail = params?.thumbnail ?? 0;
|
||||
this.photos = params?.photos ?? [];
|
||||
this.waypoints = [];
|
||||
this.waypoints = [];
|
||||
this.summit_logs = [];
|
||||
this.gpx = params?.gpx;
|
||||
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 {
|
||||
q: string,
|
||||
category: string[],
|
||||
@@ -108,10 +94,9 @@ interface TrailFilter {
|
||||
elevationGainMax: number;
|
||||
completed?: boolean;
|
||||
sort: "name" | "distance" | "elevation_gain" | "created";
|
||||
sortOrder: "+" | "-"
|
||||
sortOrder: "+" | "-"
|
||||
}
|
||||
|
||||
export { Trail, trailSchema };
|
||||
|
||||
export type { TrailFilter };
|
||||
export { Trail };
|
||||
|
||||
export type { TrailFilter };
|
||||
@@ -60,7 +60,7 @@ export async function trails_search_filter(filter: TrailFilter, page: number = 1
|
||||
if (!r.ok) {
|
||||
throw new ClientResponseError(result)
|
||||
}
|
||||
|
||||
|
||||
const trailIds = result.hits.map((h: Record<string, any>) => h.id);
|
||||
|
||||
if (trailIds.length == 0) {
|
||||
@@ -92,6 +92,8 @@ export async function trails_search_bounding_box(northEast: LatLng, southWest: L
|
||||
if (filter) {
|
||||
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) {
|
||||
filterText += ` AND category IN [${filter.category.join(",")}]`;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const privateRoutes = [
|
||||
"/profile",
|
||||
"/lists"
|
||||
"/lists",
|
||||
"/trail",
|
||||
]
|
||||
|
||||
export function isRouteProtected(path: string) {
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
beforeNavigate((n) => {
|
||||
if (!$currentUser && isRouteProtected(n.to?.url?.pathname ?? "")) {
|
||||
n.cancel();
|
||||
goto("/login");
|
||||
goto("/login?r="+n.to?.url?.pathname);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -108,6 +108,16 @@
|
||||
id="trails"
|
||||
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}
|
||||
<a href="/trail/view/{$trails[i].id}">
|
||||
<TrailCard trail={$trails[i]}></TrailCard></a
|
||||
@@ -115,18 +125,33 @@
|
||||
{/each}
|
||||
</div>
|
||||
<div class="max-w-md md:mx-auto space-y-8">
|
||||
<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 $trails.length == 0}
|
||||
<h2 class="text-4xl md:text-5xl font-bold">
|
||||
{$_("hero_section_1_heading")}
|
||||
</h2>
|
||||
<h5>{$_("hero_section_1_text_alternative")}</h5>
|
||||
<a
|
||||
class="inline-block btn-primary btn-large"
|
||||
href="/trail/edit/new"
|
||||
data-sveltekit-preload-data="off"
|
||||
role="button">{$_("new-trail")}</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>
|
||||
</section>
|
||||
<section
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
import { object, string } from "yup";
|
||||
import { theme } from "$lib/stores/theme_store";
|
||||
import { _ } from "svelte-i18n";
|
||||
import { page } from "$app/stores";
|
||||
|
||||
let loading: boolean = false;
|
||||
const { form, errors, handleChange, handleSubmit } = createForm<User>({
|
||||
@@ -20,14 +21,14 @@
|
||||
password: "",
|
||||
},
|
||||
validationSchema: object<User>({
|
||||
username: string().required("Required"),
|
||||
password: string().required("Required"),
|
||||
username: string().required($_("required")),
|
||||
password: string().required($_("required")),
|
||||
}),
|
||||
onSubmit: async (newUser) => {
|
||||
loading = true;
|
||||
try {
|
||||
await login(newUser);
|
||||
goto(`/`);
|
||||
goto($page.url.searchParams.get("r") ?? "/");
|
||||
} catch (e) {
|
||||
if (
|
||||
e instanceof ClientResponseError &&
|
||||
@@ -36,13 +37,13 @@
|
||||
show_toast({
|
||||
icon: "close",
|
||||
type: "error",
|
||||
text: "Wrong username or password.",
|
||||
text: $_('wrong-username-or-password'),
|
||||
});
|
||||
} else {
|
||||
show_toast({
|
||||
icon: "close",
|
||||
type: "error",
|
||||
text: "Error during login.",
|
||||
text: $_('error-during-login'),
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
@@ -90,9 +91,9 @@
|
||||
>
|
||||
</div>
|
||||
<span
|
||||
>{$_('no-account')} <a
|
||||
class="text-blue-500 underline"
|
||||
href="/register">{$_('make-one')}</a
|
||||
>{$_("no-account")}
|
||||
<a class="text-blue-500 underline" href="/register"
|
||||
>{$_("make-one")}</a
|
||||
></span
|
||||
>
|
||||
</form>
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
let showFilter: boolean = false;
|
||||
let showMap: boolean = true;
|
||||
|
||||
let filter: TrailFilter;
|
||||
const filter: TrailFilter = $page.data.filter;
|
||||
|
||||
onMount(async () => {
|
||||
L = (await import("leaflet")).default;
|
||||
@@ -130,20 +130,26 @@
|
||||
|
||||
const response = await r.json();
|
||||
|
||||
const trailItems = response.results[0].hits.map((t: Record<string, any>) => ({
|
||||
text: t.name,
|
||||
description: `Trail | ${t.location}`,
|
||||
value: t,
|
||||
icon: "route",
|
||||
}));
|
||||
const cityItems = response.results[1].hits.map((c: Record<string, any>) => ({
|
||||
text: c.name,
|
||||
description: `City | ${
|
||||
country_codes[c["country code"] as keyof typeof country_codes]
|
||||
}`,
|
||||
value: c,
|
||||
icon: "city",
|
||||
}));
|
||||
const trailItems = response.results[0].hits.map(
|
||||
(t: Record<string, any>) => ({
|
||||
text: t.name,
|
||||
description: `Trail | ${t.location}`,
|
||||
value: t,
|
||||
icon: "route",
|
||||
}),
|
||||
);
|
||||
const cityItems = response.results[1].hits.map(
|
||||
(c: Record<string, any>) => ({
|
||||
text: c.name,
|
||||
description: `City | ${
|
||||
country_codes[
|
||||
c["country code"] as keyof typeof country_codes
|
||||
]
|
||||
}`,
|
||||
value: c,
|
||||
icon: "city",
|
||||
}),
|
||||
);
|
||||
|
||||
searchDropdownItems = [...trailItems, ...cityItems];
|
||||
}
|
||||
@@ -291,7 +297,7 @@
|
||||
categories={$categories}
|
||||
showTrailSearch={false}
|
||||
showCitySearch={false}
|
||||
bind:filter
|
||||
{filter}
|
||||
on:update={(e) => handleFilterUpdate(e.detail)}
|
||||
></TrailFilterPanel>
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,27 @@
|
||||
import type { TrailFilter } from "$lib/models/trail";
|
||||
import { categories_index } from "$lib/stores/category_store";
|
||||
import { trails } from "$lib/stores/trail_store";
|
||||
import type { ServerLoad } from "@sveltejs/kit";
|
||||
|
||||
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)
|
||||
|
||||
trails.set([])
|
||||
|
||||
return { filter: filter }
|
||||
};
|
||||
@@ -35,6 +35,10 @@
|
||||
];
|
||||
let activeTab = 0;
|
||||
|
||||
const thumbnail = $trail.photos.length
|
||||
? getFileURL(trail, $trail.photos[$trail.thumbnail])
|
||||
: "/imgs/default_thumbnail.webp";
|
||||
|
||||
onMount(() => {
|
||||
lightboxDataSource = $trail.photos.map((p) => ({
|
||||
src: getFileURL($trail, p),
|
||||
@@ -83,7 +87,7 @@
|
||||
<section class="relative h-80">
|
||||
<img
|
||||
class="w-full h-80 object-cover"
|
||||
src={getFileURL($trail, $trail.photos[$trail.thumbnail])}
|
||||
src={thumbnail}
|
||||
alt=""
|
||||
/>
|
||||
<div
|
||||
|
||||
@@ -6,11 +6,10 @@
|
||||
import LogoTextTwoLineLight from "$lib/components/logo/logo_text_two_line_light.svelte";
|
||||
import { theme } from "$lib/stores/theme_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 { object, string } from "yup";
|
||||
import { _ } from "svelte-i18n";
|
||||
|
||||
import { object, string } from "yup";
|
||||
let loading: boolean = false;
|
||||
const { form, errors, handleChange, handleSubmit } = createForm<User>({
|
||||
initialValues: {
|
||||
@@ -20,11 +19,15 @@
|
||||
password: "",
|
||||
},
|
||||
validationSchema: object<User>({
|
||||
username: string().required("Required"),
|
||||
email: string().email().required("Required"),
|
||||
username: string()
|
||||
.required($_("required"))
|
||||
.matches(/^[\w][\w\.]*$/, { message: $_("invalid-username") }),
|
||||
email: string()
|
||||
.email($_("not-a-valid-email-address"))
|
||||
.required($_("required")),
|
||||
password: string()
|
||||
.min(8, "Must be at least 8 characters long")
|
||||
.required("Required"),
|
||||
.min(8, $_("must-be-at-least-8-characters-long"))
|
||||
.required($_("required")),
|
||||
}),
|
||||
onSubmit: async (newUser) => {
|
||||
loading = true;
|
||||
@@ -34,8 +37,9 @@
|
||||
show_toast({
|
||||
icon: "close",
|
||||
type: "error",
|
||||
text: "Error creating user.",
|
||||
text: $_("error-creating-user"),
|
||||
});
|
||||
return;
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
@@ -46,7 +50,7 @@
|
||||
show_toast({
|
||||
icon: "close",
|
||||
type: "error",
|
||||
text: "Error during login.",
|
||||
text: $_("error-during-login"),
|
||||
});
|
||||
} finally {
|
||||
loading = false;
|
||||
@@ -56,7 +60,7 @@
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{$_('register')} | wanderer</title>
|
||||
<title>{$_("register")} | wanderer</title>
|
||||
</svelte:head>
|
||||
<main class="flex justify-center">
|
||||
<form
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
import WaypointCard from "$lib/components/waypoint/waypoint_card.svelte";
|
||||
import WaypointModal from "$lib/components/waypoint/waypoint_modal.svelte";
|
||||
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 { categories } from "$lib/stores/category_store";
|
||||
import { summitLog } from "$lib/stores/summit_log_store";
|
||||
@@ -37,6 +37,7 @@
|
||||
import "leaflet/dist/leaflet.css";
|
||||
import { onMount } from "svelte";
|
||||
import { _ } from "svelte-i18n";
|
||||
import { array, number, object, string } from "yup";
|
||||
|
||||
export let data: { trail: Trail };
|
||||
|
||||
@@ -55,6 +56,22 @@
|
||||
|
||||
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 () => {
|
||||
L = (await import("leaflet")).default;
|
||||
await import("leaflet-gpx");
|
||||
@@ -457,9 +474,9 @@
|
||||
label={$_("difficulty")}
|
||||
bind:value={$form.difficulty}
|
||||
items={[
|
||||
{ text: $_('easy'), value: "easy" },
|
||||
{ text: $_('moderate'), value: "moderate" },
|
||||
{ text: $_('difficult'), value: "difficult" },
|
||||
{ text: $_("easy"), value: "easy" },
|
||||
{ text: $_("moderate"), value: "moderate" },
|
||||
{ text: $_("difficult"), value: "difficult" },
|
||||
]}
|
||||
></Select>
|
||||
{#if $form.expand.category}
|
||||
|
||||
@@ -39,6 +39,10 @@
|
||||
let lightbox: PhotoSwipeLightbox;
|
||||
let lightboxDataSource: DataSource;
|
||||
|
||||
const thumbnail = $trail.photos.length
|
||||
? getFileURL(trail, $trail.photos[$trail.thumbnail])
|
||||
: "/imgs/default_thumbnail.webp";
|
||||
|
||||
onMount(async () => {
|
||||
const L = (await import("leaflet")).default;
|
||||
await import("leaflet-gpx");
|
||||
@@ -134,7 +138,7 @@
|
||||
<section class="relative h-80">
|
||||
<img
|
||||
class="w-full h-80"
|
||||
src={getFileURL($trail, $trail.photos[$trail.thumbnail])}
|
||||
src={thumbnail}
|
||||
alt=""
|
||||
/>
|
||||
<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