RegisterView.vue
4,702 bytes
| 1 | <script setup lang="ts"> |
|---|---|
| 2 | import { ref } from 'vue' |
| 3 | import { useRouter } from 'vue-router' |
| 4 | import { useI18n } from 'vue-i18n' |
| 5 | import { useAuthStore } from '@/stores/auth' |
| 6 | import AccountService from '@/services/AccountService' |
| 7 | |
| 8 | const router = useRouter() |
| 9 | const authStore = useAuthStore() |
| 10 | const { t } = useI18n() |
| 11 | |
| 12 | const firstName = ref('') |
| 13 | const lastName = ref('') |
| 14 | const email = ref('') |
| 15 | const password = ref('') |
| 16 | const errors = ref<string[]>([]) |
| 17 | const isLoading = ref(false) |
| 18 | |
| 19 | async function handleRegister() { |
| 20 | errors.value = [] |
| 21 | isLoading.value = true |
| 22 | |
| 23 | const result = await AccountService.registerAsync( |
| 24 | email.value, |
| 25 | password.value, |
| 26 | firstName.value, |
| 27 | lastName.value, |
| 28 | ) |
| 29 | |
| 30 | if (result.errors) { |
| 31 | errors.value = result.errors |
| 32 | } else if (result.data) { |
| 33 | authStore.jwt = result.data.jwt |
| 34 | authStore.refreshToken = result.data.refreshToken |
| 35 | authStore.userName = `${result.data.firstName} ${result.data.lastName}` |
| 36 | router.push({ name: 'Home' }) |
| 37 | } |
| 38 | |
| 39 | isLoading.value = false |
| 40 | } |
| 41 | </script> |
| 42 | |
| 43 | <template> |
| 44 | <div class="sa-auth-page"> |
| 45 | <div class="sa-auth-card sa-card sa-animate-fade-in"> |
| 46 | <div class="sa-card-body" style="padding: var(--sa-space-10) var(--sa-space-8)"> |
| 47 | <!-- Brand --> |
| 48 | <div class="sa-auth-brand"> |
| 49 | <div class="sa-auth-logo"> |
| 50 | <i class="bi bi-airplane-fill"></i> |
| 51 | </div> |
| 52 | <h1 class="sa-text-primary">SplitApp</h1> |
| 53 | <p>{{ t('auth.register.getStarted') }}</p> |
| 54 | </div> |
| 55 | |
| 56 | <!-- Errors --> |
| 57 | <div v-if="errors.length" class="sa-card-static sa-card-accent sa-card-accent-danger mb-4"> |
| 58 | <div class="sa-card-body" style="padding: var(--sa-space-3) var(--sa-space-4)"> |
| 59 | <div v-for="error in errors" :key="error" style="font-size: 0.875rem"> |
| 60 | <i class="bi bi-exclamation-circle me-1"></i>{{ error }} |
| 61 | </div> |
| 62 | </div> |
| 63 | </div> |
| 64 | |
| 65 | <!-- Form --> |
| 66 | <form @submit.prevent="handleRegister"> |
| 67 | <div class="d-flex gap-3 mb-3"> |
| 68 | <div class="flex-fill"> |
| 69 | <label for="firstName" class="form-label">{{ t('auth.register.firstName') }}</label> |
| 70 | <input |
| 71 | id="firstName" |
| 72 | v-model="firstName" |
| 73 | type="text" |
| 74 | class="form-control" |
| 75 | :placeholder="t('auth.register.firstNamePlaceholder')" |
| 76 | required |
| 77 | /> |
| 78 | </div> |
| 79 | <div class="flex-fill"> |
| 80 | <label for="lastName" class="form-label">{{ t('auth.register.lastName') }}</label> |
| 81 | <input |
| 82 | id="lastName" |
| 83 | v-model="lastName" |
| 84 | type="text" |
| 85 | class="form-control" |
| 86 | :placeholder="t('auth.register.lastNamePlaceholder')" |
| 87 | required |
| 88 | /> |
| 89 | </div> |
| 90 | </div> |
| 91 | |
| 92 | <div class="mb-3"> |
| 93 | <label for="email" class="form-label">{{ t('auth.register.email') }}</label> |
| 94 | <input |
| 95 | id="email" |
| 96 | v-model="email" |
| 97 | type="email" |
| 98 | class="form-control" |
| 99 | :placeholder="t('auth.register.emailPlaceholder')" |
| 100 | required |
| 101 | autocomplete="email" |
| 102 | /> |
| 103 | </div> |
| 104 | |
| 105 | <div class="mb-4"> |
| 106 | <label for="password" class="form-label">{{ t('auth.register.password') }}</label> |
| 107 | <input |
| 108 | id="password" |
| 109 | v-model="password" |
| 110 | type="password" |
| 111 | class="form-control" |
| 112 | :placeholder="t('auth.register.passwordPlaceholder')" |
| 113 | required |
| 114 | minlength="6" |
| 115 | autocomplete="new-password" |
| 116 | /> |
| 117 | </div> |
| 118 | |
| 119 | <button |
| 120 | type="submit" |
| 121 | class="sa-btn sa-btn-primary w-100" |
| 122 | :class="{ 'sa-btn-loading': isLoading }" |
| 123 | :disabled="isLoading" |
| 124 | > |
| 125 | {{ isLoading ? t('auth.register.creatingAccount') : t('auth.register.createAccount') }} |
| 126 | </button> |
| 127 | </form> |
| 128 | |
| 129 | <!-- Login link --> |
| 130 | <div class="text-center mt-4" style="font-size: 0.938rem; color: var(--sa-gray-500)"> |
| 131 | {{ t('auth.register.haveAccount') }} |
| 132 | <RouterLink :to="{ name: 'Login' }" style="font-weight: 600">{{ t('auth.register.signIn') }}</RouterLink> |
| 133 | </div> |
| 134 | </div> |
| 135 | </div> |
| 136 | </div> |
| 137 | </template> |
| 138 | |
| 139 | <style scoped> |
| 140 | .sa-auth-logo { |
| 141 | width: 56px; |
| 142 | height: 56px; |
| 143 | border-radius: 16px; |
| 144 | background: linear-gradient(135deg, #e8604c 0%, #d4456a 100%); |
| 145 | display: flex; |
| 146 | align-items: center; |
| 147 | justify-content: center; |
| 148 | font-size: 1.5rem; |
| 149 | color: #fff; |
| 150 | margin: 0 auto 16px; |
| 151 | } |
| 152 | </style> |
| 153 | |