feat: login

This commit is contained in:
Fadhli Syaifullah 2024-06-04 22:54:37 +07:00
parent 8a9bad2eb3
commit 5e0f9e4cd8
10 changed files with 246 additions and 1 deletions

View File

@ -1,3 +1,3 @@
<template> <template>
<h1>Asia Golf</h1> <RouterView />
</template> </template>

View File

@ -0,0 +1,14 @@
<script setup lang="ts">
import Logo from "@/assets/images/logo-white.jpg";
import LoginForm from "@/features/auth/components/LoginForm.vue";
</script>
<template>
<div
class="mx-auto border bg-white p-8 shadow-xl shadow-white/20 md:w-[500px] md:rounded-lg"
>
<img :src="Logo" title="Asia Golf" alt="asia-golf" class="w-full" />
<hr class="my-8" />
<login-form />
</div>
</template>

View File

@ -0,0 +1,108 @@
<script setup lang="ts">
import { reactive } from "vue";
import { useRouter } from "vue-router";
import { useForm } from "vee-validate";
import { object, string } from "zod";
import { toTypedSchema } from "@vee-validate/zod";
import { useLogin } from "@/features/auth/composables/useLogin";
import LoginSuccess from "@/features/auth/components/partials/feedbacks/LoginSuccess.vue";
import LoginFailed from "@/features/auth/components/partials/feedbacks/LoginFailed.vue";
import { sleep } from "@/helpers/sleep";
const router = useRouter();
const { login } = useLogin();
const state = reactive({
showPassword: false,
loggingIn: false,
disableForm: false,
showLoginSuccessFeedback: false,
showLoginFailedFeedback: false,
});
const { errors, handleSubmit, defineField } = useForm({
validationSchema: toTypedSchema(
object({
email: string({ message: "Email is required" }),
password: string({ message: "Password is required" }),
}),
),
});
const [email, emailAttrs] = defineField("email");
const [password, passwordAttrs] = defineField("password");
const onSubmit = handleSubmit(async (values) => {
try {
state.disableForm = true;
state.showLoginSuccessFeedback = false;
state.showLoginFailedFeedback = false;
state.loggingIn = true;
await login(values);
state.showLoginSuccessFeedback = true;
await sleep(2000);
router.push("/");
} catch (e) {
state.showLoginFailedFeedback = true;
state.disableForm = false;
} finally {
state.loggingIn = false;
}
});
function handleToggleShowPassword() {
if (state.showPassword) {
state.showPassword = false;
return;
}
state.showPassword = true;
}
</script>
<template>
<v-form @submit="onSubmit">
<login-success v-if="state.showLoginSuccessFeedback" />
<login-failed v-if="state.showLoginFailedFeedback" />
<div class="mb-2">
<label for="email" class="mb-2 font-semibold">Email</label>
<v-text-field
type="email"
placeholder="Enter your email"
variant="outlined"
rounded="md"
class="mb-3 mt-2"
:error-messages="errors.email"
:disabled="state.disableForm"
v-model="email"
v-bind="emailAttrs"
></v-text-field>
</div>
<div class="mb-2">
<label for="password" class="mb-2 font-semibold">Password</label>
<v-text-field
:type="state.showPassword ? 'text' : 'password'"
placeholder="Enter your password"
variant="outlined"
rounded="md"
class="mb-3 mt-2"
:append-inner-icon="state.showPassword ? 'mdi-eye' : 'mdi-eye-off'"
:error-messages="errors.password"
:disabled="state.disableForm"
v-model="password"
v-bind="passwordAttrs"
@click:append-inner="handleToggleShowPassword"
></v-text-field>
</div>
<v-btn
type="submit"
variant="tonal"
rounded="xl"
class="bg-black p-6 text-white"
:loading="state.loggingIn"
:disabled="state.disableForm"
block
>
Login
</v-btn>
</v-form>
</template>

View File

