profileShare

rasmusjy / splitapp-frontend-vue

Read-only snapshot

No repository description.

main default branch 96 files Expires Sep 13, 2026, 9:06 AM
LoginView.vue 3,636 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 email = ref('')
13 const password = ref('')
14 const errors = ref<string[]>([])
15 const isLoading = ref(false)
16
17 async function handleLogin() {
18 errors.value = []
19 isLoading.value = true
20
21 const result = await AccountService.loginAsync(email.value, password.value)
22 if (result.errors) {
23 errors.value = result.errors
24 } else if (result.data) {
25 authStore.jwt = result.data.jwt
26 authStore.refreshToken = result.data.refreshToken
27 authStore.userName = `${result.data.firstName} ${result.data.lastName}`
28 router.push({ name: 'Home' })
29 }
30
31 isLoading.value = false
32 }
33 </script>
34
35 <template>
36 <div class="sa-auth-page">
37 <div class="sa-auth-card sa-card sa-animate-fade-in">
38 <div class="sa-card-body" style="padding: var(--sa-space-10) var(--sa-space-8)">
39 <!-- Brand -->
40 <div class="sa-auth-brand">
41 <div class="sa-auth-logo">
42 <i class="bi bi-airplane-fill"></i>
43 </div>
44 <h1 class="sa-text-primary">SplitApp</h1>
45 <p>{{ t('auth.login.welcomeBack') }}</p>
46 </div>
47
48 <!-- Errors -->
49 <div v-if="errors.length" class="sa-card-static sa-card-accent sa-card-accent-danger mb-4">
50 <div class="sa-card-body" style="padding: var(--sa-space-3) var(--sa-space-4)">
51 <div v-for="error in errors" :key="error" style="font-size: 0.875rem">
52 <i class="bi bi-exclamation-circle me-1"></i>{{ error }}
53 </div>
54 </div>
55 </div>
56
57 <!-- Form -->
58 <form @submit.prevent="handleLogin">
59 <div class="mb-3">
60 <label for="email" class="form-label">{{ t('auth.login.email') }}</label>
61 <input
62 id="email"
63 v-model="email"
64 type="email"
65 class="form-control"
66 :placeholder="t('auth.login.emailPlaceholder')"
67 required
68 autocomplete="email"
69 />
70 </div>
71
72 <div class="mb-4">
73 <label for="password" class="form-label">{{ t('auth.login.password') }}</label>
74 <input
75 id="password"
76 v-model="password"
77 type="password"
78 class="form-control"
79 :placeholder="t('auth.login.passwordPlaceholder')"
80 required
81 autocomplete="current-password"
82 />
83 </div>
84
85 <button
86 type="submit"
87 class="sa-btn sa-btn-primary w-100"
88 :class="{ 'sa-btn-loading': isLoading }"
89 :disabled="isLoading"
90 >
91 {{ isLoading ? t('auth.login.signingIn') : t('auth.login.signIn') }}
92 </button>
93 </form>
94
95 <!-- Register link -->
96 <div class="text-center mt-4" style="font-size: 0.938rem; color: var(--sa-gray-500)">
97 {{ t('auth.login.noAccount') }}
98 <RouterLink :to="{ name: 'Register' }" style="font-weight: 600">{{ t('auth.login.createOne') }}</RouterLink>
99 </div>
100 </div>
101 </div>
102 </div>
103 </template>
104
105 <style scoped>
106 .sa-auth-logo {
107 width: 56px;
108 height: 56px;
109 border-radius: 16px;
110 background: linear-gradient(135deg, #e8604c 0%, #d4456a 100%);
111 display: flex;
112 align-items: center;
113 justify-content: center;
114 font-size: 1.5rem;
115 color: #fff;
116 margin: 0 auto 16px;
117 }
118 </style>
119