Skip to content

Commit b6c2e78

Browse files
authored
Merge branch 'main' into feat/skills-modal-tabs
2 parents b27e2e4 + a18e655 commit b6c2e78

37 files changed

Lines changed: 1298 additions & 179 deletions

.env.example

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#secure password, can use openssl rand --hex 32
2+
NUXT_SESSION_PASSWORD=""

app/components/AppHeader.vue

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,9 @@
22
withDefaults(
33
defineProps<{
44
showLogo?: boolean
5-
showConnector?: boolean
65
}>(),
76
{
87
showLogo: true,
9-
showConnector: true,
108
},
119
)
1210
@@ -98,9 +96,7 @@ onKeyStroke(',', e => {
9896
</kbd>
9997
</NuxtLink>
10098

101-
<div v-if="showConnector" class="hidden sm:block">
102-
<ConnectorStatus />
103-
</div>
99+
<HeaderAccountMenu />
104100
</div>
105101
</nav>
106102
</header>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<script setup lang="ts">
2+
const showModal = ref(false)
3+
const { user } = useAtproto()
4+
</script>
5+
6+
<template>
7+
<div class="relative">
8+
<button
9+
type="button"
10+
class="relative font-mono text-sm flex items-center justify-center w-fit rounded-md transition-colors duration-200 hover:bg-bg-subtle focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50"
11+
@click="showModal = true"
12+
>
13+
{{ user?.handle || 'login' }}
14+
</button>
15+
16+
<AuthModal v-model:open="showModal" />
17+
</div>
18+
</template>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<template>
2+
<div class="relative">
3+
<button
4+
type="button"
5+
class="relative font-mono text-sm flex items-center justify-center w-fit rounded-md transition-colors duration-200 hover:bg-bg-subtle focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50"
6+
>
7+
login
8+
</button>
9+
</div>
10+
</template>

app/components/AuthButton.vue

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<script setup lang="ts">
2+
const showModal = ref(false)
3+
const { user } = useAtproto()
4+
</script>
5+
6+
<template>
7+
<div class="relative">
8+
<button
9+
type="button"
10+
class="relative font-mono text-sm flex items-center justify-center w-fit rounded-md transition-colors duration-200 hover:bg-bg-subtle focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50"
11+
@click="showModal = true"
12+
>
13+
{{ user?.handle || 'login' }}
14+
</button>
15+
16+
<AuthModal v-model:open="showModal" />
17+
</div>
18+
</template>

app/components/AuthModal.vue

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
<script setup lang="ts">
2+
const open = defineModel<boolean>('open', { default: false })
3+
4+
const handleInput = ref('')
5+
6+
const { user, logout } = useAtproto()
7+
8+
async function handleBlueskySignIn() {
9+
await navigateTo(
10+
{
11+
path: '/api/auth/atproto',
12+
query: { handle: 'https://bsky.social' },
13+
},
14+
{ external: true },
15+
)
16+
}
17+
18+
async function handleCreateAccount() {
19+
await navigateTo(
20+
{
21+
path: '/api/auth/atproto',
22+
query: { handle: 'https://npmx.social', create: 'true' },
23+
},
24+
{ external: true },
25+
)
26+
}
27+
28+
async function handleLogin() {
29+
if (handleInput.value) {
30+
await navigateTo(
31+
{
32+
path: '/api/auth/atproto',
33+
query: { handle: handleInput.value },
34+
},
35+
{ external: true },
36+
)
37+
}
38+
}
39+
</script>
40+
41+
<template>
42+
<Teleport to="body">
43+
<Transition
44+
enter-active-class="transition-opacity duration-200"
45+
leave-active-class="transition-opacity duration-200"
46+
enter-from-class="opacity-0"
47+
leave-to-class="opacity-0"
48+
>
49+
<div v-if="open" class="fixed inset-0 z-50 flex items-center justify-center p-4">
50+
<!-- Backdrop -->
51+
<button
52+
type="button"
53+
class="absolute inset-0 bg-black/60 cursor-default"
54+
:aria-label="$t('auth.modal.close')"
55+
@click="open = false"
56+
/>
57+
58+
<!-- Modal -->
59+
<div
60+
class="relative w-full max-w-lg bg-bg border border-border rounded-lg shadow-xl max-h-[90vh] overflow-y-auto overscroll-contain"
61+
role="dialog"
62+
aria-modal="true"
63+
aria-labelledby="auth-modal-title"
64+
>
65+
<div class="p-6">
66+
<div class="flex items-center justify-between mb-6">
67+
<h2 id="auth-modal-title" class="font-mono text-lg font-medium">
68+
{{ $t('auth.modal.title') }}
69+
</h2>
70+
<button
71+
type="button"
72+
class="text-fg-subtle hover:text-fg transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50 rounded"
73+
:aria-label="$t('common.close')"
74+
@click="open = false"
75+
>
76+
<span class="i-carbon-close block w-5 h-5" aria-hidden="true" />
77+
</button>
78+
</div>
79+
80+
<div v-if="user?.handle" class="space-y-4">
81+
<div class="flex items-center gap-3 p-4 bg-bg-subtle border border-border rounded-lg">
82+
<span class="w-3 h-3 rounded-full bg-green-500" aria-hidden="true" />
83+
<div>
84+
<p class="font-mono text-xs text-fg-muted">
85+
{{ $t('auth.modal.connected_as', { handle: user.handle }) }}
86+
</p>
87+
</div>
88+
</div>
89+
<button
90+
class="w-full px-4 py-2 font-mono text-sm text-fg-muted bg-bg-subtle border border-border rounded-md transition-colors duration-200 hover:text-fg hover:border-border-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50"
91+
@click="logout"
92+
>
93+
{{ $t('auth.modal.disconnect') }}
94+
</button>
95+
</div>
96+
97+
<!-- Disconnected state -->
98+
<form v-else class="space-y-4" @submit.prevent="handleLogin">
99+
<p class="text-sm text-fg-muted">{{ $t('auth.modal.connect_prompt') }}</p>
100+
101+
<div class="space-y-3">
102+
<div>
103+
<label
104+
for="handle-input"
105+
class="block text-xs text-fg-subtle uppercase tracking-wider mb-1.5"
106+
>
107+
{{ $t('auth.modal.handle_label') }}
108+
</label>
109+
<input
110+
id="handle-input"
111+
v-model="handleInput"
112+
type="text"
113+
name="handle"
114+
:placeholder="$t('auth.modal.handle_placeholder')"
115+
autocomplete="off"
116+
spellcheck="false"
117+
class="w-full px-3 py-2 font-mono text-sm bg-bg-subtle border border-border rounded-md text-fg placeholder:text-fg-subtle transition-colors duration-200 focus:border-border-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50"
118+
/>
119+
</div>
120+
121+
<details class="text-sm">
122+
<summary
123+
class="text-fg-subtle cursor-pointer hover:text-fg-muted transition-colors duration-200"
124+
>
125+
{{ $t('auth.modal.what_is_atmosphere') }}
126+
</summary>
127+
<div class="mt-3">
128+
<i18n-t keypath="auth.modal.atmosphere_explanation" tag="p">
129+
<template #npmx>
130+
<span class="font-bold">npmx.dev</span>
131+
</template>
132+
<template #atproto>
133+
<a
134+
href="https://atproto.com"
135+
target="_blank"
136+
class="text-blue-400 hover:underline"
137+
>
138+
AT Protocol
139+
</a>
140+
</template>
141+
<template #bluesky>
142+
<a
143+
href="https://bsky.app"
144+
target="_blank"
145+
class="text-blue-400 hover:underline"
146+
>
147+
Bluesky
148+
</a>
149+
</template>
150+
<template #tangled>
151+
<a
152+
href="https://tangled.org"
153+
target="_blank"
154+
class="text-blue-400 hover:underline"
155+
>
156+
Tangled
157+
</a>
158+
</template>
159+
</i18n-t>
160+
</div>
161+
</details>
162+
</div>
163+
164+
<button
165+
type="submit"
166+
:disabled="!handleInput.trim()"
167+
class="w-full px-4 py-2 font-mono text-sm text-bg bg-fg rounded-md transition-all duration-200 hover:bg-fg/90 disabled:opacity-50 disabled:cursor-not-allowed focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50 focus-visible:ring-offset-2 focus-visible:ring-offset-bg"
168+
>
169+
{{ $t('auth.modal.connect') }}
170+
</button>
171+
<button
172+
type="button"
173+
class="w-full px-4 py-2 font-mono text-sm text-bg bg-fg rounded-md transition-all duration-200 hover:bg-fg/90 disabled:opacity-50 disabled:cursor-not-allowed focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50 focus-visible:ring-offset-2 focus-visible:ring-offset-bg"
174+
@click="handleCreateAccount"
175+
>
176+
{{ $t('auth.modal.create_account') }}
177+
</button>
178+
<hr />
179+
<button
180+
type="button"
181+
class="w-full px-4 py-2 font-mono text-sm text-bg bg-fg rounded-md transition-all duration-200 hover:bg-fg/90 disabled:opacity-50 disabled:cursor-not-allowed focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50 focus-visible:ring-offset-2 focus-visible:ring-offset-bg flex items-center justify-center gap-2"
182+
@click="handleBlueskySignIn"
183+
>
184+
{{ $t('auth.modal.connect_bluesky') }}
185+
<svg fill="none" viewBox="0 0 64 57" width="20" style="width: 20px">
186+
<path
187+
fill="#0F73FF"
188+
d="M13.873 3.805C21.21 9.332 29.103 20.537 32 26.55v15.882c0-.338-.13.044-.41.867-1.512 4.456-7.418 21.847-20.923 7.944-7.111-7.32-3.819-14.64 9.125-16.85-7.405 1.264-15.73-.825-18.014-9.015C1.12 23.022 0 8.51 0 6.55 0-3.268 8.579-.182 13.873 3.805ZM50.127 3.805C42.79 9.332 34.897 20.537 32 26.55v15.882c0-.338.13.044.41.867 1.512 4.456 7.418 21.847 20.923 7.944 7.111-7.32 3.819-14.64-9.125-16.85 7.405 1.264 15.73-.825 18.014-9.015C62.88 23.022 64 8.51 64 6.55c0-9.818-8.578-6.732-13.873-2.745Z"
189+
></path>
190+
</svg>
191+
</button>
192+
</form>
193+
</div>
194+
</div>
195+
</div>
196+
</Transition>
197+
</Teleport>
198+
</template>

app/components/ConnectorModal.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ watch(open, isOpen => {
9393
<div>
9494
<p class="font-mono text-sm text-fg">{{ $t('connector.modal.connected') }}</p>
9595
<p v-if="npmUser" class="font-mono text-xs text-fg-muted">
96-
{{ $t('connector.modal.logged_in_as', { user: npmUser }) }}
96+
{{ $t('connector.modal.connected_as_user', { user: npmUser }) }}
9797
</p>
9898
</div>
9999
</div>

app/components/ConnectorStatus.client.vue

Lines changed: 0 additions & 104 deletions
This file was deleted.

app/components/ConnectorStatus.server.vue

Lines changed: 0 additions & 5 deletions
This file was deleted.

0 commit comments

Comments
 (0)