All files / src/views LoginPage.vue

89.74% Statements 35/39
85.71% Branches 30/35
80% Functions 8/10
89.74% Lines 35/39

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  14x       48x   14x         1x       8x               1x     8x                                   14x                     1x               1x   1x                                                                   1x                         14x 14x 14x 14x   14x 14x 14x 14x 14x 14x 14x   14x   14x               9x 9x 9x   9x 9x 4x     5x       5x 5x       9x                                            
<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" class="form-label">{{ t('auth.email') }}</label>
      <input
        id="email"
        v-model="email"
        class="form-control"
        type="email"
        name="username"
        autocomplete="username"
        required
      />
      <label for="password" class="form-label">{{ t('auth.password') }}</label>
      <div class="password-wrapper">
        <input
          id="password"
          v-model="password"
          class="form-control pe-5"
          :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 v-if="isNotConfirmedError" class="mt-2">
          <router-link
            :to="{ path: '/signup', query: { email, confirm: 'true' } }"
            class="btn btn-sm btn-secondary w-100"
          >
            {{ t('auth.confirmYourAccount') }}
          </router-link>
        </div>
      </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="authStore.signInWithGoogle()"
    >
      <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>
  </AuthLayout>
</template>
 
<script setup lang="ts">
// @implements UC-AUTH-002.1
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 isNotConfirmedError = ref(false)
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 = ''
  isNotConfirmedError.value = false
 
  try {
    await authStore.login(email.value, password.value)
    router.push('/')
  } catch (err: any) {
    const isUserNotConfirmedError =
      err?.name === 'UserNotConfirmedException' ||
      err?.code === 'UserNotConfirmedException' ||
      err?.message === 'User is not confirmed.'
 
    isNotConfirmedError.value = isUserNotConfirmedError
    error.value = isUserNotConfirmedError
      ? t('auth.userNotConfirmed')
      : 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;
}
</style>