developed
This commit is contained in:
parent
2b4e6b902a
commit
257021fe55
|
|
@ -0,0 +1,77 @@
|
|||
<template>
|
||||
<!-- BEGIN: Languages -->
|
||||
<Dropdown class="intro-x w-8">
|
||||
<DropdownToggle
|
||||
tag="div"
|
||||
role="button"
|
||||
class="w-6 h-6 rounded-full overflow-hidden shadow-lg image-fit zoom-in scale-110"
|
||||
>
|
||||
<img alt="flag" :src="`/src/assets/images/flags/${selectedLang}.png`" />
|
||||
</DropdownToggle>
|
||||
|
||||
<DropdownMenu class="pt-2">
|
||||
<DropdownContent tag="div">
|
||||
<div
|
||||
v-for="(lang, key) in languages"
|
||||
:key="key"
|
||||
class="cursor-pointer relative flex items-center"
|
||||
:class="{ 'mt-5': key }"
|
||||
@click="onLanguageChanged(lang.code)"
|
||||
>
|
||||
<div class="w-5 h-5 flex-none image-fit mr-1">
|
||||
<img
|
||||
alt="Icewall Tailwind HTML Admin Template"
|
||||
class="rounded-full"
|
||||
:src="`/src/assets/images/flags/${lang.code}.png`"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="ml-2 overflow-hidden">
|
||||
<div class="flex items-center">
|
||||
<a href="javascript:;" class="font-medium truncate mr-5">{{
|
||||
lang.label
|
||||
}}</a>
|
||||
<!-- <a class="font-medium truncate mr-5">Rus Dili</a> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DropdownContent>
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
<!-- END: Languages -->
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onBeforeMount } from "vue";
|
||||
import { SELECTED_LANG } from "@/helpers";
|
||||
import i18n from "@/i18n";
|
||||
|
||||
const selectedLang = ref("");
|
||||
const languages = [
|
||||
{
|
||||
code: "tm",
|
||||
label: "Türkmen dili",
|
||||
},
|
||||
{
|
||||
code: "ru",
|
||||
label: "Русский",
|
||||
},
|
||||
{
|
||||
code: "en",
|
||||
label: "English",
|
||||
},
|
||||
];
|
||||
|
||||
const onLanguageChanged = (val) => {
|
||||
selectedLang.value = val;
|
||||
i18n.global.locale = val;
|
||||
localStorage.setItem(SELECTED_LANG, val);
|
||||
};
|
||||
|
||||
onBeforeMount(() => {
|
||||
const lang = localStorage.getItem(SELECTED_LANG);
|
||||
|
||||
if (lang) selectedLang.value = lang;
|
||||
else selectedLang.value = "tm";
|
||||
});
|
||||
</script>
|
||||
|
|
@ -65,49 +65,7 @@
|
|||
</Dropdown>
|
||||
<!-- END: Notifications -->
|
||||
|
||||
<!-- BEGIN: Languages -->
|
||||
<Dropdown class="intro-x w-8 mr-4 sm:mr-6">
|
||||
<DropdownToggle
|
||||
tag="div"
|
||||
role="button"
|
||||
class="w-6 h-6 rounded-full overflow-hidden shadow-lg image-fit zoom-in scale-110"
|
||||
>
|
||||
<img
|
||||
alt="flag"
|
||||
:src="`/src/assets/images/flags/${selectedLang}.png`"
|
||||
/>
|
||||
</DropdownToggle>
|
||||
|
||||
<DropdownMenu class="pt-2">
|
||||
<DropdownContent tag="div">
|
||||
<div
|
||||
v-for="(lang, key) in languages"
|
||||
:key="key"
|
||||
class="cursor-pointer relative flex items-center"
|
||||
:class="{ 'mt-5': key }"
|
||||
@click="onLanguageChanged(lang.code)"
|
||||
>
|
||||
<div class="w-5 h-5 flex-none image-fit mr-1">
|
||||
<img
|
||||
alt="Icewall Tailwind HTML Admin Template"
|
||||
class="rounded-full"
|
||||
:src="`/src/assets/images/flags/${lang.code}.png`"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="ml-2 overflow-hidden">
|
||||
<div class="flex items-center">
|
||||
<a href="javascript:;" class="font-medium truncate mr-5">{{
|
||||
lang.label
|
||||
}}</a>
|
||||
<!-- <a class="font-medium truncate mr-5">Rus Dili</a> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DropdownContent>
|
||||
</DropdownMenu>
|
||||
</Dropdown>
|
||||
<!-- END: Languages -->
|
||||
<Lang class="mr-4" />
|
||||
|
||||
<!-- BEGIN: Account Menu -->
|
||||
<Dropdown class="intro-x">
|
||||
|
|
@ -163,40 +121,9 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onBeforeMount } from "vue";
|
||||
import { useAuthStore } from "@/stores";
|
||||
import { SELECTED_LANG } from "@/helpers";
|
||||
import i18n from "@/i18n";
|
||||
import Logo from "@/components/logo/Main.vue";
|
||||
|
||||
const searchDropdown = ref(false);
|
||||
const showSearchDropdown = () => {
|
||||
searchDropdown.value = true;
|
||||
};
|
||||
const hideSearchDropdown = () => {
|
||||
searchDropdown.value = false;
|
||||
};
|
||||
|
||||
const selectedLang = ref("");
|
||||
const languages = [
|
||||
{
|
||||
code: "tm",
|
||||
label: "Türkmen dili",
|
||||
},
|
||||
{
|
||||
code: "ru",
|
||||
label: "Русский",
|
||||
},
|
||||
{
|
||||
code: "en",
|
||||
label: "English",
|
||||
},
|
||||
];
|
||||
const onLanguageChanged = (val) => {
|
||||
selectedLang.value = val;
|
||||
i18n.global.locale = val;
|
||||
localStorage.setItem(SELECTED_LANG, val);
|
||||
};
|
||||
import Lang from "@/components/lang/Main.vue";
|
||||
|
||||
const onLogout = () => {
|
||||
console.log("Logout");
|
||||
|
|
@ -204,11 +131,4 @@ const onLogout = () => {
|
|||
const authStore = useAuthStore();
|
||||
return authStore.logout();
|
||||
};
|
||||
|
||||
onBeforeMount(() => {
|
||||
const lang = localStorage.getItem(SELECTED_LANG);
|
||||
|
||||
if (lang) selectedLang.value = lang;
|
||||
else selectedLang.value = "tm";
|
||||
});
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -2,4 +2,5 @@ export const locale = {
|
|||
GENERAL_REPORT: "General Report",
|
||||
ERROR: "Error",
|
||||
SUCCESS: "Success",
|
||||
SIGN_IN: "Sign In",
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,4 +2,5 @@ export const locale = {
|
|||
GENERAL_REPORT: "Общий отчет",
|
||||
ERROR: "Ошибка",
|
||||
SUCCESS: "Успех",
|
||||
SIGN_IN: "Войти",
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,4 +2,5 @@ export const locale = {
|
|||
GENERAL_REPORT: "Umumy hasabat",
|
||||
ERROR: "Ýalňyşlyk",
|
||||
SUCCESS: "Üstünlik",
|
||||
SIGN_IN: "Giriň",
|
||||
};
|
||||
|
|
|
|||
|
|
@ -116,7 +116,12 @@ const router = createRouter({
|
|||
|
||||
router.beforeEach(async (to) => {
|
||||
// redirect to login page if not logged in and trying to access a restricted page
|
||||
const publicPages = ["/login", "/register", "/email-verify"];
|
||||
const publicPages = [
|
||||
"/login",
|
||||
"/register",
|
||||
"/email-verify",
|
||||
"/update-password",
|
||||
];
|
||||
const authRequired = !publicPages.includes(to.path);
|
||||
const authStore = useAuthStore();
|
||||
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ export const useAuthStore = defineStore({
|
|||
async register(newUser) {
|
||||
try {
|
||||
await fetchWrapper.post(`${baseUrl}/register`, newUser);
|
||||
|
||||
|
||||
// redirect to Email verification page
|
||||
router.push({ path: "/email-verify" });
|
||||
} catch (error) {
|
||||
|
|
@ -55,7 +55,10 @@ export const useAuthStore = defineStore({
|
|||
|
||||
async verifyEmail(email, token) {
|
||||
try {
|
||||
const response = await fetchWrapper.post(`${baseUrl}/verify-email`, { email, token })
|
||||
const response = await fetchWrapper.post(`${baseUrl}/verify-email`, {
|
||||
email,
|
||||
token,
|
||||
});
|
||||
|
||||
// update pinia state
|
||||
this.user = response["data"];
|
||||
|
|
@ -65,7 +68,6 @@ export const useAuthStore = defineStore({
|
|||
|
||||
// redirect to home page
|
||||
router.push({ path: "/" });
|
||||
|
||||
} catch (error) {
|
||||
// const alertStore = useAlertStore();
|
||||
// alertStore.error(error);
|
||||
|
|
@ -74,24 +76,43 @@ export const useAuthStore = defineStore({
|
|||
|
||||
async updateClient(client) {
|
||||
try {
|
||||
const response = await fetchWrapper.post(`${baseUrl}/update-client`, client);
|
||||
const response = await fetchWrapper.post(
|
||||
`${baseUrl}/update-client`,
|
||||
client
|
||||
);
|
||||
|
||||
const updatedClient = response["data"];
|
||||
updatedClient["token"] = this.user.token
|
||||
updatedClient["token"] = this.user.token;
|
||||
|
||||
// update pinia state
|
||||
this.user = updatedClient
|
||||
|
||||
this.user = updatedClient;
|
||||
|
||||
// store user details and jwt in local storage to keep user logged in between page refreshes
|
||||
localStorage.setItem(USER, JSON.stringify(this.user));
|
||||
|
||||
const alertStore = useAlertStore();
|
||||
// trigger alert
|
||||
alertStore.success('Successfully updated');
|
||||
alertStore.success("Successfully updated");
|
||||
} catch (error) {
|
||||
// const alertStore = useAlertStore();
|
||||
// alertStore.error(error);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
async forgotPassword(email) {
|
||||
try {
|
||||
const response = await fetchWrapper.post(`${baseUrl}/forgot-password`, {
|
||||
email,
|
||||
});
|
||||
|
||||
console.log("forgot password", response);
|
||||
|
||||
// redirect to home page
|
||||
router.push({ path: "/update-password" });
|
||||
} catch (error) {
|
||||
// const alertStore = useAlertStore();
|
||||
// alertStore.error(error);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -46,11 +46,15 @@
|
|||
<div
|
||||
class="my-auto mx-auto xl:ml-20 bg-white dark:bg-darkmode-600 xl:bg-transparent px-5 sm:px-8 py-8 xl:p-0 rounded-md shadow-md xl:shadow-none w-full sm:w-3/4 lg:w-2/4 xl:w-auto"
|
||||
>
|
||||
<h2
|
||||
class="intro-x font-bold text-2xl xl:text-3xl text-center xl:text-left"
|
||||
>
|
||||
Sign In
|
||||
</h2>
|
||||
<div class="flex items-center justify-between">
|
||||
<h2
|
||||
class="intro-x font-bold text-2xl xl:text-3xl text-center xl:text-left"
|
||||
>
|
||||
{{ $t("SIGN_IN") }}
|
||||
</h2>
|
||||
<Lang />
|
||||
</div>
|
||||
|
||||
<div class="intro-x mt-2 text-slate-400 xl:hidden text-center">
|
||||
A few more clicks to sign in to your account. Manage all your
|
||||
e-commerce accounts in one place
|
||||
|
|
@ -78,6 +82,7 @@
|
|||
class="intro-x login__input form-control py-3 px-4 block mt-4"
|
||||
:class="{ 'border-danger': validate.password.$error }"
|
||||
placeholder="Password"
|
||||
@keyup.enter="onLogin"
|
||||
/>
|
||||
<template v-if="validate.password.$error">
|
||||
<div
|
||||
|
|
@ -140,13 +145,16 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, reactive, toRefs, ref, watch } from "vue";
|
||||
import { onMounted, reactive, toRefs, ref, onBeforeMount } from "vue";
|
||||
import dom from "@left4code/tw-starter/dist/js/dom";
|
||||
import { useVuelidate } from "@vuelidate/core";
|
||||
import { required, minLength, email } from "@vuelidate/validators";
|
||||
import { useAuthStore } from "@/stores";
|
||||
import router from "@/router";
|
||||
import Logo from "@/components/logo/Main.vue";
|
||||
import Lang from "@/components/lang/Main.vue";
|
||||
import { SELECTED_LANG } from "@/helpers";
|
||||
import i18n from "@/i18n";
|
||||
|
||||
const formData = reactive({
|
||||
email: "",
|
||||
|
|
@ -174,25 +182,23 @@ const onLogin = async () => {
|
|||
|
||||
validate.value.$touch();
|
||||
|
||||
// if form is valid
|
||||
if (!validate.value.$invalid) {
|
||||
isLoading.value = true;
|
||||
await authStore.login(formData.email, formData.password);
|
||||
isLoading.value = false;
|
||||
}
|
||||
// if form is invalid
|
||||
if (validate.value.$invalid) return;
|
||||
|
||||
isLoading.value = true;
|
||||
await authStore.login(formData.email, formData.password);
|
||||
isLoading.value = false;
|
||||
};
|
||||
|
||||
watch(
|
||||
() => router.params,
|
||||
(previous, current) => {
|
||||
console.log(`${previous} and ${current}`);
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
}
|
||||
);
|
||||
const onForgotPassword = async () => {
|
||||
const authStore = useAuthStore();
|
||||
validate.value.email.$touch();
|
||||
|
||||
const onForgotPassword = () => router.push({ path: "/error-page" });
|
||||
if (validate.value.email.$invalid) return;
|
||||
|
||||
console.log("validate email only: ", formData.email);
|
||||
await authStore.forgotPassword(formData.email);
|
||||
};
|
||||
|
||||
const onRegister = () => router.push({ path: "/register" });
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +1,214 @@
|
|||
<template><div>Hello World</div></template>
|
||||
<template>
|
||||
<div>
|
||||
<div class="container sm:px-10">
|
||||
<div class="block xl:grid grid-cols-2 gap-4">
|
||||
<!-- BEGIN: Login Info -->
|
||||
<div class="hidden xl:flex flex-col min-h-screen">
|
||||
<a href="" class="-intro-x flex items-center pt-5">
|
||||
<Logo />
|
||||
<span class="text-white text-lg ml-3">
|
||||
Türkmenistanyň Döwlet haryt-çig mal biržasy
|
||||
</span>
|
||||
</a>
|
||||
|
||||
<div class="my-auto">
|
||||
<img
|
||||
alt="Midone Tailwind HTML Admin Template"
|
||||
class="-intro-x w-1/2 -mt-16"
|
||||
src="@/assets/images/illustration.svg"
|
||||
/>
|
||||
<div
|
||||
class="-intro-x text-white font-medium text-4xl leading-tight mt-10"
|
||||
>
|
||||
A few more clicks to <br />
|
||||
sign in to your account.
|
||||
</div>
|
||||
<div
|
||||
class="-intro-x mt-5 text-lg text-white text-opacity-70 dark:text-slate-400"
|
||||
>
|
||||
Manage all your documents in one place
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- END: Login Info -->
|
||||
|
||||
<!-- BEGIN: Login Form -->
|
||||
<div class="h-screen xl:h-auto md:flex py-5 xl:py-0 my-10 xl:my-0">
|
||||
<div class="md:hidden mb-6">
|
||||
<a>
|
||||
<Logo class="mx-auto mb-2" />
|
||||
<div class="text-center text-white text-lg ml-3">
|
||||
Türkmenistanyň Döwlet haryt-çig mal biržasy
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="my-auto mx-auto xl:ml-20 bg-white dark:bg-darkmode-600 xl:bg-transparent px-5 sm:px-8 py-8 xl:p-0 rounded-md shadow-md xl:shadow-none w-full sm:w-3/4 lg:w-2/4 xl:w-auto"
|
||||
>
|
||||
<h2
|
||||
class="intro-x font-bold text-2xl xl:text-3xl text-center xl:text-left"
|
||||
>
|
||||
Update password
|
||||
</h2>
|
||||
<div class="intro-x mt-2 text-slate-400 xl:hidden text-center">
|
||||
A few more clicks to sign in to your account. Manage all your
|
||||
e-commerce accounts in one place
|
||||
</div>
|
||||
|
||||
<div class="intro-x mt-8">
|
||||
<input
|
||||
type="text"
|
||||
v-model.trim="validate.email.$model"
|
||||
class="intro-x login__input form-control py-3 px-4 block mt-4"
|
||||
:class="{ 'border-danger': validate.email.$error }"
|
||||
placeholder="Email"
|
||||
/>
|
||||
<template v-if="validate.email.$error">
|
||||
<div
|
||||
v-for="(error, index) in validate.email.$errors"
|
||||
:key="index"
|
||||
class="text-danger mt-2"
|
||||
>
|
||||
{{ error.$message }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<input
|
||||
type="text"
|
||||
v-model.trim="validate.token.$model"
|
||||
class="intro-x login__input form-control py-3 px-4 block mt-4"
|
||||
:class="{ 'border-danger': validate.token.$error }"
|
||||
placeholder="Type token you received in mail"
|
||||
/>
|
||||
<template v-if="validate.token.$error">
|
||||
<div
|
||||
v-for="(error, index) in validate.token.$errors"
|
||||
:key="index"
|
||||
class="text-danger mt-2"
|
||||
>
|
||||
{{ error.$message }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<input
|
||||
type="password"
|
||||
v-model.trim="validate.password.$model"
|
||||
class="intro-x login__input form-control py-3 px-4 block mt-4"
|
||||
:class="{ 'border-danger': validate.password.$error }"
|
||||
placeholder="Password"
|
||||
/>
|
||||
<template v-if="validate.password.$error">
|
||||
<div
|
||||
v-for="(error, index) in validate.password.$errors"
|
||||
:key="index"
|
||||
class="text-danger mt-2"
|
||||
>
|
||||
{{ error.$message }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<input
|
||||
type="password"
|
||||
v-model.trim="validate.newPassword.$model"
|
||||
class="intro-x login__input form-control py-3 px-4 block mt-4"
|
||||
:class="{ 'border-danger': validate.newPassword.$error }"
|
||||
placeholder="New password"
|
||||
/>
|
||||
<template v-if="validate.newPassword.$error">
|
||||
<div
|
||||
v-for="(error, index) in validate.newPassword.$errors"
|
||||
:key="index"
|
||||
class="text-danger mt-2"
|
||||
>
|
||||
{{ error.$message }}
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="intro-x mt-5 xl:mt-8 text-center xl:text-left">
|
||||
<button
|
||||
class="btn btn-primary py-3 px-4 w-full xl:w-32 xl:mr-3 align-top"
|
||||
@click.prevent="onUpdate"
|
||||
>
|
||||
Update
|
||||
<LoadingIcon
|
||||
icon="oval"
|
||||
color="white"
|
||||
class="w-4 h-4 ml-2"
|
||||
v-if="isLoading"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-outline-secondary py-3 px-4 w-full xl:w-32 mt-3 xl:mt-0 align-top"
|
||||
@click.prevent="onLogin"
|
||||
>
|
||||
Sign in
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- END: Login Form -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, reactive, toRefs, ref, watch } from "vue";
|
||||
import dom from "@left4code/tw-starter/dist/js/dom";
|
||||
import { useVuelidate } from "@vuelidate/core";
|
||||
import { required, minLength, email } from "@vuelidate/validators";
|
||||
import { useAuthStore } from "@/stores";
|
||||
import router from "@/router";
|
||||
import Logo from "@/components/logo/Main.vue";
|
||||
|
||||
const formData = reactive({
|
||||
email: "",
|
||||
password: "",
|
||||
newPassword: "",
|
||||
token: "",
|
||||
});
|
||||
|
||||
const isLoading = ref(false);
|
||||
|
||||
const rules = {
|
||||
email: {
|
||||
required,
|
||||
email,
|
||||
},
|
||||
|
||||
password: {
|
||||
required,
|
||||
minLength: minLength(8),
|
||||
},
|
||||
newPassword: {
|
||||
required,
|
||||
minLength: minLength(8),
|
||||
},
|
||||
token: {
|
||||
required,
|
||||
minLength: minLength(4),
|
||||
},
|
||||
};
|
||||
|
||||
const validate = useVuelidate(rules, toRefs(formData));
|
||||
|
||||
const onLogin = () => router.push({ path: "/login" });
|
||||
|
||||
const onUpdate = async () => {
|
||||
const authStore = useAuthStore();
|
||||
|
||||
validate.value.$touch();
|
||||
|
||||
// if form is valid
|
||||
if (!validate.value.$invalid) {
|
||||
// isLoading.value = true;
|
||||
// await authStore.login(formData.email, formData.password);
|
||||
// isLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
dom("body").removeClass("main").removeClass("error-page").addClass("login");
|
||||
});
|
||||
</script>
|
||||
|
|
|
|||
Loading…
Reference in New Issue