tangled
alpha
login
or
join now
slipnote.app
/
slipnote
3
fork
atom
Built for people who think better out loud.
3
fork
atom
overview
issues
pulls
pipelines
frontend: add OAuth Storybook demo
isaaccorbrey.com
1 month ago
e9efc005
c81eb949
verified
This commit was signed with the committer's
known signature
.
isaaccorbrey.com
SSH Key Fingerprint:
SHA256:mwogCTZEXIXrYk4l7PaavTNPxe1Xqjf5jMIBe0LvAHU=
+141
-1
3 changed files
expand all
collapse all
unified
split
frontend
src
components
Input.svelte
OAuthDemo.stories.svelte
OAuthDemo.svelte
+2
-1
frontend/src/components/Input.svelte
reviewed
···
1
1
<script lang="ts">
2
2
import type { HTMLInputAttributes } from "svelte/elements";
3
3
-
let { class: className = "", ...props }: HTMLInputAttributes = $props();
3
3
+
let { class: className = "", value = $bindable(""), ...props }: HTMLInputAttributes = $props();
4
4
</script>
5
5
6
6
<input
7
7
{...props}
8
8
+
bind:value
8
9
class="
9
10
px-3 py-px font-base bg-slate-950/7
10
11
border-s-slate-950 rounded-md
+23
frontend/src/components/OAuthDemo.stories.svelte
reviewed
···
1
1
+
<script module>
2
2
+
import { defineMeta } from "@storybook/addon-svelte-csf";
3
3
+
import OAuthDemo from "./OAuthDemo.svelte";
4
4
+
5
5
+
const { Story } = defineMeta({
6
6
+
title: "Experiments/OAuth Demo",
7
7
+
component: OAuthDemo,
8
8
+
argTypes: {
9
9
+
backendBase: { control: "text" },
10
10
+
subject: { control: "text" },
11
11
+
openInNewTab: { control: "boolean" },
12
12
+
},
13
13
+
});
14
14
+
</script>
15
15
+
16
16
+
<Story
17
17
+
name="OAuth Demo"
18
18
+
args={{
19
19
+
backendBase: "http://localhost:3001",
20
20
+
subject: "",
21
21
+
openInNewTab: true,
22
22
+
}}
23
23
+
/>
+116
frontend/src/components/OAuthDemo.svelte
reviewed
···
1
1
+
<script lang="ts">
2
2
+
import Button from "./Button.svelte";
3
3
+
import Input from "./Input.svelte";
4
4
+
5
5
+
let {
6
6
+
backendBase = $bindable("http://localhost:3001"),
7
7
+
subject = $bindable(""),
8
8
+
openInNewTab = $bindable(true),
9
9
+
} = $props();
10
10
+
11
11
+
let error = $state("");
12
12
+
13
13
+
function normalizeBase(value: string) {
14
14
+
return value.trim().replace(/\/+$/, "");
15
15
+
}
16
16
+
17
17
+
function buildStartUrl(base: string, subjectValue: string) {
18
18
+
const normalized = normalizeBase(base);
19
19
+
const trimmedSubject = subjectValue.trim();
20
20
+
if (!normalized || !trimmedSubject) {
21
21
+
return "";
22
22
+
}
23
23
+
return `${normalized}/api/auth/atproto/start?subject=${encodeURIComponent(trimmedSubject)}`;
24
24
+
}
25
25
+
26
26
+
const startUrl = $derived(buildStartUrl(backendBase, subject));
27
27
+
28
28
+
function beginOAuth() {
29
29
+
if (!subject.trim()) {
30
30
+
error = "Enter a handle or DID before starting OAuth.";
31
31
+
return;
32
32
+
}
33
33
+
error = "";
34
34
+
if (!startUrl) {
35
35
+
error = "Start URL could not be built. Check the backend base URL.";
36
36
+
return;
37
37
+
}
38
38
+
if (openInNewTab) {
39
39
+
window.open(startUrl, "_blank", "noopener,noreferrer");
40
40
+
} else {
41
41
+
window.location.assign(startUrl);
42
42
+
}
43
43
+
}
44
44
+
</script>
45
45
+
46
46
+
<div
47
47
+
class="
48
48
+
max-w-2xl rounded-xl border border-slate-900/15
49
49
+
bg-[radial-gradient(circle_at_top,#f8fafc,transparent)]
50
50
+
p-6 shadow-[0_10px_30px_rgba(15,23,42,0.12)]
51
51
+
"
52
52
+
>
53
53
+
<div class="flex flex-col gap-2">
54
54
+
<p class="text-sm uppercase tracking-[0.18em] text-slate-600">
55
55
+
ATProto OAuth Demo
56
56
+
</p>
57
57
+
<h2 class="font-display text-2xl text-slate-900">
58
58
+
Log in to your PDS
59
59
+
</h2>
60
60
+
<p class="text-sm text-slate-600">
61
61
+
This demo hits your backend OAuth endpoints and redirects you to your
62
62
+
authorization server. Make sure the backend is running with the OAuth
63
63
+
env vars configured for your PDS.
64
64
+
</p>
65
65
+
</div>
66
66
+
67
67
+
<div class="mt-6 grid gap-4">
68
68
+
<label class="grid gap-2 text-sm text-slate-700">
69
69
+
Backend base URL
70
70
+
<Input bind:value={backendBase} placeholder="http://localhost:3001" />
71
71
+
</label>
72
72
+
73
73
+
<label class="grid gap-2 text-sm text-slate-700">
74
74
+
Handle or DID
75
75
+
<Input bind:value={subject} placeholder="alice.example.com or did:plc:..." />
76
76
+
</label>
77
77
+
78
78
+
<label class="flex items-center gap-3 text-sm text-slate-700">
79
79
+
<input
80
80
+
type="checkbox"
81
81
+
class="h-4 w-4 rounded border-slate-400 text-blue-600"
82
82
+
bind:checked={openInNewTab}
83
83
+
/>
84
84
+
Open OAuth in a new tab
85
85
+
</label>
86
86
+
87
87
+
<div class="flex flex-wrap gap-3">
88
88
+
<Button type="button" onclick={beginOAuth}>
89
89
+
Start OAuth
90
90
+
</Button>
91
91
+
{#if startUrl}
92
92
+
<a
93
93
+
class="text-sm text-slate-700 underline decoration-slate-400 underline-offset-4"
94
94
+
href={startUrl}
95
95
+
target="_blank"
96
96
+
rel="noopener noreferrer"
97
97
+
>
98
98
+
Open start URL
99
99
+
</a>
100
100
+
{/if}
101
101
+
</div>
102
102
+
103
103
+
{#if error}
104
104
+
<p class="text-sm text-red-600">{error}</p>
105
105
+
{/if}
106
106
+
107
107
+
{#if startUrl}
108
108
+
<div class="rounded-lg bg-white/70 p-3 text-xs text-slate-600">
109
109
+
<div class="uppercase tracking-[0.2em] text-[10px] text-slate-500">
110
110
+
Start URL
111
111
+
</div>
112
112
+
<div class="mt-1 break-all font-mono">{startUrl}</div>
113
113
+
</div>
114
114
+
{/if}
115
115
+
</div>
116
116
+
</div>