fee4f9add8
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
316 lines
8.0 KiB
Vue
316 lines
8.0 KiB
Vue
<template>
|
|
<section
|
|
class="container mx-auto max-w-screen-sm"
|
|
v-if="!currentUser?.isLoggedIn"
|
|
>
|
|
<h1 class="text-4xl">{{ t("Welcome back!") }}</h1>
|
|
<o-notification
|
|
v-if="errorCode === LoginErrorCode.NEED_TO_LOGIN"
|
|
title="Info"
|
|
variant="info"
|
|
:aria-close-label="t('Close')"
|
|
>{{ t("You need to login.") }}</o-notification
|
|
>
|
|
<o-notification
|
|
v-else-if="errorCode === LoginError.LOGIN_PROVIDER_ERROR"
|
|
variant="danger"
|
|
:aria-close-label="t('Close')"
|
|
>{{
|
|
t("Error while login with {provider}. Retry or login another way.", {
|
|
provider: currentProvider,
|
|
})
|
|
}}</o-notification
|
|
>
|
|
<o-notification
|
|
v-else-if="errorCode === LoginError.LOGIN_PROVIDER_NOT_FOUND"
|
|
variant="danger"
|
|
:aria-close-label="t('Close')"
|
|
>{{
|
|
t(
|
|
"Error while login with {provider}. This login provider doesn't exist.",
|
|
{
|
|
provider: currentProvider,
|
|
}
|
|
)
|
|
}}</o-notification
|
|
>
|
|
<o-notification
|
|
:title="t('Error')"
|
|
variant="danger"
|
|
v-for="error in errors"
|
|
:key="error"
|
|
>
|
|
{{ error }}
|
|
</o-notification>
|
|
<form @submit="loginAction">
|
|
<o-field
|
|
:label="t('Email')"
|
|
label-for="email"
|
|
:message="caseWarningText"
|
|
:type="caseWarningType"
|
|
>
|
|
<o-input
|
|
aria-required="true"
|
|
required
|
|
id="email"
|
|
type="email"
|
|
v-model="credentials.email"
|
|
/>
|
|
</o-field>
|
|
|
|
<o-field :label="t('Password')" label-for="password">
|
|
<o-input
|
|
aria-required="true"
|
|
id="password"
|
|
required
|
|
type="password"
|
|
password-reveal
|
|
v-model="credentials.password"
|
|
/>
|
|
</o-field>
|
|
|
|
<p class="text-center my-2">
|
|
<o-button
|
|
variant="primary"
|
|
size="large"
|
|
native-type="submit"
|
|
:disabled="submitted"
|
|
>
|
|
{{ t("Login") }}
|
|
</o-button>
|
|
</p>
|
|
<!-- <o-loading :is-full-page="false" v-model="submitted" /> -->
|
|
|
|
<div
|
|
class="control"
|
|
v-if="config && config?.auth.oauthProviders.length > 0"
|
|
>
|
|
<auth-providers :oauthProviders="config.auth.oauthProviders" />
|
|
</div>
|
|
|
|
<div class="flex flex-wrap gap-2 mt-3">
|
|
<o-button
|
|
tag="router-link"
|
|
variant="text"
|
|
:to="{
|
|
name: RouteName.SEND_PASSWORD_RESET,
|
|
params: { email: credentials.email },
|
|
}"
|
|
>{{ t("Forgot your password?") }}</o-button
|
|
>
|
|
<o-button
|
|
tag="router-link"
|
|
variant="text"
|
|
:to="{
|
|
name: RouteName.RESEND_CONFIRMATION,
|
|
params: { email: credentials.email },
|
|
}"
|
|
>{{ t("Didn't receive the instructions?") }}</o-button
|
|
>
|
|
<p class="control" v-if="config && config.registrationsOpen">
|
|
<o-button
|
|
tag="router-link"
|
|
variant="text"
|
|
:to="{
|
|
name: RouteName.REGISTER,
|
|
params: {
|
|
default_email: credentials.email,
|
|
default_password: credentials.password,
|
|
},
|
|
}"
|
|
>{{ t("Create an account") }}</o-button
|
|
>
|
|
</p>
|
|
</div>
|
|
</form>
|
|
</section>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { LOGIN } from "@/graphql/auth";
|
|
import { LOGIN_CONFIG } from "@/graphql/config";
|
|
import { UPDATE_CURRENT_USER_CLIENT } from "@/graphql/user";
|
|
import { IConfig } from "@/types/config.model";
|
|
import { ILogin } from "@/types/login.model";
|
|
import { saveUserData, SELECTED_PROVIDERS } from "@/utils/auth";
|
|
import {
|
|
initializeCurrentActor,
|
|
NoIdentitiesException,
|
|
} from "@/utils/identity";
|
|
import { useMutation, useQuery } from "@vue/apollo-composable";
|
|
import { computed, reactive, ref, onMounted } from "vue";
|
|
import { useI18n } from "vue-i18n";
|
|
import { useRoute, useRouter } from "vue-router";
|
|
import AuthProviders from "@/components/User/AuthProviders.vue";
|
|
import RouteName from "@/router/name";
|
|
import { LoginError, LoginErrorCode } from "@/types/enums";
|
|
import { useCurrentUserClient } from "@/composition/apollo/user";
|
|
import { useHead } from "@vueuse/head";
|
|
|
|
const props = withDefaults(
|
|
defineProps<{
|
|
email?: string;
|
|
password?: string;
|
|
}>(),
|
|
{ email: "", password: "" }
|
|
);
|
|
|
|
const { t } = useI18n({ useScope: "global" });
|
|
const router = useRouter();
|
|
const route = useRoute();
|
|
|
|
const { currentUser } = useCurrentUserClient();
|
|
|
|
const { result: configResult } = useQuery<{
|
|
config: Pick<IConfig, "auth" | "registrationsOpen">;
|
|
}>(LOGIN_CONFIG);
|
|
|
|
const config = computed(() => configResult.value?.config);
|
|
|
|
const errors = ref<string[]>([]);
|
|
const submitted = ref(false);
|
|
|
|
const credentials = reactive({
|
|
email: "",
|
|
password: "",
|
|
});
|
|
|
|
const redirect = ref<string | undefined>("");
|
|
|
|
const errorCode = ref<LoginErrorCode | null>(null);
|
|
|
|
const {
|
|
onDone: onLoginMutationDone,
|
|
onError: onLoginMutationError,
|
|
mutate: loginMutation,
|
|
} = useMutation(LOGIN);
|
|
|
|
onLoginMutationDone(async (result) => {
|
|
const data = result.data;
|
|
submitted.value = false;
|
|
if (data == null) {
|
|
throw new Error("Data is undefined");
|
|
}
|
|
|
|
saveUserData(data.login);
|
|
await setupClientUserAndActors(data.login);
|
|
|
|
if (redirect.value) {
|
|
router.push(redirect.value);
|
|
return;
|
|
}
|
|
console.debug("No redirect, going to homepage");
|
|
if (window.localStorage) {
|
|
console.debug("Has localstorage, setting welcome back");
|
|
window.localStorage.setItem("welcome-back", "yes");
|
|
}
|
|
router.replace({ name: RouteName.HOME });
|
|
return;
|
|
});
|
|
|
|
onLoginMutationError((err) => {
|
|
console.error(err);
|
|
submitted.value = false;
|
|
if (err.graphQLErrors) {
|
|
err.graphQLErrors.forEach(({ message }: { message: string }) => {
|
|
errors.value.push(message);
|
|
});
|
|
} else if (err.networkError) {
|
|
errors.value.push(err.networkError.message);
|
|
}
|
|
});
|
|
|
|
const loginAction = (e: Event) => {
|
|
e.preventDefault();
|
|
if (submitted.value) {
|
|
return;
|
|
}
|
|
|
|
submitted.value = true;
|
|
errors.value = [];
|
|
|
|
loginMutation({
|
|
email: credentials.email,
|
|
password: credentials.password,
|
|
});
|
|
};
|
|
|
|
const { onDone: onCurrentUserMutationDone, mutate: updateCurrentUserMutation } =
|
|
useMutation(UPDATE_CURRENT_USER_CLIENT);
|
|
|
|
onCurrentUserMutationDone(async () => {
|
|
try {
|
|
await initializeCurrentActor();
|
|
} catch (err: any) {
|
|
if (err instanceof NoIdentitiesException && currentUser.value) {
|
|
await router.push({
|
|
name: RouteName.REGISTER_PROFILE,
|
|
params: {
|
|
email: currentUser.value.email,
|
|
userAlreadyActivated: "true",
|
|
},
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
const setupClientUserAndActors = async (login: ILogin): Promise<void> => {
|
|
updateCurrentUserMutation({
|
|
id: login.user.id,
|
|
email: credentials.email,
|
|
isLoggedIn: true,
|
|
role: login.user.role,
|
|
});
|
|
};
|
|
|
|
const hasCaseWarning = computed<boolean>(() => {
|
|
return credentials.email !== credentials.email.toLowerCase();
|
|
});
|
|
|
|
const caseWarningText = computed<string | undefined>(() => {
|
|
if (hasCaseWarning.value) {
|
|
return t(
|
|
"Emails usually don't contain capitals, make sure you haven't made a typo."
|
|
) as string;
|
|
}
|
|
return undefined;
|
|
});
|
|
|
|
const caseWarningType = computed<string | undefined>(() => {
|
|
if (hasCaseWarning.value) {
|
|
return "warning";
|
|
}
|
|
return undefined;
|
|
});
|
|
|
|
const currentProvider = computed(() => {
|
|
const queryProvider = route?.query.provider as string | undefined;
|
|
if (queryProvider) {
|
|
return SELECTED_PROVIDERS[queryProvider];
|
|
}
|
|
return "unknown provider";
|
|
});
|
|
|
|
onMounted(() => {
|
|
credentials.email = props.email;
|
|
credentials.password = props.password;
|
|
|
|
const query = route?.query;
|
|
errorCode.value = query?.code as LoginErrorCode;
|
|
redirect.value = query?.redirect as string | undefined;
|
|
|
|
// Already-logged-in and accessing /login
|
|
if (currentUser.value?.isLoggedIn) {
|
|
console.debug(
|
|
"Current user is already logged-in, redirecting to Homepage",
|
|
currentUser
|
|
);
|
|
router.push("/");
|
|
}
|
|
});
|
|
|
|
useHead({
|
|
title: computed(() => t("Login")),
|
|
});
|
|
</script>
|