Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 | 13x 43x 13x 1x 1x 7x 1x 1x 7x 13x 1x 1x 1x 1x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 8x 8x 8x 8x 4x 4x 8x | <template>
<AuthLayout
:presentation-title="t('auth.presentationTitleLogin')"
:presentation-subtitle="t('auth.presentationSubtitleLogin')"
>
<span>{{ t('auth.newToFlows') }}</span>
<router-link to="/signup" class="btn btn-success shadow p-2 my-3 w-100">
{{ t('auth.tryItFree') }}
</router-link>
<form class="signin-form" @submit.prevent="handleLogin">
<hr class="w-100" />
<span class="mb-2">{{ t('auth.alreadyMember') }}</span>
<label for="email">{{ t('auth.email') }}</label>
<input
id="email"
v-model="email"
class="w-100"
type="email"
name="username"
autocomplete="username"
required
/>
<label for="password">{{ t('auth.password') }}</label>
<div class="password-wrapper">
<input
id="password"
v-model="password"
class="w-100"
:type="showPassword ? 'text' : 'password'"
name="password"
autocomplete="current-password"
required
/>
<button
type="button"
class="password-toggle"
:aria-label="showPassword ? t('auth.hidePassword') : t('auth.showPassword')"
@click="showPassword = !showPassword"
>
<FontAwesomeIcon :icon="showPassword ? 'eye-slash' : 'eye'" />
</button>
</div>
<router-link to="/forgot-password" class="forgot-password">{{
t('auth.forgotPassword')
}}</router-link>
<button type="submit" class="btn btn-secondary shadow p-2 my-3" :disabled="loading">
{{ t('auth.signIn') }}
</button>
<div v-if="error" class="alert alert-danger" role="alert">
{{ error }}
</div>
<div v-if="resetSuccess" class="alert alert-success" role="alert">
{{ t('auth.resetPasswordSuccess') }}
</div>
</form>
<div class="row vertical-center">
<div class="col-5"><hr /></div>
<div class="col-2 separator-text">{{ t('auth.or') }}</div>
<div class="col-5"><hr /></div>
</div>
<a href="#" class="btn btn-light signin-google shadow p-2 my-3 w-100" @click.prevent>
<span class="icon">
<svg
width="18"
height="18"
viewBox="0 0 256 262"
xmlns="http://www.w3.org/2000/svg"
preserveAspectRatio="xMidYMid"
>
<path
d="M254.69 106.22H129.775v51.002h73.14c-3.666 15.153-8.197 20.653-12.692 25.788-5.01 5.702-9.894 10.653-15.43 14.717l-1.413 2.294 43.225 30.734 2.993-1.17c22.808-22.018 36.388-55.09 36.388-98.727 0-8.456.243-16.632-1.297-24.638"
fill="#4486F4"
/>
<path
d="M130.965 261.93c35.635 0 66.484-10.96 88.633-32.345l-44.805-31.858c-10.44 7.663-23.235 12.12-43.828 12.12-34.67 0-64.096-22.38-74.674-53.472l-2.52-1.466-39.713 32.06.18 3.354c21.65 42.487 65.774 71.605 116.728 71.605"
fill="#34A853"
/>
<path
d="M52.083 130.965c0-9.032 1.538-17.7 4.333-25.78l.273-2.36L15.08 71.47l-1.506 1.43C4.9 90.4 0 110.106 0 130.964c0 21.375 5.154 41.536 14.237 59.36l42.054-33.95c-2.713-7.976-4.207-16.516-4.207-25.41"
fill="#FBBC05"
/>
<path
d="M56.416 105.185c10.685-30.9 40.015-53.102 74.55-53.102 19.68 0 37.663 7.225 51.482 19.146l36.562-37.22C195.753 12.88 164.863 0 130.965 0 79.493 0 34.98 29.708 13.575 72.9l42.84 32.285"
fill="#EC4235"
/>
</svg>
</span>
<span class="text">{{ t('auth.signInWithGoogle') }}</span>
</a>
<a href="#" class="btn btn-light signin-facebook shadow p-2 mt-2 w-100" @click.prevent>
<span class="icon">
<svg width="18" height="18" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
<path
d="M17.007 18H12.42v-6.97h2.34l.35-2.717h-2.69V6.578c0-.786.218-1.322 1.346-1.322h1.44v-2.43a19.322 19.322 0 0 0-2.097-.107c-2.075 0-3.495 1.265-3.495 3.59v2.003H7.27v2.716h2.345V18H.993A.994.994 0 0 1 0 17.006V.994C0 .444.445 0 .993 0h16.014c.548 0 .993.445.993.994v16.012c0 .55-.445.994-.993.994z"
fill="#43619C"
fill-rule="evenodd"
/>
</svg>
</span>
<span class="text">{{ t('auth.signInWithFacebook') }}</span>
</a>
</AuthLayout>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { useAuthStore } from '@/stores/auth'
import { useUILanguage } from '@/composables/useUILanguage'
import AuthLayout from '@/components/AuthLayout.vue'
const router = useRouter()
const route = useRoute()
const authStore = useAuthStore()
const { t } = useUILanguage()
const email = ref('')
const password = ref('')
const showPassword = ref(false)
const loading = ref(false)
const error = ref('')
const resetSuccess = ref(false)
onMounted(() => {
// Check if redirected from password reset
Iif (route.query.resetSuccess === 'true') {
resetSuccess.value = true
// Clear query param from URL
router.replace({ query: {} })
}
})
async function handleLogin() {
loading.value = true
error.value = ''
try {
await authStore.login(email.value, password.value)
router.push('/')
} catch (err: any) {
error.value = err.message || 'Login failed'
} finally {
loading.value = false
}
}
</script>
<style scoped>
.icon {
align-items: center;
justify-content: center;
margin-right: 8px;
}
.forgot-password {
font-size: small;
}
.separator-text {
text-align: center;
color: #666666;
font-size: small;
}
.password-wrapper {
position: relative;
width: 100%;
}
.password-toggle {
position: absolute;
right: 8px;
top: 50%;
transform: translateY(-50%);
background: none;
border: none;
cursor: pointer;
color: #666;
padding: 0 4px;
line-height: 1;
}
.password-toggle:hover {
color: #333;
}
</style>
|