Monorepo for Tangled
tangled.org
1<script lang="ts">
2 import { browser } from "$app/environment";
3 import { resolve } from "$app/paths";
4 import { page } from "$app/state";
5 import { getAuth } from "$lib/auth.svelte";
6 import Button from "$lib/components/ui/Button.svelte";
7 import CircleAlert from "$icon/circle-alert";
8 import { onMount } from "svelte";
9 import { SvelteURL } from "svelte/reactivity";
10
11 const appPath = (path: string) => resolve(path as "/");
12 const auth = getAuth();
13 const returnTo = $derived(page.url.searchParams.get("return_url") ?? "/");
14
15 let identifier = $state(page.url.searchParams.get("identifier") ?? "");
16 let isSubmitting = $state(false);
17
18 onMount(() => {
19 if (browser && location.hostname === "localhost") {
20 const url = new SvelteURL(location.href);
21 url.hostname = "127.0.0.1";
22 location.replace(url);
23 }
24 });
25
26 const submit = async (event: SubmitEvent) => {
27 event.preventDefault();
28 isSubmitting = true;
29 try {
30 await auth.signIn(identifier, returnTo);
31 } finally {
32 isSubmitting = false;
33 }
34 };
35</script>
36
37<svelte:head>
38 <title>Login · Tangled</title>
39</svelte:head>
40
41<section class="w-full max-w-md">
42 <div class="mb-4 flex flex-col items-center">
43 <a href={appPath("/")} class="flex items-center gap-2 no-underline hover:no-underline">
44 <img src="/logos/logotype.svg" alt="tangled" class="h-24 w-48 dark:invert" />
45 </a>
46 <p class="text-center text-xl text-foreground-default italic">tightly-knit social coding.</p>
47 </div>
48
49 <form class="mt-4" onsubmit={submit}>
50 <div class="flex flex-col">
51 <label for="identifier" class="py-2 text-sm text-foreground-default">Handle</label>
52 <input
53 id="identifier"
54 bind:value={identifier}
55 disabled={isSubmitting}
56 type="text"
57 autocomplete="username"
58 autocapitalize="none"
59 spellcheck="false"
60 inputmode="url"
61 required
62 placeholder="akshay.tngl.sh"
63 class="w-full rounded border border-border-default bg-background-default p-3 text-lg text-foreground-default placeholder:text-foreground-placeholder focus:ring-1 focus:ring-border-strong focus:outline-none md:text-base"
64 />
65 <p class="mt-1 text-sm text-foreground-subtle">
66 Use your <span class="text-foreground-default">AT Protocol</span> handle to log in. If
67 you're unsure, this is likely your Tangled
68 <code class="rounded bg-background-inset px-1 font-mono text-xs text-foreground-default"
69 >(.tngl.sh)</code
70 >
71 or Bluesky
72 <code class="rounded bg-background-inset px-1 font-mono text-xs text-foreground-default"
73 >(.bsky.social)</code
74 >
75 account.
76 </p>
77 </div>
78
79 <Button
80 type="submit"
81 size="sm"
82 disabled={isSubmitting}
83 loading={isSubmitting}
84 spinnerClass="size-4"
85 aria-label={isSubmitting ? "Logging in" : undefined}
86 class="my-2 mt-6 w-full text-base disabled:opacity-100"
87 >
88 Login
89 </Button>
90 </form>
91
92 <p class="text-sm text-foreground-subtle">
93 Don't have an account? <a href={appPath("/signup")} class="text-foreground-default underline"
94 >Create an account</a
95 >
96 on Tangled now!
97 </p>
98
99 {#if auth.error}
100 <div
101 class="my-2 flex gap-2 rounded border border-border-danger bg-background-danger-subtle px-3 py-2 text-foreground-danger drop-shadow-sm"
102 >
103 <span class="py-1" aria-hidden="true"><CircleAlert class="size-4" /></span>
104 <div>
105 <h5 class="font-medium">Login error</h5>
106 <p class="text-sm">{auth.error} Please try again.</p>
107 </div>
108 </div>
109 {/if}
110</section>