@ -0,0 +1,11 @@
<template>
<v-alert
closable
icon="mdi-alert-circle-outline"
title="Failed to login"
text="Please check your account credentials"
type="error"
variant="tonal"
class="mb-8"
></v-alert>
</template>

View File

@ -0,0 +1,11 @@
<template>
<v-alert
closable
icon="mdi-check-circle-outline"
title="Login success"
text="Please wait, redirecting..."
type="success"
variant="tonal"
class="mb-8"
></v-alert>
</template>

View File

@ -0,0 +1,7 @@
import { useAuth } from "@/features/auth/stores/useAuth";
export function useCurrentUser() {
const { auth } = useAuth();
return { user: auth.auth?.user };
}

View File

@ -0,0 +1,11 @@
<script setup lang="ts">
import LoginCard from "@/features/auth/components/LoginCard.vue";
</script>
<template>
<div class="h-dvh w-full bg-black">
<main class="flex h-full justify-center md:items-center">
<login-card />
</main>
</div>
</template>

View File

@ -10,6 +10,7 @@ import * as components from "vuetify/components";
import * as directives from "vuetify/directives"; import * as directives from "vuetify/directives";
import App from "@/App.vue"; import App from "@/App.vue";
import { router } from "@/router";
import "@mdi/font/css/materialdesignicons.css"; import "@mdi/font/css/materialdesignicons.css";
import "@fontsource/poppins"; import "@fontsource/poppins";
@ -33,5 +34,6 @@ const app = createApp(App);
app.use(vuetify); app.use(vuetify);
app.use(pinia); app.use(pinia);
app.use(router);
app.use(VueQueryPlugin); app.use(VueQueryPlugin);
app.mount("#app"); app.mount("#app");

View File

@ -0,0 +1,52 @@
<script setup lang="ts">
import { reactive } from "vue";
import { useRouter } from "vue-router";
import { useCurrentUser } from "@/features/auth/composables/useCurrentUser";
import { useLogout } from "@/features/auth/composables/useLogout";
import Logo from "@/assets/images/logo-white.jpg";
const router = useRouter();
const { user } = useCurrentUser();
const { logout } = useLogout();
const state = reactive({ isLoggingOut: false });
async function handleLogout() {
try {
state.isLoggingOut = true;
await logout();
router.push("/login");
} catch (e) {
state.isLoggingOut = false;
}
}
</script>
<template>
<div
class="flex items-center justify-between gap-4 border-b bg-slate-50/50 p-4"
>
<RouterLink to="/"
><img :src="Logo" title="Asia Golf" alt="asia-golf" class="h-8"
/></RouterLink>
<div class="flex-1"></div>
<v-tooltip :text="user?.name" location="bottom">
<template v-slot:activator="{ props }">
<v-avatar
:image="user?.photo"
class="border"
v-bind="props"
></v-avatar> </template
></v-tooltip>
<v-btn
type="submit"
variant="tonal"
rounded="xl"
class="bg-black text-white"
:loading="state.isLoggingOut"
@click="handleLogout"
>
Logout
</v-btn>
</div>
</template>

29
src/router.ts Normal file
View File

@ -0,0 +1,29 @@
import { createRouter, createWebHistory } from "vue-router";
import { useAuth } from "@/features/auth/stores/useAuth";
import LoginPage from "@/features/auth/pages/LoginPage.vue";
import DashboardPage from "@/pages/DashboardPage.vue";
const routes = [
{ path: "/", component: DashboardPage },
{ path: "/login", component: LoginPage },
];
export const router = createRouter({
history: createWebHistory(),
routes,
});
router.beforeEach((to, _, next) => {
const { isAuthenticated } = useAuth();
if (to.path === "/login" && isAuthenticated) {
next({ path: "/" });
return;
}
if (to.path !== "/login" && !isAuthenticated) {
next({ path: "/login" });
return;
} else next();
});