This commit is contained in:
skidoodle 2024-03-13 00:30:45 +01:00
commit d761a10bf7
102 changed files with 4761 additions and 0 deletions

37
routes/+error.svelte Normal file
View file

@ -0,0 +1,37 @@
<script lang="ts">
import { browser } from '$app/environment';
import { page } from '$app/stores';
import Button from '$lib/components/dashboard/elements/Button.svelte';
import NoSuchUser from '$lib/components/screens/NoSuchUser.svelte';
$: loading = false;
$: (() => {
if (browser && window) window.onbeforeunload = null;
})();
</script>
<svelte:head>
<title>YourSitee</title>
</svelte:head>
<div class="flex justify-center items-center h-full p-8">
{#if $page.error?.code === 'NO_SUCH_USER'}
<NoSuchUser username={$page.url.pathname.split('/')[1]} />
{:else}
<div class="flex flex-col gap-4 w-[400px]">
<div>
<p class="text-[32px] font-bold">⚠️ An error occurred</p>
<p class="text-secondary">
{$page.data?.error ?? $page.error?.message ?? $page.error?.code ?? 'Unexpected error'}
</p>
</div>
<Button
{loading}
onClick={() => {
loading = true;
location.reload();
}}>Reload</Button>
</div>
{/if}
</div>

39
routes/+layout.svelte Normal file
View file

@ -0,0 +1,39 @@
<script lang="ts">
import 'inter-ui/inter.css';
import '../app.css';
import '../github-dark.css';
import '../github.css';
import Mount from '$lib/components/dashboard/modal/Mount.svelte';
import { modalStore } from '$lib/stores/modal';
import { browser } from '$app/environment';
import { type } from '$lib/models/modal';
import colors from '$lib/colors';
import { SvelteToast } from '@zerodevx/svelte-toast';
import type { SvelteToastOptions } from '@zerodevx/svelte-toast/stores.js';
const toastOptions: SvelteToastOptions = {
classes: ['toast-notification'],
};
$: modal = browser && $modalStore?.type != null ? type[$modalStore.type] : null;
const dismissModal = () => {
if ($modalStore?.visible && modal?.navbarMount !== true) $modalStore.hideModal();
};
</script>
<svelte:head>
<meta name="theme-color" content={colors.background} />
<meta property="og:type" content="website" />
<meta name="twitter:card" content="summary" />
</svelte:head>
<div class="w-full" on:mouseup={dismissModal} on:keyup={dismissModal}>
<slot />
</div>
<Mount {modal} />
<SvelteToast options={toastOptions} />

51
routes/+page.svelte Normal file
View file

@ -0,0 +1,51 @@
<script lang="ts">
import Avatar from '$lib/components/bio/profile/Avatar.svelte';
import Landing from '$lib/components/screens/Landing.svelte';
export let data;
$: title = 'The most advanced link-in-bio · YourSitee';
$: description =
'Feature-rich, customizable, modern, and user-friendly link-in-bio tool. Create your own sitee now — yoursit.ee!';
$: image = 'https://cdn.yoursit.ee/branding/logo_blue_background-sm.png';
</script>
<svelte:head>
<title>YourSitee</title>
<meta property="og:title" content={title} />
<meta name="twitter:title" content={title} />
<meta property="og:description" content={description} />
<meta name="twitter:description" content={description} />
<meta property="og:image" content={image} />
<meta name="twitter:image" content={image} />
</svelte:head>
<Landing bio={data.bio}>
<p class="text-[60px] 2xl:text-[100px] 3xl:text-[120px] font-medium leading-none max-w-[1330px]">
The most advanced <span class="whitespace-nowrap">link-in-bio</span>. Ever.
</p>
<p class="text-xl leading-[1.25] text-[#D1D6DB] max-w-[430px]">
YourSitee is a feature-rich, customizable, modern, and user-friendly link-in-bio tool.
</p>
<div class="flex gap-2 flex-col md:flex-row">
{#if data.session}
<a data-sveltekit-reload href="/dashboard" class="button primary flex gap-1"
><Avatar uniqueId={data.session.data.uniqueId} veryTiny={true} /> Continue to the Dashboard</a>
<a data-sveltekit-reload href="/dashboard/logout" class="button">Log out</a>
{:else}
<a data-sveltekit-reload href="/dashboard/register" class="button primary">Invited for Alpha? Sign up</a>
<a data-sveltekit-reload href="/dashboard/login" class="button">Log in</a>
{/if}
</div>
</Landing>
<style lang="postcss">
.button {
@apply bg-button-dark-transparent-fill hover:bg-button-dark-fill !text-text-primary font-medium rounded-2xl box-border px-4 py-2 w-fit h-fit;
}
.button.primary {
@apply bg-text-header hover:bg-text-primary !text-item;
}
</style>

View file

@ -0,0 +1,111 @@
<script lang="ts">
import { browser } from '$app/environment';
import { page } from '$app/stores';
import Navbar from '$lib/components/dashboard/Navbar.svelte';
import Showcase from '$lib/components/dashboard/auth/Showcase.svelte';
import ProfileContainerPlaceholder from '$lib/components/screens/ProfileContainerPlaceholder.svelte';
import { type } from '$lib/models/modal.js';
import { modalStore } from '$lib/stores/modal.js';
import Loading from '$lib/components/screens/Loading.svelte';
export let data;
$: auth =
$page.url.pathname.startsWith('/dashboard/login') ||
$page.url.pathname.startsWith('/dashboard/register') ||
$page.url.pathname.startsWith('/dashboard/logout');
$: modal = browser && $modalStore?.type != null ? type[$modalStore.type] : null;
$: isModal = $modalStore?.visible && modal?.navbarMount === true;
$: mouseDown = false;
const dismissModal = () => {
mouseDown = false;
if ($modalStore?.visible) $modalStore.hideModal();
};
</script>
{#if browser}
<div class="flex h-full">
{#if !auth}
<div class="w-full h-full">
<div class="h-full" on:mouseup={modal?.navbarMount ? undefined : dismissModal}>
<slot />
</div>
<div
class="navbar-container top-0 bottom-0 left-0 right-0 fixed z-40 flex flex-col justify-end items-center gap-4 p-6 overflow-hidden pointer-events-none select-none transition-all"
class:!p-0={$modalStore?.visible && modal?.navbarMount}
class:sm:!p-6={$modalStore?.visible && modal?.navbarMount}
class:!pointer-events-auto={$modalStore?.visible && modal?.navbarMount}
on:mousedown|self={() => (mouseDown = true)}
on:mouseup|self={() => (mouseDown ? dismissModal() : undefined)}
class:!gap-0={isModal}>
<Navbar sessionData={data.session?.data} />
</div>
</div>
{:else}
<div class="flex w-full h-full justify-center items-center">
<div
class="w-full h-full flex gap-6 xl:gap-[60px] 2xl:gap-[120px] pl-6 xl:pl-[60px] 2xl:pl-[120px] pr-6 items-center justify-center">
<div
class="flex-shrink-0 w-[400px] lg:max-h-screen px-1 py-6 overflow-x-hidden overflow-y-auto flex flex-col items-center gap-4 md:gap-6">
<a class="box-content p-4" href="/">
<div class="w-16 h-16 bg-white logo" title="YourSitee logo" />
</a>
<slot />
</div>
<div class="showcase w-full h-full max-w-[1920px] max-h-[1440px] hidden lg:block">
<Showcase />
</div>
</div>
</div>
{/if}
</div>
{:else}
{#if !auth}
<ProfileContainerPlaceholder uniqueId={data.session?.data.uniqueId} />
{:else}
<Loading />
{/if}
<noscript
><div class="absolute top-0 left-0 right-0 bottom-0 bg-dark-app-bg z-50 flex justify-center items-center">
<div class="p-8">
<p class="text-[32px] font-bold">⚠️ You don't have JavaScript enabled</p>
<p class="text-secondary">
Unfortunately, <b>JavaScript is required</b> for the dashboard. Enable it and try again.
</p>
</div>
</div></noscript>
{/if}
<style lang="postcss">
.navbar-container {
animation: pop-up 1.5s cubic-bezier(0.16, 1, 0.3, 1) 1;
transition: gap 0.2s linear;
}
@keyframes pop-up {
0% {
opacity: 0;
transform: translateY(3.25%);
}
50% {
opacity: 0;
transform: translateY(3.25%);
}
100% {
}
}
.logo {
mask-image: url(/assets/brand/icon.svg);
}
.showcase {
height: calc(100vh - 3rem);
}
</style>

View file

@ -0,0 +1 @@
export const ssr = true;

View file

@ -0,0 +1,42 @@
<script lang="ts">
import InputGroup from '$lib/components/dashboard/elements/InputGroup.svelte';
import TextInput from '$lib/components/dashboard/elements/TextInput.svelte';
import Panel from '$lib/components/dashboard/auth/Panel.svelte';
import constraints from '$lib/constraints';
export let data;
</script>
<svelte:head>
<title>YourSitee</title>
</svelte:head>
<Panel
title="Welcome back! Log in to continue"
links={[
{ url: 'register', text: 'Register' },
// { url: 'login/recovery', text: 'Forgot password?' },
]}
button="Sign In">
<InputGroup title="Username/Email address" required={true}>
<TextInput
name="identifier"
required
placeholder="Provide your username or email"
tabindex={1}
minlength={1}
maxlength={64}
value={data.username ?? data.email ?? ''} />
</InputGroup>
<InputGroup title="Password" required={true}>
<TextInput
name="password"
required
placeholder="Password"
type="password"
tabindex={2}
minlength={constraints.password.min}
maxlength={constraints.password.max} />
</InputGroup>
</Panel>

View file

@ -0,0 +1,8 @@
import type { PageLoad } from './$types';
export const load: PageLoad = async ({ url }) => {
return {
username: url.searchParams.get('un'),
email: url.searchParams.get('e'),
};
};

View file

@ -0,0 +1,30 @@
<script lang="ts">
import Button from '$lib/components/dashboard/elements/Button.svelte';
import InputGroup from '$lib/components/dashboard/elements/InputGroup.svelte';
import TextInput from '$lib/components/dashboard/elements/TextInput.svelte';
import Back from '$lib/components/dashboard/auth/Back.svelte';
import Panel from '$lib/components/dashboard/auth/Panel.svelte';
export let data;
</script>
<svelte:head>
<title>YourSitee</title>
</svelte:head>
<Back />
{#if !data.code}
<Panel
title="Forgot your password?"
subtitle="No problem at all! Let us send you an email with a recovery link to help you log into your account.">
<InputGroup title="Email address"><TextInput name="email" placeholder="you@example.com" required /></InputGroup>
<Button>Send recovery link</Button>
</Panel>
{:else}
<Panel title="Create a new password" action="?/new">
<InputGroup title="New password"><TextInput name="password" type="password" required /></InputGroup>
<InputGroup title="Confirm new password"><TextInput name="password-confirm" type="password" required /></InputGroup>
<Button>Set new password</Button>
</Panel>
{/if}

View file

@ -0,0 +1,62 @@
<script lang="ts">
import InputGroup from '$lib/components/dashboard/elements/InputGroup.svelte';
import TextInput from '$lib/components/dashboard/elements/TextInput.svelte';
import Panel from '$lib/components/dashboard/auth/Panel.svelte';
import Checkbox from '$lib/components/dashboard/elements/Checkbox.svelte';
import constraints from '$lib/constraints.js';
export let data;
</script>
<svelte:head>
<title>YourSitee</title>
</svelte:head>
<Panel
title="Join thousands of people already using YourSitee"
links={[
{ url: 'login', text: 'Login' },
// { url: 'login/recovery', text: 'Forgot password?' },
]}
button="Register">
<InputGroup title="Username" required={true}>
<TextInput
name="username"
required
placeholder="Pick a cool username"
minlength={constraints.username.min}
maxlength={constraints.username.max}
value={data.username ?? ''} />
</InputGroup>
<InputGroup title="Email address" required={true}>
<TextInput
name="email"
required
type="email"
placeholder="Provide your email"
minlength={constraints.email.min}
maxlength={constraints.email.max}
value={data.email ?? ''} />
</InputGroup>
<InputGroup title="Password" required={true}>
<TextInput
name="password"
required
placeholder="Password"
type="password"
minlength={constraints.password.min}
maxlength={constraints.password.max} />
<TextInput
name="password-confirm"
required
placeholder="Password again"
type="password"
minlength={constraints.password.min}
maxlength={constraints.password.max} />
</InputGroup>
<Checkbox name="accept" required>
By checking this box, I acknowledge that I have read and agree to abide by the <a href="/terms" target="_blank"
>Terms of Service</a>
and the <a href="/privacy" target="_blank">Privacy Policy</a>.</Checkbox>
</Panel>

View file

@ -0,0 +1,8 @@
import type { PageLoad } from './$types';
export const load: PageLoad = async ({ url }) => {
return {
username: url.searchParams.get('un'),
email: url.searchParams.get('e'),
};
};

View file

@ -0,0 +1,20 @@
<script lang="ts">
import Landing from '$lib/components/screens/Landing.svelte';
export let data;
</script>
<svelte:head>
<title>YourSitee</title>
</svelte:head>
<Landing dim={true}>
<div class="container mx-auto">
<p class="font-medium text-6xl mb-16">Privacy Policy</p>
<div class="markdown">
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
{@html data.text}
</div>
</div>
</Landing>

20
routes/terms/+page.svelte Normal file
View file

@ -0,0 +1,20 @@
<script lang="ts">
import Landing from '$lib/components/screens/Landing.svelte';
export let data;
</script>
<svelte:head>
<title>YourSitee</title>
</svelte:head>
<Landing dim={true}>
<div class="container mx-auto">
<p class="font-medium text-6xl mb-16">Terms of Service</p>
<div class="markdown">
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
{@html data.text}
</div>
</div>
</Landing>