@cat-factory/app 0.46.11 → 0.47.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/components/auth/AuthGate.vue +3 -2
- package/app/components/auth/LoginScreen.vue +172 -32
- package/app/components/auth/ResetPasswordScreen.vue +16 -13
- package/app/components/auth/UserMenu.vue +3 -2
- package/app/components/layout/AccountDeploymentSettings.vue +307 -37
- package/app/components/layout/AccountFragmentSettings.vue +2 -3
- package/app/components/layout/AccountTeamSettings.vue +75 -45
- package/app/components/layout/AiProvidersBanner.vue +11 -11
- package/app/components/layout/BoardSwitcher.vue +63 -29
- package/app/components/layout/CommandBar.vue +68 -54
- package/app/components/layout/GitHubPatBanner.vue +13 -6
- package/app/components/layout/IntegrationBackTitle.vue +4 -1
- package/app/components/layout/IntegrationsHub.vue +67 -49
- package/app/components/layout/NotificationsInbox.vue +48 -19
- package/app/components/layout/PersonalSetupModal.vue +26 -16
- package/app/components/layout/ProviderConfigBanner.vue +14 -9
- package/app/components/layout/SideBar.vue +20 -18
- package/app/components/layout/SpendWarningBanner.vue +16 -16
- package/app/components/providers/ApiKeysSection.vue +27 -3
- package/app/components/providers/PersonalSubscriptionSection.vue +18 -4
- package/app/components/providers/SignInRequiredNotice.vue +19 -0
- package/app/components/settings/AccountSettingsPanel.vue +9 -6
- package/app/components/settings/IssueTrackerPanel.vue +114 -53
- package/app/components/settings/LocalModeSettingsPanel.vue +60 -28
- package/app/components/settings/LocalModelEndpointsPanel.vue +72 -27
- package/app/components/settings/MergeThresholdsPanel.vue +93 -45
- package/app/components/settings/ModelConfigurationPanel.vue +62 -36
- package/app/components/settings/ObservabilityConnectionPanel.vue +65 -35
- package/app/components/settings/OpenRouterCatalogPanel.vue +70 -40
- package/app/components/settings/ProviderConnectionPanel.vue +115 -61
- package/app/components/settings/ServiceFragmentDefaultsPanel.vue +9 -12
- package/app/components/settings/UserSecretsSection.vue +37 -18
- package/app/components/settings/WorkspaceSettingsPanel.vue +114 -62
- package/app/composables/api/auth.ts +7 -0
- package/app/stores/auth.ts +25 -1
- package/app/types/accountSettings.ts +4 -0
- package/i18n/locales/en.json +908 -0
- package/i18n/locales/es.json +899 -0
- package/i18n/locales/fr.json +899 -0
- package/i18n/locales/pl.json +899 -0
- package/i18n/locales/uk.json +899 -0
- package/package.json +2 -2
|
@@ -7,6 +7,7 @@ import LoginScreen from '~/components/auth/LoginScreen.vue'
|
|
|
7
7
|
// inside the default slot, so it only fires once the user is allowed in.
|
|
8
8
|
const auth = useAuthStore()
|
|
9
9
|
const route = useRoute()
|
|
10
|
+
const { t } = useI18n()
|
|
10
11
|
|
|
11
12
|
// The password-reset page is public: a recipient of an emailed reset link is signed
|
|
12
13
|
// out, so it must render even when auth is required and there's no user.
|
|
@@ -21,12 +22,12 @@ onMounted(() => auth.bootstrap())
|
|
|
21
22
|
class="flex h-screen w-screen flex-col items-center justify-center gap-3 bg-slate-950 text-slate-400"
|
|
22
23
|
>
|
|
23
24
|
<UIcon name="i-lucide-loader" class="h-8 w-8 animate-spin" />
|
|
24
|
-
<span class="text-sm">
|
|
25
|
+
<span class="text-sm">{{ t('auth.gate.loading') }}</span>
|
|
25
26
|
</div>
|
|
26
27
|
|
|
27
28
|
<slot v-else-if="isPublicRoute" />
|
|
28
29
|
|
|
29
|
-
<LoginScreen v-else-if="auth.
|
|
30
|
+
<LoginScreen v-else-if="auth.needsLogin" />
|
|
30
31
|
|
|
31
32
|
<slot v-else />
|
|
32
33
|
</template>
|
|
@@ -1,8 +1,73 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { computed, ref } from 'vue'
|
|
2
|
+
import { computed, ref, watch } from 'vue'
|
|
3
3
|
import { apiErrorEnvelope } from '~/composables/api/errors'
|
|
4
4
|
|
|
5
5
|
const auth = useAuthStore()
|
|
6
|
+
const { t } = useI18n()
|
|
7
|
+
|
|
8
|
+
// Local-mode source-control PAT login. GitHub/GitLab are brand names (kept verbatim across
|
|
9
|
+
// locales), as are the token-settings URLs, so they're inline constants rather than catalog
|
|
10
|
+
// keys — same convention as the provider descriptors in ApiKeysSection. The actual link
|
|
11
|
+
// prefers the server's scopes-preselected deep link (`patLogin.setupUrls`); these are the
|
|
12
|
+
// fallback when it's absent.
|
|
13
|
+
type PatProvider = 'github' | 'gitlab'
|
|
14
|
+
const PROVIDER_LABELS: Record<PatProvider, string> = { github: 'GitHub', gitlab: 'GitLab' }
|
|
15
|
+
const PROVIDER_ICONS: Record<PatProvider, string> = {
|
|
16
|
+
github: 'i-lucide-github',
|
|
17
|
+
gitlab: 'i-lucide-gitlab',
|
|
18
|
+
}
|
|
19
|
+
// Fallback token-creation pages, used only if the server didn't advertise a deep link.
|
|
20
|
+
const PROVIDER_TOKEN_URLS: Record<PatProvider, string> = {
|
|
21
|
+
github: 'https://github.com/settings/tokens/new',
|
|
22
|
+
gitlab: 'https://gitlab.com/-/user_settings/personal_access_tokens',
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const patLoginCfg = computed(() => auth.localMode?.patLogin)
|
|
26
|
+
const configuredProviders = computed<PatProvider[]>(
|
|
27
|
+
() => (patLoginCfg.value?.configured ?? []) as PatProvider[],
|
|
28
|
+
)
|
|
29
|
+
const availableProviders = computed<PatProvider[]>(
|
|
30
|
+
() => (patLoginCfg.value?.available ?? []) as PatProvider[],
|
|
31
|
+
)
|
|
32
|
+
const showLocalLogin = computed(() => availableProviders.value.length > 0)
|
|
33
|
+
|
|
34
|
+
const patProvider = ref<PatProvider>('github')
|
|
35
|
+
const patToken = ref('')
|
|
36
|
+
const patBusy = ref(false)
|
|
37
|
+
const patError = ref<string | null>(null)
|
|
38
|
+
|
|
39
|
+
// Keep the picker on an actually-available provider.
|
|
40
|
+
watch(
|
|
41
|
+
availableProviders,
|
|
42
|
+
(list) => {
|
|
43
|
+
if (list.length && !list.includes(patProvider.value)) patProvider.value = list[0]!
|
|
44
|
+
},
|
|
45
|
+
{ immediate: true },
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
const patProviderItems = computed(() =>
|
|
49
|
+
availableProviders.value.map((p) => ({ label: PROVIDER_LABELS[p], value: p })),
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
// Prefer the server's scopes-preselected deep link (it owns the per-provider scopes);
|
|
53
|
+
// fall back to the plain token page if it wasn't advertised.
|
|
54
|
+
const tokenCreateUrl = computed(
|
|
55
|
+
() => patLoginCfg.value?.setupUrls?.[patProvider.value] ?? PROVIDER_TOKEN_URLS[patProvider.value],
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
/** One-click (configured PAT) or pasted-token sign-in; reloads so the app boots signed in. */
|
|
59
|
+
async function submitPat(provider: PatProvider, token?: string) {
|
|
60
|
+
patError.value = null
|
|
61
|
+
patBusy.value = true
|
|
62
|
+
try {
|
|
63
|
+
await auth.patLogin(token ? { provider, token } : { provider })
|
|
64
|
+
if (typeof window !== 'undefined') window.location.assign(window.location.pathname)
|
|
65
|
+
} catch (e) {
|
|
66
|
+
patError.value = apiErrorEnvelope(e)?.message ?? t('auth.localMode.failed')
|
|
67
|
+
} finally {
|
|
68
|
+
patBusy.value = false
|
|
69
|
+
}
|
|
70
|
+
}
|
|
6
71
|
|
|
7
72
|
// An invite token may ride in on the URL (?invite=…) — it flows through the OAuth
|
|
8
73
|
// redirect and the password signup so a brand-new user can join the org on first login.
|
|
@@ -41,8 +106,7 @@ async function submitPassword() {
|
|
|
41
106
|
// Reload so the app boots with the new session.
|
|
42
107
|
if (typeof window !== 'undefined') window.location.assign(window.location.pathname)
|
|
43
108
|
} catch (e) {
|
|
44
|
-
error.value =
|
|
45
|
-
apiErrorEnvelope(e)?.message ?? 'Sign-in failed. Check your details and try again.'
|
|
109
|
+
error.value = apiErrorEnvelope(e)?.message ?? t('auth.login.signInFailed')
|
|
46
110
|
} finally {
|
|
47
111
|
busy.value = false
|
|
48
112
|
}
|
|
@@ -56,7 +120,7 @@ async function submitForgot() {
|
|
|
56
120
|
forgotSent.value = true
|
|
57
121
|
} catch {
|
|
58
122
|
// The request endpoint is generic by design; only an infra error lands here.
|
|
59
|
-
error.value = '
|
|
123
|
+
error.value = t('auth.login.genericError')
|
|
60
124
|
} finally {
|
|
61
125
|
busy.value = false
|
|
62
126
|
}
|
|
@@ -81,15 +145,81 @@ const showOAuthDivider = computed(
|
|
|
81
145
|
>
|
|
82
146
|
<div class="mb-6 text-center">
|
|
83
147
|
<UIcon name="i-lucide-layout-dashboard" class="mx-auto mb-3 h-10 w-10 text-indigo-400" />
|
|
84
|
-
<h1 class="mb-1 text-lg font-semibold text-white">
|
|
148
|
+
<h1 class="mb-1 text-lg font-semibold text-white">{{ t('auth.login.appTitle') }}</h1>
|
|
85
149
|
<p class="text-sm text-slate-400">
|
|
86
|
-
<template v-if="mode === 'forgot'">
|
|
150
|
+
<template v-if="mode === 'forgot'">{{ t('auth.login.forgotSubtitle') }}</template>
|
|
87
151
|
<template v-else>{{
|
|
88
|
-
invite ? '
|
|
152
|
+
invite ? t('auth.login.inviteSubtitle') : t('auth.login.subtitle')
|
|
89
153
|
}}</template>
|
|
90
154
|
</p>
|
|
91
155
|
</div>
|
|
92
156
|
|
|
157
|
+
<!-- Local mode: sign in with a source-control PAT (no OAuth round-trip needed) -->
|
|
158
|
+
<div v-if="showLocalLogin && mode !== 'forgot'" class="space-y-3">
|
|
159
|
+
<!-- One-click: a PAT is already configured server-side -->
|
|
160
|
+
<UButton
|
|
161
|
+
v-for="p in configuredProviders"
|
|
162
|
+
:key="p"
|
|
163
|
+
block
|
|
164
|
+
size="lg"
|
|
165
|
+
color="primary"
|
|
166
|
+
:icon="PROVIDER_ICONS[p]"
|
|
167
|
+
:loading="patBusy"
|
|
168
|
+
@click="submitPat(p)"
|
|
169
|
+
>
|
|
170
|
+
{{ t('auth.localMode.continueWith', { provider: PROVIDER_LABELS[p] }) }}
|
|
171
|
+
</UButton>
|
|
172
|
+
|
|
173
|
+
<!-- Enter a PAT inline -->
|
|
174
|
+
<form class="space-y-2" @submit.prevent="submitPat(patProvider, patToken.trim())">
|
|
175
|
+
<p class="text-xs font-medium text-slate-400">{{ t('auth.localMode.enterPatTitle') }}</p>
|
|
176
|
+
<USelect
|
|
177
|
+
v-if="patProviderItems.length > 1"
|
|
178
|
+
v-model="patProvider"
|
|
179
|
+
:items="patProviderItems"
|
|
180
|
+
size="lg"
|
|
181
|
+
class="w-full"
|
|
182
|
+
/>
|
|
183
|
+
<UTextarea
|
|
184
|
+
v-model="patToken"
|
|
185
|
+
:rows="2"
|
|
186
|
+
:placeholder="
|
|
187
|
+
t('auth.localMode.tokenPlaceholder', { provider: PROVIDER_LABELS[patProvider] })
|
|
188
|
+
"
|
|
189
|
+
class="w-full font-mono"
|
|
190
|
+
/>
|
|
191
|
+
<div class="flex items-center justify-between gap-2">
|
|
192
|
+
<a
|
|
193
|
+
:href="tokenCreateUrl"
|
|
194
|
+
target="_blank"
|
|
195
|
+
rel="noopener noreferrer"
|
|
196
|
+
class="text-xs text-indigo-400 hover:underline"
|
|
197
|
+
>
|
|
198
|
+
{{ t('auth.localMode.createToken', { provider: PROVIDER_LABELS[patProvider] }) }}
|
|
199
|
+
</a>
|
|
200
|
+
<UButton
|
|
201
|
+
size="lg"
|
|
202
|
+
color="neutral"
|
|
203
|
+
variant="subtle"
|
|
204
|
+
type="submit"
|
|
205
|
+
:loading="patBusy"
|
|
206
|
+
:disabled="!patToken.trim()"
|
|
207
|
+
>
|
|
208
|
+
{{ t('auth.localMode.submit') }}
|
|
209
|
+
</UButton>
|
|
210
|
+
</div>
|
|
211
|
+
</form>
|
|
212
|
+
<p v-if="patError" class="text-sm text-rose-400">{{ patError }}</p>
|
|
213
|
+
</div>
|
|
214
|
+
|
|
215
|
+
<div
|
|
216
|
+
v-if="showLocalLogin && auth.providers.password && mode !== 'forgot'"
|
|
217
|
+
class="my-4 flex items-center gap-3 text-xs text-slate-500"
|
|
218
|
+
>
|
|
219
|
+
<span class="h-px flex-1 bg-slate-800" /> {{ t('auth.localMode.orDivider') }}
|
|
220
|
+
<span class="h-px flex-1 bg-slate-800" />
|
|
221
|
+
</div>
|
|
222
|
+
|
|
93
223
|
<!-- OAuth providers -->
|
|
94
224
|
<div v-if="mode !== 'forgot'" class="space-y-2">
|
|
95
225
|
<UButton
|
|
@@ -100,7 +230,7 @@ const showOAuthDivider = computed(
|
|
|
100
230
|
icon="i-lucide-github"
|
|
101
231
|
@click="auth.login(invite)"
|
|
102
232
|
>
|
|
103
|
-
|
|
233
|
+
{{ t('auth.login.continueWithGithub') }}
|
|
104
234
|
</UButton>
|
|
105
235
|
<UButton
|
|
106
236
|
v-if="auth.providers.google"
|
|
@@ -111,7 +241,7 @@ const showOAuthDivider = computed(
|
|
|
111
241
|
icon="i-lucide-mail"
|
|
112
242
|
@click="auth.loginWithGoogle(invite)"
|
|
113
243
|
>
|
|
114
|
-
|
|
244
|
+
{{ t('auth.login.continueWithGoogle') }}
|
|
115
245
|
</UButton>
|
|
116
246
|
</div>
|
|
117
247
|
|
|
@@ -119,7 +249,8 @@ const showOAuthDivider = computed(
|
|
|
119
249
|
v-if="showOAuthDivider && mode !== 'forgot'"
|
|
120
250
|
class="my-4 flex items-center gap-3 text-xs text-slate-500"
|
|
121
251
|
>
|
|
122
|
-
<span class="h-px flex-1 bg-slate-800" /> or
|
|
252
|
+
<span class="h-px flex-1 bg-slate-800" /> {{ t('auth.login.or') }}
|
|
253
|
+
<span class="h-px flex-1 bg-slate-800" />
|
|
123
254
|
</div>
|
|
124
255
|
|
|
125
256
|
<!-- Email / password -->
|
|
@@ -131,7 +262,7 @@ const showOAuthDivider = computed(
|
|
|
131
262
|
<UInput
|
|
132
263
|
v-if="mode === 'signup'"
|
|
133
264
|
v-model="name"
|
|
134
|
-
placeholder="
|
|
265
|
+
:placeholder="t('auth.login.namePlaceholder')"
|
|
135
266
|
icon="i-lucide-user"
|
|
136
267
|
size="lg"
|
|
137
268
|
class="w-full"
|
|
@@ -140,7 +271,7 @@ const showOAuthDivider = computed(
|
|
|
140
271
|
v-model="email"
|
|
141
272
|
type="email"
|
|
142
273
|
required
|
|
143
|
-
placeholder="
|
|
274
|
+
:placeholder="t('auth.login.emailPlaceholder')"
|
|
144
275
|
icon="i-lucide-at-sign"
|
|
145
276
|
size="lg"
|
|
146
277
|
class="w-full"
|
|
@@ -149,36 +280,46 @@ const showOAuthDivider = computed(
|
|
|
149
280
|
v-model="password"
|
|
150
281
|
type="password"
|
|
151
282
|
required
|
|
152
|
-
placeholder="
|
|
283
|
+
:placeholder="t('auth.login.passwordPlaceholder')"
|
|
153
284
|
icon="i-lucide-lock"
|
|
154
285
|
size="lg"
|
|
155
286
|
class="w-full"
|
|
156
287
|
/>
|
|
157
288
|
<p v-if="error" class="text-sm text-rose-400">{{ error }}</p>
|
|
158
289
|
<UButton block size="lg" color="primary" type="submit" :loading="busy">
|
|
159
|
-
{{ mode === 'signup' ? '
|
|
290
|
+
{{ mode === 'signup' ? t('auth.login.createAccount') : t('auth.login.signIn') }}
|
|
160
291
|
</UButton>
|
|
161
292
|
<p class="text-center text-xs text-slate-400">
|
|
162
293
|
<template v-if="mode === 'login'">
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
294
|
+
<i18n-t keypath="auth.login.needAccount" tag="span" scope="global">
|
|
295
|
+
<template #signUp>
|
|
296
|
+
<button
|
|
297
|
+
type="button"
|
|
298
|
+
class="text-indigo-400 hover:underline"
|
|
299
|
+
@click="setMode('signup')"
|
|
300
|
+
>
|
|
301
|
+
{{ t('auth.login.signUp') }}
|
|
302
|
+
</button>
|
|
303
|
+
</template>
|
|
304
|
+
</i18n-t>
|
|
171
305
|
</template>
|
|
172
306
|
<template v-else>
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
307
|
+
<i18n-t keypath="auth.login.haveAccount" tag="span" scope="global">
|
|
308
|
+
<template #signIn>
|
|
309
|
+
<button
|
|
310
|
+
type="button"
|
|
311
|
+
class="text-indigo-400 hover:underline"
|
|
312
|
+
@click="setMode('login')"
|
|
313
|
+
>
|
|
314
|
+
{{ t('auth.login.signIn') }}
|
|
315
|
+
</button>
|
|
316
|
+
</template>
|
|
317
|
+
</i18n-t>
|
|
177
318
|
</template>
|
|
178
319
|
</p>
|
|
179
320
|
<p v-if="mode === 'login'" class="text-center text-xs text-slate-400">
|
|
180
321
|
<button type="button" class="text-indigo-400 hover:underline" @click="setMode('forgot')">
|
|
181
|
-
|
|
322
|
+
{{ t('auth.login.forgotPassword') }}
|
|
182
323
|
</button>
|
|
183
324
|
</p>
|
|
184
325
|
</form>
|
|
@@ -191,8 +332,7 @@ const showOAuthDivider = computed(
|
|
|
191
332
|
>
|
|
192
333
|
<template v-if="forgotSent">
|
|
193
334
|
<p class="text-sm text-slate-300">
|
|
194
|
-
|
|
195
|
-
inbox.
|
|
335
|
+
{{ t('auth.login.forgotSent') }}
|
|
196
336
|
</p>
|
|
197
337
|
</template>
|
|
198
338
|
<template v-else>
|
|
@@ -200,19 +340,19 @@ const showOAuthDivider = computed(
|
|
|
200
340
|
v-model="email"
|
|
201
341
|
type="email"
|
|
202
342
|
required
|
|
203
|
-
placeholder="
|
|
343
|
+
:placeholder="t('auth.login.emailPlaceholder')"
|
|
204
344
|
icon="i-lucide-at-sign"
|
|
205
345
|
size="lg"
|
|
206
346
|
class="w-full"
|
|
207
347
|
/>
|
|
208
348
|
<p v-if="error" class="text-sm text-rose-400">{{ error }}</p>
|
|
209
349
|
<UButton block size="lg" color="primary" type="submit" :loading="busy">
|
|
210
|
-
|
|
350
|
+
{{ t('auth.login.sendResetLink') }}
|
|
211
351
|
</UButton>
|
|
212
352
|
</template>
|
|
213
353
|
<p class="text-center text-xs text-slate-400">
|
|
214
354
|
<button type="button" class="text-indigo-400 hover:underline" @click="setMode('login')">
|
|
215
|
-
|
|
355
|
+
{{ t('auth.login.backToSignIn') }}
|
|
216
356
|
</button>
|
|
217
357
|
</p>
|
|
218
358
|
</form>
|
|
@@ -5,6 +5,7 @@ import { computed, ref } from 'vue'
|
|
|
5
5
|
// (`/reset-password?token=…`). It is a public route (see AuthGate), so a recipient who
|
|
6
6
|
// is signed out can still set a new password. On success we send them to the login.
|
|
7
7
|
const auth = useAuthStore()
|
|
8
|
+
const { t } = useI18n()
|
|
8
9
|
|
|
9
10
|
const token = computed(() => {
|
|
10
11
|
if (typeof window === 'undefined') return ''
|
|
@@ -20,11 +21,11 @@ const done = ref(false)
|
|
|
20
21
|
async function submit() {
|
|
21
22
|
error.value = null
|
|
22
23
|
if (password.value.length < 8) {
|
|
23
|
-
error.value = '
|
|
24
|
+
error.value = t('auth.resetPassword.errorTooShort')
|
|
24
25
|
return
|
|
25
26
|
}
|
|
26
27
|
if (password.value !== confirm.value) {
|
|
27
|
-
error.value = '
|
|
28
|
+
error.value = t('auth.resetPassword.errorMismatch')
|
|
28
29
|
return
|
|
29
30
|
}
|
|
30
31
|
busy.value = true
|
|
@@ -34,7 +35,7 @@ async function submit() {
|
|
|
34
35
|
} catch (e) {
|
|
35
36
|
error.value =
|
|
36
37
|
(e as { data?: { error?: { message?: string } } })?.data?.error?.message ??
|
|
37
|
-
'
|
|
38
|
+
t('auth.resetPassword.errorInvalidLink')
|
|
38
39
|
} finally {
|
|
39
40
|
busy.value = false
|
|
40
41
|
}
|
|
@@ -52,23 +53,25 @@ function goToLogin() {
|
|
|
52
53
|
>
|
|
53
54
|
<div class="mb-6 text-center">
|
|
54
55
|
<UIcon name="i-lucide-key-round" class="mx-auto mb-3 h-10 w-10 text-indigo-400" />
|
|
55
|
-
<h1 class="mb-1 text-lg font-semibold text-white">
|
|
56
|
-
<p class="text-sm text-slate-400">
|
|
56
|
+
<h1 class="mb-1 text-lg font-semibold text-white">{{ t('auth.resetPassword.title') }}</h1>
|
|
57
|
+
<p class="text-sm text-slate-400">{{ t('auth.resetPassword.subtitle') }}</p>
|
|
57
58
|
</div>
|
|
58
59
|
|
|
59
60
|
<template v-if="done">
|
|
60
61
|
<p class="mb-4 text-sm text-slate-300">
|
|
61
|
-
|
|
62
|
+
{{ t('auth.resetPassword.doneBody') }}
|
|
62
63
|
</p>
|
|
63
|
-
<UButton block size="lg" color="primary" @click="goToLogin">
|
|
64
|
+
<UButton block size="lg" color="primary" @click="goToLogin">{{
|
|
65
|
+
t('auth.resetPassword.goToSignIn')
|
|
66
|
+
}}</UButton>
|
|
64
67
|
</template>
|
|
65
68
|
|
|
66
69
|
<template v-else-if="!token">
|
|
67
70
|
<p class="mb-4 text-sm text-rose-400">
|
|
68
|
-
|
|
71
|
+
{{ t('auth.resetPassword.missingToken') }}
|
|
69
72
|
</p>
|
|
70
73
|
<UButton block size="lg" color="neutral" variant="subtle" @click="goToLogin">
|
|
71
|
-
|
|
74
|
+
{{ t('auth.resetPassword.backToSignIn') }}
|
|
72
75
|
</UButton>
|
|
73
76
|
</template>
|
|
74
77
|
|
|
@@ -77,7 +80,7 @@ function goToLogin() {
|
|
|
77
80
|
v-model="password"
|
|
78
81
|
type="password"
|
|
79
82
|
required
|
|
80
|
-
placeholder="
|
|
83
|
+
:placeholder="t('auth.resetPassword.newPasswordPlaceholder')"
|
|
81
84
|
icon="i-lucide-lock"
|
|
82
85
|
size="lg"
|
|
83
86
|
class="w-full"
|
|
@@ -86,18 +89,18 @@ function goToLogin() {
|
|
|
86
89
|
v-model="confirm"
|
|
87
90
|
type="password"
|
|
88
91
|
required
|
|
89
|
-
placeholder="
|
|
92
|
+
:placeholder="t('auth.resetPassword.confirmPasswordPlaceholder')"
|
|
90
93
|
icon="i-lucide-lock"
|
|
91
94
|
size="lg"
|
|
92
95
|
class="w-full"
|
|
93
96
|
/>
|
|
94
97
|
<p v-if="error" class="text-sm text-rose-400">{{ error }}</p>
|
|
95
98
|
<UButton block size="lg" color="primary" type="submit" :loading="busy">
|
|
96
|
-
|
|
99
|
+
{{ t('auth.resetPassword.submit') }}
|
|
97
100
|
</UButton>
|
|
98
101
|
<p class="text-center text-xs text-slate-400">
|
|
99
102
|
<button type="button" class="text-indigo-400 hover:underline" @click="goToLogin">
|
|
100
|
-
|
|
103
|
+
{{ t('auth.resetPassword.backToSignIn') }}
|
|
101
104
|
</button>
|
|
102
105
|
</p>
|
|
103
106
|
</form>
|
|
@@ -4,20 +4,21 @@ import type { DropdownMenuItem } from '@nuxt/ui'
|
|
|
4
4
|
// Signed-in identity + per-user actions, shown in the sidebar when auth is enabled.
|
|
5
5
|
const auth = useAuthStore()
|
|
6
6
|
const ui = useUiStore()
|
|
7
|
+
const { t } = useI18n()
|
|
7
8
|
|
|
8
9
|
const items = computed<DropdownMenuItem[][]>(() => [
|
|
9
10
|
[
|
|
10
11
|
{
|
|
11
12
|
// The user-scoped "My setup" hub (personal GitHub token, local runners, personal
|
|
12
13
|
// subscriptions) — kept out of the workspace Integrations hub, reachable here.
|
|
13
|
-
label: '
|
|
14
|
+
label: t('auth.userMenu.mySetup'),
|
|
14
15
|
icon: 'i-lucide-user-cog',
|
|
15
16
|
onSelect: () => ui.openPersonalSetup(),
|
|
16
17
|
},
|
|
17
18
|
],
|
|
18
19
|
[
|
|
19
20
|
{
|
|
20
|
-
label: '
|
|
21
|
+
label: t('auth.userMenu.signOut'),
|
|
21
22
|
icon: 'i-lucide-log-out',
|
|
22
23
|
onSelect: () => auth.logout(),
|
|
23
24
|
},
|