adds profile page

This commit is contained in:
Christian Beutel
2024-02-27 22:59:22 +01:00
parent 965e4196ae
commit 0d3683a319
13 changed files with 342 additions and 69 deletions

View File

@@ -54,10 +54,11 @@
</script>
<main
class="grid grid-cols-1 md:grid-cols-[400px_1fr] gap-8 max-w-7xl mx-6 md:mx-auto min-h-screen"
class="grid grid-cols-1 md:grid-cols-[400px_1fr] gap-8 max-w-7xl mx-4 md:mx-auto"
style="min-height: calc(100vh - 124px)"
>
<ul
class="list-list max-w-xl mx-2 md:mx-auto mt-8 rounded-xl border border-input-border p-4"
class="list-list max-w-xl mx-2 md:mx-auto mt-8 rounded-xl border border-input-border p-4"
>
<button
class="flex w-full items-center gap-4 hover:bg-menu-item-background-hover transition-colors rounded-xl p-4 cursor-pointer"
@@ -67,17 +68,20 @@
<h5 class="text-xl font-semibold">Create new list</h5>
</button>
<hr class="border-separator my-2" />
{#each $lists as item, i}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-noninteractive-element-interactions -->
<li on:click={() => list.set(item)}>
<ListCard list={item} on:change={(e) => handleDropdownClick(e, item)} active={item.id === $list.id}
></ListCard>
{#if i != $lists.length - 1}
<hr class="border-separator my-2" />
{/if}
</li>
{/each}
<div class="max-h-[512px] md:max-h-none overflow-y-auto">
{#each $lists as item, i}
<li on:click={() => list.set(item)} role="presentation">
<ListCard
list={item}
on:change={(e) => handleDropdownClick(e, item)}
active={item.id === $list.id}
></ListCard>
{#if i != $lists.length - 1}
<hr class="border-separator my-2" />
{/if}
</li>
{/each}
</div>
</ul>
<TrailList
bind:filter

View File

@@ -30,6 +30,7 @@
import { categories } from "$lib/stores/category_store";
import { slide } from "svelte/transition";
import { browser } from "$app/environment";
import { getFileURL } from "$lib/util/file_util";
let L: any;
let map: Map;
@@ -188,7 +189,7 @@
`<a href="/trail/view/${trail.id}">
<li class="flex items-center gap-4 cursor-pointer text-black">
<div class="shrink-0"><img class="h-14 w-14 object-cover rounded-xl" src="${
trail.thumbnail
getFileURL(trail, trail.thumbnail)
}" alt="">
</div>
<div>

View File

@@ -0,0 +1,102 @@
<script lang="ts">
import Search, {
type SearchItem,
} from "$lib/components/base/search.svelte";
import Select, {
type SelectItem,
} from "$lib/components/base/select.svelte";
import TextField from "$lib/components/base/text_field.svelte";
import { ms } from "$lib/meilisearch";
import { currentUser } from "$lib/stores/user_store";
import { country_codes } from "$lib/util/country_code_util";
import RadioGroup, {
type RadioItem,
} from "$lib/components/base/radio_group.svelte";
const languages: SelectItem[] = [
{ text: "English", value: "en" },
{ text: "German", value: "de" },
];
const units: RadioItem[] = [
{ text: "Metric", value: "metric" },
{ text: "Imperial", value: "imperial" },
];
let selectedLanguage = "en";
let searchDropdownItems: SearchItem[] = [];
let citySearchQuery: string = "";
async function searchCities(q: string) {
const result = await ms.index("cities500").search(q, { limit: 5 });
searchDropdownItems = result.hits.map((h) => ({
text: h.name,
description:
country_codes[h["country code"] as keyof typeof country_codes],
value: h,
icon: "city",
}));
}
async function handleSearchClick(item: SearchItem) {}
</script>
<main
class="grid grid-cols-1 md:grid-cols-[356px_1fr] max-w-4xl mx-4 md:mx-auto gap-x-8 items-start"
>
{#if $currentUser}
<div
class="flex flex-col items-center rounded-xl border border-input-border p-4"
>
<img
class="rounded-full w-32 aspect-square"
src={$currentUser.avatar ||
`https://api.dicebear.com/7.x/initials/svg?seed=${$currentUser.username}&backgroundType=gradientLinear`}
alt=""
/>
<h3 class="text-2xl mt-4 font-semibold">{$currentUser.username}</h3>
<h5 class="font-medium">{$currentUser.email}</h5>
</div>
<div class="space-y-6 rounded-xl p-4">
<h3 class="text-2xl font-semibold">Settings</h3>
<div>
<h5 class="font-medium mb-1">Default Location</h5>
<Search
items={searchDropdownItems}
placeholder="Search cities..."
bind:value={citySearchQuery}
on:update={(e) => searchCities(e.detail)}
on:click={(e) => handleSearchClick(e.detail)}
></Search>
</div>
<div>
<h5 class="font-medium mb-1">Language</h5>
<Select items={languages} bind:value={selectedLanguage}
></Select>
</div>
<div>
<h5 class="font-medium mb-1">Units</h5>
<RadioGroup
name="unit"
items={units}
selected={0}
on:change={() => {}}
></RadioGroup>
</div>
<div class="space-y-2">
<h5 class="font-medium mb-1">Set new password</h5>
<TextField placeholder="New password" label="" type="password"
></TextField>
<TextField placeholder="Repeat password" type="password"
></TextField>
</div>
<hr class="border-input-border" />
<div class="space-y-4">
<h4 class="text-xl text-red-400">Danger zone</h4>
<button class="btn-danger">Delete Account</button>
</div>
</div>
{/if}
</main>

View File

@@ -1,12 +1,22 @@
<script lang="ts">
import { goto } from "$app/navigation";
import Dropdown from "$lib/components/base/dropdown.svelte";
import type { DropdownItem } from "$lib/components/base/dropdown.svelte";
import ConfirmModal from "$lib/components/confirm_modal.svelte";
import ListSelectModal from "$lib/components/list/list_select_modal.svelte";
import SummitLogCard from "$lib/components/summit_log/summit_log_card.svelte";
import Tabs from "$lib/components/tabs.svelte";
import WaypointCard from "$lib/components/waypoint/waypoint_card.svelte";
import type { List } from "$lib/models/list";
import {
lists_add_trail,
lists_index,
lists_remove_trail,
} from "$lib/stores/list_store";
import { show_toast } from "$lib/stores/toast_store";
import { trail, trails_delete } from "$lib/stores/trail_store";
import { currentUser } from "$lib/stores/user_store";
import { getFileURL } from "$lib/util/file_util";
import { formatMeters, formatTimeHHMM } from "$lib/util/format_util";
import { createMarkerFromWaypoint } from "$lib/util/leaflet_util";
import type { Icon, Map, Marker } from "leaflet";
@@ -29,11 +39,15 @@
let lightboxDataSource: DataSource;
let openConfirmModal: () => void;
let openListSelectModal: () => void;
let mapFullScreen: boolean = false;
const dropdownItems: DropdownItem[] = [
{ text: "Show on map", value: "map", icon: "map" },
{ text: "Directions", value: "direction", icon: "car" },
...($trail.gpx ? [{ text: "Download GPX", value: "download", icon: "download" }] : []),
{ text: "Add to list", value: "list", icon: "bookmark" },
{ text: "Edit", value: "edit", icon: "pen" },
{ text: "Delete", value: "delete", icon: "trash" },
];
@@ -129,6 +143,18 @@
async function handleDropdownClick(item: { text: string; value: any }) {
if (item.value == "map") {
goto(`/map/?lat=${$trail.lat}&lon=${$trail.lon}`);
} else if (item.value == "list") {
openListSelectModal();
} else if (item.value == "direction") {
window
.open(
`https://www.google.com/maps/dir/Current+Location/${$trail.lat},${$trail.lon}`,
"_blank",
)
?.focus();
} else if (item.value == "download") {
downloadURI(getFileURL($trail, $trail.gpx), $trail.gpx!);
} else if (item.value == "edit") {
goto(`/trail/edit/${$trail.id}`);
} else if (item.value == "delete") {
@@ -136,6 +162,15 @@
}
}
function downloadURI(uri: string, name: string) {
var link = document.createElement("a");
link.setAttribute("download", name);
link.href = uri;
document.body.appendChild(link);
link.click();
link.remove();
}
async function deleteTrail() {
trails_delete($trail).then(() => history.back());
}
@@ -145,13 +180,46 @@
await tick();
map.invalidateSize();
}
async function handleListSelection(list: List) {
try {
if (list.trails?.includes($trail.id!)) {
await lists_remove_trail(list, $trail);
show_toast({
type: "success",
icon: "check",
text: `Removed trail from "${list.name}"`,
});
} else {
await lists_add_trail(list, $trail);
show_toast({
type: "success",
icon: "check",
text: `Added trail to "${list.name}"`,
});
}
await lists_index();
} catch (e) {
console.error(e);
show_toast({
type: "error",
icon: "close",
text: "Error adding trail to list.",
});
}
}
</script>
<div
class="trail-panel max-w-5xl mx-auto border border-input-border rounded-3xl overflow-hidden"
>
<section class="relative h-80">
<img class="w-full h-80" src={$trail.thumbnail} alt="" />
<img
class="w-full h-80"
src={getFileURL($trail, $trail.thumbnail)}
alt=""
/>
<div
class="absolute bottom-0 w-full h-1/2 bg-gradient-to-b from-transparent to-black opacity-50"
></div>
@@ -168,18 +236,17 @@
</h3>
</div>
{#if $currentUser && $currentUser.id == $trail.author}
<div
class="px-4 py-2 bg-menu-background rounded-full space-x-2"
<Dropdown
items={dropdownItems}
on:change={(e) => handleDropdownClick(e.detail)}
let:toggleMenu={openDropdown}
><button
class="rounded-full bg-white text-black hover:bg-gray-200 focus:ring-4 ring-gray-100/50 transition-colors h-12 w-12"
on:click={openDropdown}
>
<i class="fa fa-ellipsis-vertical"></i>
</button></Dropdown
>
{#each dropdownItems as item}
<button
data-title={item.text}
class="tooltip btn-icon"
on:click={() => handleDropdownClick(item)}
><i class="fa fa-{item.icon}"></i></button
>
{/each}
</div>
{/if}
</div>
</section>
@@ -288,6 +355,10 @@
bind:openModal={openConfirmModal}
on:confirm={deleteTrail}
></ConfirmModal>
<ListSelectModal
bind:openModal={openListSelectModal}
on:change={(e) => handleListSelection(e.detail)}
></ListSelectModal>
</div>
<style>

View File

@@ -1,6 +1,8 @@
import { lists_index } from "$lib/stores/list_store";
import { trails_show } from "$lib/stores/trail_store";
import type { Load } from "@sveltejs/kit";
export const load: Load = async ({ params }) => {
await trails_show(params.id!, true)
await lists_index();
};