Skip to content

Commit fde38d0

Browse files
committed
refactor: define ids for header dropdown modals, switch ConnectorModal to client component
1 parent 09fd230 commit fde38d0

6 files changed

Lines changed: 226 additions & 267 deletions

File tree

app/app.vue

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,6 @@ if (import.meta.client) {
9393

9494
<ScrollToTop />
9595
</div>
96-
97-
<!-- <ConnectorModal /> -->
98-
<AuthModal />
9996
</template>
10097

10198
<style>

app/components/AuthModal.client.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ async function handleLogin() {
3838

3939
<template>
4040
<!-- Modal -->
41-
<Modal :modalTitle="$t('auth.modal.title')" class="max-w-lg">
41+
<Modal :modalTitle="$t('auth.modal.title')" class="max-w-lg" id="auth-modal">
4242
<div v-if="user?.handle" class="space-y-4">
4343
<div class="flex items-center gap-3 p-4 bg-bg-subtle border border-border rounded-lg">
4444
<span class="w-3 h-3 rounded-full bg-green-500" aria-hidden="true" />
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
<script setup lang="ts">
2+
const open = defineModel<boolean>('open', { default: false })
3+
4+
const { isConnected, isConnecting, npmUser, error, hasOperations, connect, disconnect } =
5+
useConnector()
6+
7+
const tokenInput = shallowRef('')
8+
const portInput = shallowRef('31415')
9+
const { copied, copy } = useClipboard({ copiedDuring: 2000 })
10+
11+
async function handleConnect() {
12+
const port = Number.parseInt(portInput.value, 10) || 31415
13+
const success = await connect(tokenInput.value.trim(), port)
14+
if (success) {
15+
tokenInput.value = ''
16+
open.value = false
17+
}
18+
}
19+
20+
function handleDisconnect() {
21+
disconnect()
22+
}
23+
24+
function copyCommand() {
25+
let command = executeNpmxConnectorCommand.value
26+
if (portInput.value !== '31415') {
27+
command += ` --port ${portInput.value}`
28+
}
29+
copy(command)
30+
}
31+
32+
const selectedPM = useSelectedPackageManager()
33+
34+
const executeNpmxConnectorCommand = computed(() => {
35+
return getExecuteCommand({
36+
packageName: 'npmx-connector',
37+
packageManager: selectedPM.value,
38+
})
39+
})
40+
41+
// Reset form when modal opens
42+
watch(open, isOpen => {
43+
if (isOpen) {
44+
tokenInput.value = ''
45+
}
46+
})
47+
</script>
48+
49+
<template>
50+
<Modal
51+
:modalTitle="$t('connector.modal.title')"
52+
:class="isConnected && hasOperations ? 'max-w-2xl' : 'max-w-md'"
53+
id="connector-modal"
54+
>
55+
<!-- Connected state -->
56+
<div v-if="isConnected" class="space-y-4">
57+
<div class="flex items-center gap-3 p-4 bg-bg-subtle border border-border rounded-lg">
58+
<span class="w-3 h-3 rounded-full bg-green-500" aria-hidden="true" />
59+
<div>
60+
<p class="font-mono text-sm text-fg">{{ $t('connector.modal.connected') }}</p>
61+
<p v-if="npmUser" class="font-mono text-xs text-fg-muted">
62+
{{ $t('connector.modal.connected_as_user', { user: npmUser }) }}
63+
</p>
64+
</div>
65+
</div>
66+
67+
<!-- Operations Queue -->
68+
<OperationsQueue />
69+
70+
<div v-if="!hasOperations" class="text-sm text-fg-muted">
71+
{{ $t('connector.modal.connected_hint') }}
72+
</div>
73+
74+
<button
75+
type="button"
76+
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"
77+
@click="handleDisconnect"
78+
>
79+
{{ $t('connector.modal.disconnect') }}
80+
</button>
81+
</div>
82+
83+
<!-- Disconnected state -->
84+
<form v-else class="space-y-4" @submit.prevent="handleConnect">
85+
<p class="text-sm text-fg-muted">
86+
{{ $t('connector.modal.run_hint') }}
87+
</p>
88+
89+
<div
90+
class="flex items-center p-3 bg-bg-muted border border-border rounded-lg font-mono text-sm"
91+
>
92+
<span class="text-fg-subtle">$</span>
93+
<span class="text-fg-subtle ms-2">{{ executeNpmxConnectorCommand }}</span>
94+
<div class="ms-auto flex items-center gap-2">
95+
<PackageManagerSelect />
96+
97+
<button
98+
type="button"
99+
:aria-label="copied ? $t('connector.modal.copied') : $t('connector.modal.copy_command')"
100+
class="ms-auto text-fg-subtle hover:text-fg transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50 rounded"
101+
@click="copyCommand"
102+
>
103+
<span v-if="!copied" class="i-carbon:copy block w-5 h-5" aria-hidden="true" />
104+
<span
105+
v-else
106+
class="i-carbon:checkmark block w-5 h-5 text-green-500"
107+
aria-hidden="true"
108+
/>
109+
</button>
110+
</div>
111+
</div>
112+
113+
<p class="text-sm text-fg-muted">{{ $t('connector.modal.paste_token') }}</p>
114+
115+
<div class="space-y-3">
116+
<div>
117+
<label
118+
for="connector-token"
119+
class="block text-xs text-fg-subtle uppercase tracking-wider mb-1.5"
120+
>
121+
{{ $t('connector.modal.token_label') }}
122+
</label>
123+
<input
124+
id="connector-token"
125+
v-model="tokenInput"
126+
type="password"
127+
name="connector-token"
128+
:placeholder="$t('connector.modal.token_placeholder')"
129+
v-bind="noCorrect"
130+
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-accent/50"
131+
/>
132+
</div>
133+
134+
<details class="text-sm">
135+
<summary
136+
class="text-fg-subtle cursor-pointer hover:text-fg-muted transition-colors duration-200"
137+
>
138+
{{ $t('connector.modal.advanced') }}
139+
</summary>
140+
<div class="mt-3">
141+
<label
142+
for="connector-port"
143+
class="block text-xs text-fg-subtle uppercase tracking-wider mb-1.5"
144+
>
145+
{{ $t('connector.modal.port_label') }}
146+
</label>
147+
<input
148+
id="connector-port"
149+
v-model="portInput"
150+
type="text"
151+
name="connector-port"
152+
inputmode="numeric"
153+
autocomplete="off"
154+
class="w-full px-3 py-2 font-mono text-sm bg-bg-subtle border border-border rounded-md text-fg transition-colors duration-200 focus:border-border-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/50"
155+
/>
156+
</div>
157+
</details>
158+
</div>
159+
160+
<!-- Error message -->
161+
<div
162+
v-if="error"
163+
role="alert"
164+
class="p-3 text-sm text-red-400 bg-red-500/10 border border-red-500/20 rounded-md"
165+
>
166+
{{ error }}
167+
</div>
168+
169+
<!-- Warning message -->
170+
<div
171+
role="alert"
172+
class="p-3 text-sm text-red-400 bg-red-500/10 border border-red-500/20 rounded-md"
173+
>
174+
<p class="font-mono text-sm text-fg font-bold">
175+
{{ $t('connector.modal.warning') }}
176+
</p>
177+
<p class="text-sm text-fg-muted">
178+
{{ $t('connector.modal.warning_text') }}
179+
</p>
180+
</div>
181+
182+
<button
183+
type="submit"
184+
:disabled="!tokenInput.trim() || isConnecting"
185+
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"
186+
>
187+
{{ isConnecting ? $t('connector.modal.connecting') : $t('connector.modal.connect') }}
188+
</button>
189+
</form>
190+
</Modal>
191+
</template>

0 commit comments

Comments
 (0)