Add rudimentary routes
This commit is contained in:
parent
8c3198e4b0
commit
ff64bc1562
11 changed files with 234 additions and 14 deletions
|
|
@ -1,14 +1,26 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import favicon from '$lib/assets/favicon.svg'
|
import favicon from '$lib/assets/favicon.svg'
|
||||||
import 'modern-normalize/modern-normalize.css'
|
import 'modern-normalize/modern-normalize.css'
|
||||||
|
import '$src/app.scss'
|
||||||
|
|
||||||
export const prerender = false
|
import Navigation from '$lib/components/Navigation.svelte'
|
||||||
|
|
||||||
let { children } = $props()
|
// Get `data` and `children` from the router via $props()
|
||||||
|
const { data, children } = $props<{
|
||||||
|
data: {
|
||||||
|
isAuthenticated: boolean
|
||||||
|
account: { username: string; userId: string; role: number } | null
|
||||||
|
currentUser: unknown | null
|
||||||
|
}
|
||||||
|
children: () => any
|
||||||
|
}>()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
<link rel="icon" href={favicon} />
|
<link rel="icon" href={favicon} />
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
{@render children?.()}
|
<main>
|
||||||
|
<Navigation isAuthenticated={data?.isAuthenticated} username={data?.account?.username} />
|
||||||
|
{@render children?.()}
|
||||||
|
</main>
|
||||||
|
|
|
||||||
27
src/routes/[username]/+page.server.ts
Normal file
27
src/routes/[username]/+page.server.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
import type { PageServerLoad } from './$types'
|
||||||
|
import { error } from '@sveltejs/kit'
|
||||||
|
import { profile } from '$lib/api/resources/users'
|
||||||
|
import { parseParty } from '$lib/api/schemas/party'
|
||||||
|
import * as partiesApi from '$lib/api/resources/parties'
|
||||||
|
|
||||||
|
export const load: PageServerLoad = async ({ fetch, params, url, depends, locals }) => {
|
||||||
|
depends('app:profile')
|
||||||
|
const username = params.username
|
||||||
|
const pageParam = url.searchParams.get('page')
|
||||||
|
const page = pageParam ? Math.max(1, parseInt(pageParam, 10) || 1) : 1
|
||||||
|
const tab = url.searchParams.get('tab') === 'favorites' ? 'favorites' : 'teams'
|
||||||
|
const isOwner = locals.session?.account?.username === username
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (tab === 'favorites' && isOwner) {
|
||||||
|
const fav = await partiesApi.favorites(fetch as any, { page })
|
||||||
|
return { user: { username } as any, items: fav.items, page: fav.page, total: fav.total, totalPages: fav.totalPages, perPage: fav.perPage, tab, isOwner }
|
||||||
|
}
|
||||||
|
|
||||||
|
const { user, items, total, totalPages, perPage } = await profile(fetch as any, username, page)
|
||||||
|
const parties = items.map((p) => parseParty(p))
|
||||||
|
return { user, items: parties, page, total, totalPages, perPage, tab, isOwner }
|
||||||
|
} catch (e: any) {
|
||||||
|
throw error(e?.status || 502, e?.message || 'Failed to load profile')
|
||||||
|
}
|
||||||
|
}
|
||||||
65
src/routes/[username]/+page.svelte
Normal file
65
src/routes/[username]/+page.svelte
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { PageData } from './$types'
|
||||||
|
import ExploreGrid from '$lib/components/explore/ExploreGrid.svelte'
|
||||||
|
|
||||||
|
const { data } = $props() as { data: PageData }
|
||||||
|
const page = data.page || 1
|
||||||
|
const totalPages = data.totalPages || undefined
|
||||||
|
const tab = data.tab || 'teams'
|
||||||
|
const isOwner = data.isOwner || false
|
||||||
|
|
||||||
|
const avatarFile = data.user?.avatar?.picture || ''
|
||||||
|
function ensurePng(name: string) {
|
||||||
|
return /\.png$/i.test(name) ? name : `${name}.png`
|
||||||
|
}
|
||||||
|
function to2x(name: string) {
|
||||||
|
return /\.png$/i.test(name) ? name.replace(/\.png$/i, '@2x.png') : `${name}@2x.png`
|
||||||
|
}
|
||||||
|
const avatarSrc = avatarFile ? `/profile/${ensurePng(avatarFile)}` : ''
|
||||||
|
const avatarSrcSet = avatarFile ? `${avatarSrc} 1x, /profile/${to2x(avatarFile)} 2x` : ''
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<section class="profile">
|
||||||
|
<header class="header">
|
||||||
|
{#if data.user?.avatar?.picture}
|
||||||
|
<img class="avatar" alt={`Avatar of ${data.user.username}`} src={avatarSrc} srcset={avatarSrcSet} width="64" height="64" />
|
||||||
|
{:else}
|
||||||
|
<div class="avatar" aria-hidden="true"></div>
|
||||||
|
{/if}
|
||||||
|
<div>
|
||||||
|
<h1>@{data.user.username}</h1>
|
||||||
|
<nav class="tabs" aria-label="Profile sections">
|
||||||
|
<a class:active={tab==='teams'} href="?tab=teams" data-sveltekit-preload-data="hover">Teams</a>
|
||||||
|
{#if isOwner}
|
||||||
|
<a class:active={tab==='favorites'} href="?tab=favorites" data-sveltekit-preload-data="hover">Favorites</a>
|
||||||
|
{/if}
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<ExploreGrid items={data.items} />
|
||||||
|
|
||||||
|
<nav class="pagination" aria-label="Pagination">
|
||||||
|
{#if page > 1}
|
||||||
|
<a rel="prev" href={`?page=${page - 1}`} data-sveltekit-preload-data="hover">Previous</a>
|
||||||
|
{/if}
|
||||||
|
{#if totalPages && page < totalPages}
|
||||||
|
<a rel="next" href={`?page=${page + 1}`} data-sveltekit-preload-data="hover">Next</a>
|
||||||
|
{/if}
|
||||||
|
</nav>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@use '$src/themes/spacing' as *;
|
||||||
|
@use '$src/themes/colors' as *;
|
||||||
|
|
||||||
|
.profile { padding: $unit-2x 0; }
|
||||||
|
.header { display: flex; align-items: center; gap: $unit-2x; margin-bottom: $unit-2x; }
|
||||||
|
.avatar { width: 64px; height: 64px; border-radius: 50%; background: $grey-80; border: 1px solid $grey-75; object-fit: cover; }
|
||||||
|
.sub { color: $grey-55; margin: 0; }
|
||||||
|
.tabs { display: flex; gap: $unit-2x; margin-top: $unit-half; }
|
||||||
|
.tabs a { text-decoration: none; color: inherit; padding-bottom: 2px; border-bottom: 2px solid transparent; }
|
||||||
|
.tabs a.active { border-color: #3366ff; color: #3366ff; }
|
||||||
|
.pagination { display: flex; gap: $unit-2x; padding: $unit-2x 0; }
|
||||||
|
.pagination a { text-decoration: none; }
|
||||||
|
</style>
|
||||||
0
src/routes/collection/+page.svelte
Normal file
0
src/routes/collection/+page.svelte
Normal file
|
|
@ -1,15 +1,9 @@
|
||||||
import type { PageServerLoad } from './$types'
|
import type { PageServerLoad } from './$types'
|
||||||
import { redirect } from '@sveltejs/kit'
|
import { redirect } from '@sveltejs/kit'
|
||||||
|
|
||||||
export const load: PageServerLoad = async ({ parent }) => {
|
export const load: PageServerLoad = async ({ locals }) => {
|
||||||
const { isAuthenticated, account, currentUser } = await parent()
|
const username = locals.session?.account?.username
|
||||||
|
if (!username) throw redirect(302, '/auth/login')
|
||||||
if (!isAuthenticated) {
|
throw redirect(302, `/${encodeURIComponent(username)}`)
|
||||||
throw redirect(302, '/login?next=/me')
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
account,
|
|
||||||
user: currentUser
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
21
src/routes/teams/[shortcode]/+error.svelte
Normal file
21
src/routes/teams/[shortcode]/+error.svelte
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
<script lang="ts">
|
||||||
|
export let error: Error & { status?: number, message?: string }
|
||||||
|
export let status: number
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h1>{status === 404 ? 'Team Not Found' : 'Something went wrong'}</h1>
|
||||||
|
<p>
|
||||||
|
{status === 404
|
||||||
|
? 'We could not find a team with that code.'
|
||||||
|
: (error?.message || 'Please try again later.')}
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
section { padding: 1.5rem; }
|
||||||
|
h1 { margin: 0 0 0.5rem 0; font-size: 1.25rem; }
|
||||||
|
p { margin: 0; color: #555; }
|
||||||
|
:global(main) { display: block; }
|
||||||
|
</style>
|
||||||
|
|
||||||
34
src/routes/teams/[shortcode]/+page.server.ts
Normal file
34
src/routes/teams/[shortcode]/+page.server.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
import type { PageServerLoad } from './$types'
|
||||||
|
import { error } from '@sveltejs/kit'
|
||||||
|
import { PartyService } from '$lib/services/party.service'
|
||||||
|
|
||||||
|
export const load: PageServerLoad = async ({ fetch, params, parent }) => {
|
||||||
|
const { shortcode } = params
|
||||||
|
const partyService = new PartyService(fetch)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const party = await partyService.getByShortcode(shortcode)
|
||||||
|
|
||||||
|
const parentData = await parent()
|
||||||
|
const authUserId = (parentData as any)?.user?.id
|
||||||
|
|
||||||
|
const canEditServer = partyService.computeEditability(
|
||||||
|
party,
|
||||||
|
authUserId,
|
||||||
|
undefined,
|
||||||
|
undefined
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
party,
|
||||||
|
canEditServer: canEditServer.canEdit,
|
||||||
|
authUserId
|
||||||
|
}
|
||||||
|
} catch (err: any) {
|
||||||
|
console.error('Error loading party:', err)
|
||||||
|
if (err?.issues) console.error('Validation errors:', err.issues)
|
||||||
|
if (err.status === 404) throw error(404, 'Team not found')
|
||||||
|
throw error(err.status || 500, err.message || 'Failed to load team')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
13
src/routes/teams/[shortcode]/+page.svelte
Normal file
13
src/routes/teams/[shortcode]/+page.svelte
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import Party from '$lib/components/party/Party.svelte'
|
||||||
|
import type { PageData } from './$types'
|
||||||
|
|
||||||
|
export let data: PageData
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Party
|
||||||
|
initial={data.party}
|
||||||
|
canEditServer={data.canEditServer}
|
||||||
|
authUserId={data.authUserId}
|
||||||
|
/>
|
||||||
|
|
||||||
18
src/routes/teams/explore/+page.server.ts
Normal file
18
src/routes/teams/explore/+page.server.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
import type { PageServerLoad } from './$types'
|
||||||
|
import { error } from '@sveltejs/kit'
|
||||||
|
import * as parties from '$lib/api/resources/parties'
|
||||||
|
|
||||||
|
export const load: PageServerLoad = async ({ fetch, url, depends }) => {
|
||||||
|
depends('app:parties:list')
|
||||||
|
|
||||||
|
const pageParam = url.searchParams.get('page')
|
||||||
|
const page = pageParam ? Math.max(1, parseInt(pageParam, 10) || 1) : 1
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { items, total, totalPages, perPage } = await parties.list(fetch, { page })
|
||||||
|
return { items, page, total, totalPages, perPage }
|
||||||
|
} catch (e: any) {
|
||||||
|
throw error(e?.status || 502, e?.message || 'Failed to load teams')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
35
src/routes/teams/explore/+page.svelte
Normal file
35
src/routes/teams/explore/+page.svelte
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { PageData } from './$types'
|
||||||
|
import ExploreGrid from '$lib/components/explore/ExploreGrid.svelte'
|
||||||
|
|
||||||
|
const { data } = $props() as { data: PageData }
|
||||||
|
|
||||||
|
const page = data.page || 1
|
||||||
|
const totalPages = data.totalPages || undefined
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<section class="explore">
|
||||||
|
<header>
|
||||||
|
<h1>Explore Teams</h1>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<ExploreGrid items={data.items} />
|
||||||
|
|
||||||
|
<nav class="pagination" aria-label="Pagination">
|
||||||
|
{#if page > 1}
|
||||||
|
<a rel="prev" href={`?page=${page - 1}`} data-sveltekit-preload-data="hover">Previous</a>
|
||||||
|
{/if}
|
||||||
|
{#if totalPages && page < totalPages}
|
||||||
|
<a rel="next" href={`?page=${page + 1}`} data-sveltekit-preload-data="hover">Next</a>
|
||||||
|
{/if}
|
||||||
|
</nav>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@use '$src/themes/spacing' as *;
|
||||||
|
|
||||||
|
.explore { padding: $unit-2x 0; }
|
||||||
|
h1 { margin: 0 0 $unit-2x 0; }
|
||||||
|
.pagination { display: flex; gap: $unit-2x; padding: $unit-2x 0; }
|
||||||
|
.pagination a { text-decoration: none; }
|
||||||
|
</style>
|
||||||
1
src/routes/teams/new/+page.svelte
Normal file
1
src/routes/teams/new/+page.svelte
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svelte:options runes={true} />
|
||||||
Loading…
Reference in a new issue