|
| 1 | +<script setup lang="ts"> |
| 2 | +import { computed, ref } from 'vue' |
| 3 | +import { useI18n } from 'vue-i18n' |
| 4 | +import { toast } from 'vue-sonner' |
| 5 | +import { Button } from '@/components/ui/button' |
| 6 | +import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card' |
| 7 | +import { Input } from '@/components/ui/input' |
| 8 | +import { useKeysStore } from '@/stores' |
| 9 | +import { useAIHubMixProvider } from '.' |
| 10 | +
|
| 11 | +const { t } = useI18n() |
| 12 | +const keysStore = useKeysStore() |
| 13 | +const provider = useAIHubMixProvider() |
| 14 | +
|
| 15 | +const isRegisterMode = ref(false) |
| 16 | +const username = ref('') |
| 17 | +const password = ref('') |
| 18 | +const confirmPassword = ref('') |
| 19 | +const isLoading = ref(false) |
| 20 | +
|
| 21 | +const passwordError = computed(() => { |
| 22 | + if (isRegisterMode.value && password.value && confirmPassword.value) { |
| 23 | + if (password.value !== confirmPassword.value) |
| 24 | + return t('passwordsDoNotMatch') |
| 25 | + } |
| 26 | + if (isRegisterMode.value && password.value) { |
| 27 | + if (password.value.length < 8 || password.value.length > 20) |
| 28 | + return t('passwordLengthError') |
| 29 | + } |
| 30 | + return '' |
| 31 | +}) |
| 32 | +
|
| 33 | +const canSubmit = computed(() => { |
| 34 | + if (isLoading.value) |
| 35 | + return false |
| 36 | + if (isRegisterMode.value) |
| 37 | + return username.value && password.value && confirmPassword.value && !passwordError.value |
| 38 | + else |
| 39 | + return username.value && password.value |
| 40 | +}) |
| 41 | +
|
| 42 | +async function submitForm() { |
| 43 | + if (!canSubmit.value) |
| 44 | + return |
| 45 | +
|
| 46 | + try { |
| 47 | + isLoading.value = true |
| 48 | + if (isRegisterMode.value) { |
| 49 | + // Register logic |
| 50 | + await provider.apis.register(username.value, password.value) |
| 51 | + toast.success(t('registerSuccess')) |
| 52 | + // Switch to login mode after successful registration |
| 53 | + isRegisterMode.value = false |
| 54 | + } |
| 55 | + else { |
| 56 | + // Login logic |
| 57 | + await provider.apis.login(username.value, password.value) |
| 58 | + toast.success(t('loginSuccess')) |
| 59 | + } |
| 60 | +
|
| 61 | + await provider.refreshUser() |
| 62 | +
|
| 63 | + // Automatically create API key after successful login |
| 64 | + if (!isRegisterMode.value) |
| 65 | + await keysStore.createAndAddKey(provider) |
| 66 | +
|
| 67 | + // Clear form |
| 68 | + username.value = '' |
| 69 | + password.value = '' |
| 70 | + confirmPassword.value = '' |
| 71 | + } |
| 72 | + catch (error) { |
| 73 | + console.error('Auth error:', error) |
| 74 | + toast.error(isRegisterMode.value ? t('registerFailed') : t('loginFailed')) |
| 75 | + } |
| 76 | + finally { |
| 77 | + isLoading.value = false |
| 78 | + } |
| 79 | +} |
| 80 | +</script> |
| 81 | + |
| 82 | +<template> |
| 83 | + <Card> |
| 84 | + <CardHeader class="pb-4"> |
| 85 | + <CardTitle class="text-lg"> |
| 86 | + {{ isRegisterMode ? t('registerTitle') : t('loginTitle') }} |
| 87 | + </CardTitle> |
| 88 | + <CardDescription class="text-sm"> |
| 89 | + {{ isRegisterMode ? t('registerDescription') : t('loginDescription') }} |
| 90 | + </CardDescription> |
| 91 | + </CardHeader> |
| 92 | + <CardContent class="space-y-4"> |
| 93 | + <!-- Username Input --> |
| 94 | + <div class="flex rounded-md border border-input bg-background"> |
| 95 | + <Input |
| 96 | + id="username" v-model="username" :placeholder="t('usernamePlaceholder')" type="text" |
| 97 | + class="border-0 focus-visible:ring-0 focus-visible:ring-offset-0 h-10" |
| 98 | + /> |
| 99 | + </div> |
| 100 | + |
| 101 | + <!-- Password Input --> |
| 102 | + <div class="flex rounded-md border border-input bg-background"> |
| 103 | + <Input |
| 104 | + id="password" v-model="password" :placeholder="t('passwordPlaceholder')" type="password" |
| 105 | + class="border-0 focus-visible:ring-0 focus-visible:ring-offset-0 h-10" |
| 106 | + /> |
| 107 | + </div> |
| 108 | + |
| 109 | + <!-- Confirm Password Input (Register Mode) --> |
| 110 | + <div v-if="isRegisterMode" class="flex flex-col"> |
| 111 | + <div class="flex rounded-md border border-input bg-background"> |
| 112 | + <Input |
| 113 | + id="confirmPassword" v-model="confirmPassword" :placeholder="t('confirmPasswordPlaceholder')" |
| 114 | + type="password" class="border-0 focus-visible:ring-0 focus-visible:ring-offset-0 h-10" |
| 115 | + /> |
| 116 | + </div> |
| 117 | + <p v-if="passwordError" class="text-xs text-red-500 mt-1.5"> |
| 118 | + {{ passwordError }} |
| 119 | + </p> |
| 120 | + </div> |
| 121 | + </CardContent> |
| 122 | + <CardFooter class="flex flex-col space-y-3 pt-3 items-start"> |
| 123 | + <Button class="w-full h-10" :disabled="!canSubmit" @click="submitForm()"> |
| 124 | + <span v-if="isLoading">{{ isRegisterMode ? t('registering') : t('loggingIn') }}</span> |
| 125 | + <span v-else>{{ isRegisterMode ? t('register') : t('login') }}</span> |
| 126 | + </Button> |
| 127 | + <div class="w-full grid grid-cols-1"> |
| 128 | + <Button variant="link" class="w-full h-10 text-sm" @click="isRegisterMode = !isRegisterMode"> |
| 129 | + {{ isRegisterMode ? t('switchToLogin') : t('switchToRegister') }} |
| 130 | + </Button> |
| 131 | + </div> |
| 132 | + </CardFooter> |
| 133 | + </Card> |
| 134 | +</template> |
| 135 | + |
| 136 | +<i18n lang="yaml"> |
| 137 | +en-US: |
| 138 | + loginTitle: Login to AIHubMix |
| 139 | + loginDescription: Enter your username and password to log in |
| 140 | + registerTitle: Create an Account |
| 141 | + registerDescription: Create a new AIHubMix account |
| 142 | + usernamePlaceholder: Username |
| 143 | + passwordPlaceholder: Password |
| 144 | + confirmPasswordPlaceholder: Confirm Password |
| 145 | + passwordsDoNotMatch: Passwords do not match |
| 146 | + passwordLengthError: Password must be 8-20 characters long |
| 147 | + loggingIn: Logging in... |
| 148 | + registering: Creating account... |
| 149 | + login: Login |
| 150 | + register: Register |
| 151 | + switchToLogin: Already have an account? Login |
| 152 | + switchToRegister: Don't have an account? Register |
| 153 | + loginSuccess: Login successful! |
| 154 | + registerSuccess: Registration successful! Please log in. |
| 155 | + loginFailed: Login failed. Please check your credentials. |
| 156 | + registerFailed: Registration failed. The username may already exist. |
| 157 | +
|
| 158 | +zh-CN: |
| 159 | + loginTitle: 登录 AIHubMix |
| 160 | + loginDescription: 输入您的账户名和密码进行登录 |
| 161 | + registerTitle: 创建新账户 |
| 162 | + registerDescription: 创建一个新的 AIHubMix 账户 |
| 163 | + usernamePlaceholder: 账户名 |
| 164 | + passwordPlaceholder: 密码 |
| 165 | + confirmPasswordPlaceholder: 确认密码 |
| 166 | + passwordsDoNotMatch: 两次输入的密码不一致 |
| 167 | + passwordLengthError: 密码长度必须为 8-20 个字符 |
| 168 | + loggingIn: 登录中... |
| 169 | + registering: 注册中... |
| 170 | + login: 登录 |
| 171 | + register: 注册 |
| 172 | + switchToLogin: 已有账户?前往登录 |
| 173 | + switchToRegister: 没有账户?前往注册 |
| 174 | + loginSuccess: 登录成功! |
| 175 | + registerSuccess: 注册成功!请登录。 |
| 176 | + loginFailed: 登录失败,请检查您的账户名和密码。 |
| 177 | + registerFailed: 注册失败,账户名可能已被占用。 |
| 178 | +</i18n> |
0 commit comments