Compare commits
10 Commits
b1480a8f1b
...
d48945604a
| Author | SHA1 | Date |
|---|---|---|
|
|
d48945604a | |
|
|
8dfa070ac1 | |
|
|
787bd70499 | |
|
|
6678c6d191 | |
|
|
35a3eb0f56 | |
|
|
c1fad4a632 | |
|
|
28bc7888ce | |
|
|
0e0728a36a | |
|
|
17258c7631 | |
|
|
af2a3b36fe |
|
|
@ -10,12 +10,9 @@ ARG CURLOPT_SSL_VERIFYPEER=FALSE
|
||||||
LABEL version=$VERSION build=$BUILD mode=$TARGETENV
|
LABEL version=$VERSION build=$BUILD mode=$TARGETENV
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
RUN apt-get update && apt-get install -y curl ca-certificates
|
RUN apt-get update && apt-get install -y curl ca-certificates
|
||||||
# RUN curl -fsSLk https://deb.nodesource.com/setup_16.x | bash -
|
|
||||||
RUN apt-get install -y nginx
|
RUN apt-get install -y nginx
|
||||||
RUN rm -rf /usr/share/nginx/html/
|
RUN rm -rf /usr/share/nginx/html/
|
||||||
RUN mkdir -p /usr/share/nginx/html
|
RUN mkdir -p /usr/share/nginx/html
|
||||||
# RUN npm install forever -g
|
|
||||||
RUN mkdir /data
|
|
||||||
WORKDIR /data
|
WORKDIR /data
|
||||||
|
|
||||||
COPY package.json package-lock.json ./
|
COPY package.json package-lock.json ./
|
||||||
|
|
@ -33,8 +30,7 @@ RUN npm run build
|
||||||
|
|
||||||
# служебные штуки
|
# служебные штуки
|
||||||
RUN cp -R /data/dist/* /usr/share/nginx/html
|
RUN cp -R /data/dist/* /usr/share/nginx/html
|
||||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
COPY nginx.conf /etc/nginx/sites-enabled/default
|
||||||
RUN mkdir /usr/share/nginx/html/.well-known
|
|
||||||
RUN chmod +x /data/run.sh
|
RUN chmod +x /data/run.sh
|
||||||
|
|
||||||
CMD ["/data/run.sh"]
|
CMD ["/data/run.sh"]
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
/>
|
/>
|
||||||
<link rel="icon" href="/favicon.ico" />
|
<link rel="icon" href="/favicon.ico" />
|
||||||
|
|
||||||
<title>Vuestic Admin</title>
|
<title>Cycle Rider Ala Strava for Russian/Belorussian</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
|
|
||||||
14
nginx.conf
14
nginx.conf
|
|
@ -6,15 +6,9 @@ server {
|
||||||
gzip_vary on;
|
gzip_vary on;
|
||||||
gzip_types text/plain text/css application/json application/x-javascript application/javascript text/xml application/xml application/rss+xml text/javascript image/svg+xml application/vnd.ms-fontobject application/x-font-ttf font/opentype;
|
gzip_types text/plain text/css application/json application/x-javascript application/javascript text/xml application/xml application/rss+xml text/javascript image/svg+xml application/vnd.ms-fontobject application/x-font-ttf font/opentype;
|
||||||
|
|
||||||
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|otf|xml|txt)$ {
|
|
||||||
root /usr/share/nginx/html/browser;
|
|
||||||
try_files $uri /index.html;
|
|
||||||
}
|
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
proxy_set_header X-Forwarded-For $remote_addr;
|
root /usr/share/nginx/html;
|
||||||
proxy_set_header Host $http_host;
|
try_files $uri /index.html;
|
||||||
proxy_pass http://localhost:4000;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#error_page 404 /404.html;
|
#error_page 404 /404.html;
|
||||||
|
|
@ -23,13 +17,13 @@ server {
|
||||||
#
|
#
|
||||||
error_page 500 502 503 504 /50x.html;
|
error_page 500 502 503 504 /50x.html;
|
||||||
location = /50x.html {
|
location = /50x.html {
|
||||||
root /usr/share/nginx/html;
|
root /usr/share/nginx/html;
|
||||||
}
|
}
|
||||||
|
|
||||||
# deny access to hidden files like .git etc
|
# deny access to hidden files like .git etc
|
||||||
# concurs with nginx's one
|
# concurs with nginx's one
|
||||||
#
|
#
|
||||||
location ~ /\. {
|
location ~ /\. {
|
||||||
deny all;
|
deny all;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"name": "vuestic-admin",
|
"name": "cycle-rider",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
|
||||||
2
run.sh
2
run.sh
|
|
@ -1,4 +1,4 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
nginx
|
nginx -g 'daemon off;'
|
||||||
#node /usr/share/nginx/html/server/main.js
|
#node /usr/share/nginx/html/server/main.js
|
||||||
|
|
|
||||||
|
|
@ -108,7 +108,7 @@ withDefaults(
|
||||||
list: [
|
list: [
|
||||||
{
|
{
|
||||||
name: "logout",
|
name: "logout",
|
||||||
to: "login",
|
to: "logout",
|
||||||
icon: "mso-logout",
|
icon: "mso-logout",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,7 @@
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { onBeforeUnmount, onMounted, ref, computed } from "vue";
|
import { onBeforeUnmount, onMounted, ref, computed } from "vue";
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
import { storeToRefs } from "pinia";
|
import { storeToRefs } from "pinia";
|
||||||
import { onBeforeRouteUpdate } from "vue-router";
|
import { onBeforeRouteUpdate } from "vue-router";
|
||||||
import { useBreakpoint } from "vuestic-ui";
|
import { useBreakpoint } from "vuestic-ui";
|
||||||
|
|
@ -77,6 +78,10 @@ const onResize = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
const token = localStorage.getItem('token');
|
||||||
|
if (!token) {
|
||||||
|
useRouter().push({ name: "login" });
|
||||||
|
}
|
||||||
window.addEventListener("resize", onResize);
|
window.addEventListener("resize", onResize);
|
||||||
onResize();
|
onResize();
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,5 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useBreakpoint } from "vuestic-ui";
|
import { useBreakpoint } from "vuestic-ui";
|
||||||
import VuesticLogo from "../components/VuesticLogo.vue";
|
import VuesticLogo from "../components/VuesticLogo.vue";
|
||||||
|
|
||||||
const breakpoint = useBreakpoint();
|
const breakpoint = useBreakpoint();
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ app.use(stores);
|
||||||
app.use(router);
|
app.use(router);
|
||||||
app.use(i18n);
|
app.use(i18n);
|
||||||
app.use(createVuestic({ config: vuesticGlobalConfig }));
|
app.use(createVuestic({ config: vuesticGlobalConfig }));
|
||||||
|
app.provide('HOST', "https://cycle-rider.ru");
|
||||||
if (import.meta.env.VITE_APP_GTM_ENABLED) {
|
if (import.meta.env.VITE_APP_GTM_ENABLED) {
|
||||||
app.use(
|
app.use(
|
||||||
createGtm({
|
createGtm({
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,55 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<VaForm ref="passwordForm" @submit.prevent="submit">
|
||||||
<h1 class="font-semibold text-4xl mb-4">Check the email</h1>
|
<h1 class="font-semibold text-4xl mb-4">Авторизация по коду</h1>
|
||||||
<p class="text-base mb-4 leading-5">
|
<p class="text-base mb-4 leading-5">
|
||||||
Password reset instructions have been sent to your email. Check your
|
Вам был выслан код, введите его и вы авторизуетесь, для смены пароля вам необходимо зайти в настройки профиля.
|
||||||
inbox, including the spam folder if needed. For assistance,
|
|
||||||
<span class="va-link">contact support</span>.
|
|
||||||
</p>
|
</p>
|
||||||
|
<VaInput v-model="email" :rules="[(v) => !!v || 'Email обязательное поле']" class="mb-4" label="Введите ваш email"
|
||||||
<div class="flex justify-center mt-4">
|
type="email" />
|
||||||
<VaButton :to="{ name: 'login' }" class="w-full">Back to login</VaButton>
|
<VaInput v-model="code" :rules="[(v) => !!v || 'Код обязательное поле']" class="mb-4" label="Введите код из письма"
|
||||||
</div>
|
type="text" />
|
||||||
</div>
|
<VaButton class="w-full mb-2" @click="submit">Авторизоваться</VaButton>
|
||||||
|
</VaForm>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup></script>
|
<script lang="ts" setup>
|
||||||
|
|
||||||
|
import { inject } from 'vue'
|
||||||
|
import axios from "axios";
|
||||||
|
import { ref } from "vue";
|
||||||
|
import { useForm, useToast } from "vuestic-ui";
|
||||||
|
import { useRouter, useRoute } from "vue-router";
|
||||||
|
const form = useForm("passwordForm");
|
||||||
|
const code = ref("");
|
||||||
|
const router = useRouter();
|
||||||
|
const HOST = inject('HOST');
|
||||||
|
const email = ref(useRoute().query.email?.toString() || "");
|
||||||
|
const { init } = useToast();
|
||||||
|
|
||||||
|
const submit = () => {
|
||||||
|
if (form.validate()) {
|
||||||
|
axios
|
||||||
|
.post(`${HOST}/api/v0/profiles/passwords/confirm/recover`, {
|
||||||
|
email: email.value,
|
||||||
|
code: code.value,
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
|
||||||
|
localStorage.setItem('token', response.data.token);
|
||||||
|
localStorage.setItem('profile', JSON.stringify(response.data.profile));
|
||||||
|
localStorage.setItem('user', JSON.stringify(response.data.user));
|
||||||
|
|
||||||
|
router.push({ name: "dashboard" }).catch((error) => { });
|
||||||
|
}).catch((error) => {
|
||||||
|
if (error.response.data.detail.code_string == "ObjectNotFound") {
|
||||||
|
init({
|
||||||
|
message: "Неверный код",
|
||||||
|
color: "error",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -40,11 +40,6 @@
|
||||||
<div
|
<div
|
||||||
class="auth-layout__options flex flex-col sm:flex-row items-start sm:items-center justify-between"
|
class="auth-layout__options flex flex-col sm:flex-row items-start sm:items-center justify-between"
|
||||||
>
|
>
|
||||||
<VaCheckbox
|
|
||||||
v-model="formData.keepLoggedIn"
|
|
||||||
class="mb-2 sm:mb-0"
|
|
||||||
label="Запомнить меня"
|
|
||||||
/>
|
|
||||||
<RouterLink
|
<RouterLink
|
||||||
:to="{ name: 'recover-password' }"
|
:to="{ name: 'recover-password' }"
|
||||||
class="mt-2 sm:mt-0 sm:ml-1 font-semibold text-primary"
|
class="mt-2 sm:mt-0 sm:ml-1 font-semibold text-primary"
|
||||||
|
|
@ -60,6 +55,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { inject } from 'vue'
|
||||||
|
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { reactive } from "vue";
|
import { reactive } from "vue";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
|
|
@ -69,26 +66,41 @@ import { validators } from "../../services/utils";
|
||||||
const { validate } = useForm("form");
|
const { validate } = useForm("form");
|
||||||
const { push } = useRouter();
|
const { push } = useRouter();
|
||||||
const { init } = useToast();
|
const { init } = useToast();
|
||||||
|
const HOST = inject('HOST');
|
||||||
const formData = reactive({
|
const formData = reactive({
|
||||||
email: "",
|
email: "",
|
||||||
password: "",
|
password: "",
|
||||||
keepLoggedIn: false,
|
keepLoggedIn: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const token = localStorage.getItem("token");
|
||||||
|
if (token != undefined && token.length > 0) {
|
||||||
|
push({ name: "dashboard" }).catch((error) => {});
|
||||||
|
}
|
||||||
|
|
||||||
const submit = () => {
|
const submit = () => {
|
||||||
if (validate()) {
|
if (validate()) {
|
||||||
axios
|
axios
|
||||||
.post("http://localhost:3000/api/v0/signin", {
|
.post(`${HOST}/api/v0/signin`, {
|
||||||
login: formData.email,
|
login: formData.email,
|
||||||
password: formData.password,
|
password: formData.password,
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
// TODO: save token
|
localStorage.setItem('token', response.data.token);
|
||||||
|
localStorage.setItem('profile', JSON.stringify(response.data.profile));
|
||||||
|
localStorage.setItem('user', JSON.stringify(response.data.user));
|
||||||
|
|
||||||
init({ message: "Вы успешно вошли!", color: "success" });
|
init({ message: "Вы успешно вошли!", color: "success" });
|
||||||
push({ name: "dashboard" });
|
push({ name: "dashboard" });
|
||||||
})
|
})
|
||||||
.catch((error) => {});
|
.catch((error) => {
|
||||||
|
|
||||||
|
init({
|
||||||
|
message: "Неверный логин или пароль",
|
||||||
|
color: "error",
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
<template></template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
|
||||||
|
const { push } = useRouter();
|
||||||
|
localStorage.clear();
|
||||||
|
useRouter().push({ name: "login" });
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
@ -25,6 +25,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { inject } from 'vue'
|
||||||
|
import axios from "axios";
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
import { useForm } from "vuestic-ui";
|
import { useForm } from "vuestic-ui";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
|
|
@ -32,10 +34,17 @@ import { useRouter } from "vue-router";
|
||||||
const email = ref("");
|
const email = ref("");
|
||||||
const form = useForm("passwordForm");
|
const form = useForm("passwordForm");
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const HOST = inject('HOST');
|
||||||
|
|
||||||
const submit = () => {
|
const submit = () => {
|
||||||
if (form.validate()) {
|
if (form.validate()) {
|
||||||
router.push({ name: "recover-password-email" });
|
axios
|
||||||
|
.post(`${HOST}/api/v0/profiles/passwords/require/recover`, {
|
||||||
|
email: email.value,
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
router.push({ name: "recover-password-email", query: { email: email.value } });
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -73,6 +73,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { inject } from 'vue'
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { reactive } from "vue";
|
import { reactive } from "vue";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
|
|
@ -81,6 +82,7 @@ import { useForm, useToast } from "vuestic-ui";
|
||||||
const { validate } = useForm("form");
|
const { validate } = useForm("form");
|
||||||
const { push } = useRouter();
|
const { push } = useRouter();
|
||||||
const { init } = useToast();
|
const { init } = useToast();
|
||||||
|
const HOST = inject('HOST');
|
||||||
|
|
||||||
const formData = reactive({
|
const formData = reactive({
|
||||||
email: "",
|
email: "",
|
||||||
|
|
@ -88,15 +90,22 @@ const formData = reactive({
|
||||||
repeatPassword: "",
|
repeatPassword: "",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const token = localStorage.getItem("token");
|
||||||
|
if (token != undefined && token.length > 0) {
|
||||||
|
push({ name: "dashboard" }).catch((error) => {});
|
||||||
|
}
|
||||||
|
|
||||||
const submit = () => {
|
const submit = () => {
|
||||||
if (validate()) {
|
if (validate()) {
|
||||||
axios
|
axios
|
||||||
.post("http://localhost:3000/api/v0/signin", {
|
.post(`${HOST}/api/v0/signup`, {
|
||||||
email: formData.email,
|
email: formData.email,
|
||||||
password: formData.password,
|
password: formData.password,
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
// TODO: save token
|
localStorage.setItem('token', response.data.token);
|
||||||
|
localStorage.setItem('profile', JSON.stringify(response.data.profile));
|
||||||
|
localStorage.setItem('user', JSON.stringify(response.data.user));
|
||||||
init({
|
init({
|
||||||
message: "Вы успешно вошли",
|
message: "Вы успешно вошли",
|
||||||
color: "success",
|
color: "success",
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<h1 class="page-title">Preferences</h1>
|
<h1 class="page-title">Настройки профиля</h1>
|
||||||
<div class="flex flex-col p-4 space-y-10 bg-backgroundSecondary rounded-lg">
|
<div class="flex flex-col p-4 space-y-10 bg-backgroundSecondary rounded-lg">
|
||||||
<div class="flex space-x-5">
|
<div class="flex space-x-5">
|
||||||
<PreferencesHeader />
|
<PreferencesHeader />
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,10 @@
|
||||||
close-button
|
close-button
|
||||||
@update:modelValue="emits('cancel')"
|
@update:modelValue="emits('cancel')"
|
||||||
>
|
>
|
||||||
<h1 class="va-h5 mb-4">Reset password</h1>
|
<h1 class="va-h5 mb-4">Изменить имя</h1>
|
||||||
<VaForm ref="form" @submit.prevent="submit">
|
<VaForm ref="form" @submit.prevent="submit">
|
||||||
<VaInput v-model="Name" class="mb-4" label="Name" placeholder="Name" />
|
<VaInput v-model="Name" class="mb-4" label="Имя" placeholder="Имя" />
|
||||||
|
<VaInput v-model="Surname" class="mb-4" label="Фамилия" placeholder="Фамилия" />
|
||||||
<div
|
<div
|
||||||
class="flex flex-col-reverse md:flex-row md:items-center md:justify-end md:space-x-4"
|
class="flex flex-col-reverse md:flex-row md:items-center md:justify-end md:space-x-4"
|
||||||
>
|
>
|
||||||
|
|
@ -35,7 +36,10 @@
|
||||||
</VaModal>
|
</VaModal>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { inject } from 'vue'
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
import { useUserStore } from "../../../stores/user-store";
|
import { useUserStore } from "../../../stores/user-store";
|
||||||
|
|
||||||
import { buttonStyles } from "../styles";
|
import { buttonStyles } from "../styles";
|
||||||
|
|
@ -48,14 +52,38 @@ const { init } = useToast();
|
||||||
const emits = defineEmits(["cancel"]);
|
const emits = defineEmits(["cancel"]);
|
||||||
|
|
||||||
const Name = ref<string>(store.userName);
|
const Name = ref<string>(store.userName);
|
||||||
|
const Surname = ref<string>(store.userSurname);
|
||||||
|
const HOST = inject('HOST');
|
||||||
|
|
||||||
const submit = () => {
|
const submit = () => {
|
||||||
if (!Name.value || Name.value === store.userName) {
|
if (!Name.value && !Surname.value) {
|
||||||
return emits("cancel");
|
return emits("cancel");
|
||||||
}
|
}
|
||||||
|
if (Surname.value === store.userSurname && Name.value === store.userName) {
|
||||||
|
return emits("cancel");
|
||||||
|
}
|
||||||
|
let dataUser = JSON.parse(localStorage.getItem("profile")!);
|
||||||
|
dataUser.first_name = Name.value;
|
||||||
|
dataUser.surname = Surname.value;
|
||||||
|
const config = {
|
||||||
|
headers: { Authorization: `Bearer ${localStorage.getItem("token")}` }
|
||||||
|
};
|
||||||
|
axios
|
||||||
|
.patch(`${HOST}/api/v0/profiles/${store.profileID}`, {
|
||||||
|
"profile": dataUser,
|
||||||
|
}, config)
|
||||||
|
.then((response) => {
|
||||||
|
store.changeUserName(Name.value, Surname.value);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
init({
|
||||||
|
message: "Что-то пошло не так.",
|
||||||
|
color: "error",
|
||||||
|
});
|
||||||
|
|
||||||
store.changeUserName(Name.value);
|
});
|
||||||
init({ message: "You've successfully changed your name", color: "success" });
|
|
||||||
|
init({ message: "Вы успешно изменили имя!", color: "success" });
|
||||||
emits("cancel");
|
emits("cancel");
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -7,57 +7,27 @@
|
||||||
close-button
|
close-button
|
||||||
@update:modelValue="emits('cancel')"
|
@update:modelValue="emits('cancel')"
|
||||||
>
|
>
|
||||||
<h1 class="va-h5 mb-4">Reset password</h1>
|
<h1 class="va-h5 mb-4">Сброс пароля</h1>
|
||||||
<VaForm ref="form" class="space-y-6" @submit.prevent="submit">
|
<VaForm ref="form" class="space-y-6" @submit.prevent="submit">
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
<VaInput
|
|
||||||
v-model="oldPassowrd"
|
|
||||||
:rules="oldPasswordRules"
|
|
||||||
label="Old password"
|
|
||||||
placeholder="Old password"
|
|
||||||
required-mark
|
|
||||||
type="password"
|
|
||||||
/>
|
|
||||||
<div class="hidden md:block" />
|
|
||||||
<VaInput
|
<VaInput
|
||||||
v-model="newPassword"
|
v-model="newPassword"
|
||||||
:rules="newPasswordRules"
|
:rules="newPasswordRules"
|
||||||
label="New password"
|
label="Новый пароль"
|
||||||
placeholder="New password"
|
placeholder="Новый пароль"
|
||||||
required-mark
|
required-mark
|
||||||
type="password"
|
type="password"
|
||||||
/>
|
/>
|
||||||
<VaInput
|
<VaInput
|
||||||
v-model="repeatNewPassword"
|
v-model="repeatNewPassword"
|
||||||
:rules="repeatNewPasswordRules"
|
:rules="repeatNewPasswordRules"
|
||||||
label="Repeat new password"
|
label="Повторите новый пароль"
|
||||||
placeholder="Repeat new password"
|
placeholder="Повторите новый пароль"
|
||||||
required-mark
|
required-mark
|
||||||
type="password"
|
type="password"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col space-y-2">
|
|
||||||
<div class="flex space-x-2 items-center">
|
|
||||||
<div>
|
|
||||||
<VaIcon
|
|
||||||
:name="newPassword?.length! >= 8 ? 'mso-check' : 'mso-close'"
|
|
||||||
color="secondary"
|
|
||||||
size="20px"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<p>Must be at least 8 characters long</p>
|
|
||||||
</div>
|
|
||||||
<div class="flex space-x-2 items-center">
|
|
||||||
<div>
|
|
||||||
<VaIcon
|
|
||||||
:name="new Set(newPassword).size >= 6 ? 'mso-check' : 'mso-close'"
|
|
||||||
color="secondary"
|
|
||||||
size="20px"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<p>Must contain at least 6 unique characters</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
<div
|
||||||
class="flex flex-col-reverse md:justify-end md:flex-row md:space-x-4"
|
class="flex flex-col-reverse md:justify-end md:flex-row md:space-x-4"
|
||||||
>
|
>
|
||||||
|
|
@ -75,53 +45,64 @@
|
||||||
type="submit"
|
type="submit"
|
||||||
@click="submit"
|
@click="submit"
|
||||||
>
|
>
|
||||||
Update Password</VaButton
|
Обновить пароль</VaButton
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</VaForm>
|
</VaForm>
|
||||||
</VaModal>
|
</VaModal>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import axios from "axios";
|
||||||
|
import { inject } from 'vue'
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
import { useForm, useToast } from "vuestic-ui";
|
import { useForm, useToast } from "vuestic-ui";
|
||||||
|
|
||||||
import { buttonStyles } from "../styles";
|
import { buttonStyles } from "../styles";
|
||||||
|
|
||||||
const oldPassowrd = ref<string>();
|
|
||||||
const newPassword = ref<string>();
|
const newPassword = ref<string>();
|
||||||
const repeatNewPassword = ref<string>();
|
const repeatNewPassword = ref<string>();
|
||||||
|
const HOST = inject('HOST');
|
||||||
|
|
||||||
const { validate } = useForm("form");
|
const { validate } = useForm("form");
|
||||||
const { init } = useToast();
|
const { init } = useToast();
|
||||||
|
|
||||||
const emits = defineEmits(["cancel"]);
|
const emits = defineEmits(["cancel"]);
|
||||||
|
const config = {
|
||||||
|
headers: { Authorization: `Bearer ${localStorage.getItem("token")}` }
|
||||||
|
};
|
||||||
|
|
||||||
const submit = () => {
|
const submit = () => {
|
||||||
if (validate()) {
|
if (validate()) {
|
||||||
init({
|
axios
|
||||||
message: "You've successfully changed your password",
|
.put(`${HOST}/api/v0/profiles/passwords`, {
|
||||||
color: "success",
|
"password": newPassword.value,
|
||||||
});
|
}, config)
|
||||||
emits("cancel");
|
.then((response) => {
|
||||||
}
|
init({
|
||||||
|
message: "Вы успешно обновили пароль!",
|
||||||
|
color: "success",
|
||||||
|
});
|
||||||
|
emits("cancel");
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
init({
|
||||||
|
message: "Что-то пошло не так.",
|
||||||
|
color: "error",
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const oldPasswordRules = [
|
|
||||||
(v: string) => !!v || "Old password field is required",
|
|
||||||
];
|
|
||||||
|
|
||||||
const newPasswordRules = [
|
const newPasswordRules = [
|
||||||
(v: string) => !!v || "New password field is required",
|
(v: string) => !!v || "Поле обязательно для заполнения!"
|
||||||
(v: string) => v?.length >= 8 || "Must be at least 8 characters long",
|
|
||||||
(v: string) =>
|
|
||||||
new Set(v).size >= 6 || "Must contain at least 6 unique characters",
|
|
||||||
(v: string) => v !== oldPassowrd.value || "New password cannot be the same",
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const repeatNewPasswordRules = [
|
const repeatNewPasswordRules = [
|
||||||
(v: string) => !!v || "Repeat new password field is required",
|
(v: string) => !!v || "Поле обязательно для заполнения!",
|
||||||
(v: string) =>
|
(v: string) =>
|
||||||
v === newPassword.value || "Confirm password does not match new password",
|
v === newPassword.value || "Пароли не совпадают",
|
||||||
];
|
];
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
<p class="font-bold w-[200px]">Имя</p>
|
<p class="font-bold w-[200px]">Имя</p>
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<div class="max-w-[748px]">
|
<div class="max-w-[748px]">
|
||||||
{{ store.userName }}
|
{{ store.userSurname }} {{ store.userName }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<VaButton
|
<VaButton
|
||||||
|
|
@ -45,83 +45,13 @@
|
||||||
</VaButton>
|
</VaButton>
|
||||||
</div>
|
</div>
|
||||||
<VaDivider />
|
<VaDivider />
|
||||||
<div
|
|
||||||
class="flex flex-col md:flex-row space-y-2 md:space-y-0 md:space-x-6 min-h-[36px] leading-5"
|
|
||||||
>
|
|
||||||
<p class="font-bold w-[200px]">Двуфакторная авторизация</p>
|
|
||||||
<div class="flex-1">
|
|
||||||
<div class="max-w-[748px]">
|
|
||||||
{{ twoFA.content }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<VaButton
|
|
||||||
:style="buttonStyles"
|
|
||||||
class="w-fit h-fit"
|
|
||||||
preset="primary"
|
|
||||||
:color="twoFA.color"
|
|
||||||
@click="toggle2FA"
|
|
||||||
>
|
|
||||||
{{ twoFA.button }}
|
|
||||||
</VaButton>
|
|
||||||
</div>
|
|
||||||
<VaDivider />
|
|
||||||
<div
|
|
||||||
class="flex flex-col md:flex-row space-y-2 md:space-y-0 md:space-x-6 min-h-[36px] leading-5"
|
|
||||||
>
|
|
||||||
<p class="font-bold w-[200px]">Email подписка</p>
|
|
||||||
<div class="flex-1">
|
|
||||||
<div class="max-w-[748px]">
|
|
||||||
<p>Чтобы управлять подписками, перейдите в</p>
|
|
||||||
<div class="flex space-x-1 w-fit">
|
|
||||||
<RouterLink
|
|
||||||
:to="{ name: 'settings' }"
|
|
||||||
class="font-semibold text-primary"
|
|
||||||
>Настройки оповещения</RouterLink
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed } from "vue";
|
|
||||||
|
|
||||||
import { useToast } from "vuestic-ui/web-components";
|
|
||||||
|
|
||||||
import { useUserStore } from "../../../stores/user-store";
|
import { useUserStore } from "../../../stores/user-store";
|
||||||
|
|
||||||
import { buttonStyles } from "../styles";
|
import { buttonStyles } from "../styles";
|
||||||
|
|
||||||
const store = useUserStore();
|
const store = useUserStore();
|
||||||
|
|
||||||
const { init } = useToast();
|
|
||||||
|
|
||||||
const toastMessage = computed(() =>
|
|
||||||
store.is2FAEnabled ? "2FA успешно включена" : "2FA успешно откючена",
|
|
||||||
);
|
|
||||||
|
|
||||||
const twoFA = computed(() => {
|
|
||||||
if (store.is2FAEnabled) {
|
|
||||||
return {
|
|
||||||
color: "danger",
|
|
||||||
button: "Disable 2FA",
|
|
||||||
content:
|
|
||||||
"Для вашей учетной записи теперь включена двухфакторная аутентификация (2FA), что повышает уровень безопасности при входе в систему",
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
color: "primary",
|
|
||||||
button: "Set up 2FA",
|
|
||||||
content:
|
|
||||||
"Повысьте уровень безопасности своей учетной записи. Чтобы войти в систему, вам необходимо ввести код вместе со своим именем пользователя и паролем.",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const toggle2FA = () => {
|
|
||||||
store.toggle2FA();
|
|
||||||
init({ message: toastMessage.value, color: "success" });
|
|
||||||
};
|
|
||||||
|
|
||||||
const emits = defineEmits(["openNameModal", "openResetPasswordModal"]);
|
const emits = defineEmits(["openNameModal", "openResetPasswordModal"]);
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,11 @@ const routes: Array<RouteRecordRaw> = [
|
||||||
path: "/auth",
|
path: "/auth",
|
||||||
component: AuthLayout,
|
component: AuthLayout,
|
||||||
children: [
|
children: [
|
||||||
|
{
|
||||||
|
name: "logout",
|
||||||
|
path: "logout",
|
||||||
|
component: () => import("../pages/auth/Logout.vue"),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "login",
|
name: "login",
|
||||||
path: "login",
|
path: "login",
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,37 @@ import { defineStore } from "pinia";
|
||||||
|
|
||||||
export const useUserStore = defineStore("user", {
|
export const useUserStore = defineStore("user", {
|
||||||
state: () => {
|
state: () => {
|
||||||
|
if (!localStorage.getItem("profile")) {
|
||||||
|
return {
|
||||||
|
profileID: "",
|
||||||
|
userName: "",
|
||||||
|
email: "",
|
||||||
|
memberSince: "",
|
||||||
|
pfp: "",
|
||||||
|
is2FAEnabled: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const dataProfile = JSON.parse(localStorage.getItem("profile")!);
|
||||||
|
const dataUser = JSON.parse(localStorage.getItem("user")!);
|
||||||
|
const dateCreate = new Date(Date.parse(dataProfile.created_at));
|
||||||
|
const yyyy = dateCreate.getFullYear().toString();
|
||||||
|
const mm = dateCreate.getMonth() + 1;
|
||||||
|
const dd = dateCreate.getDate();
|
||||||
|
let stringDD = dd.toString();
|
||||||
|
let strinMM = mm.toString();
|
||||||
|
if (dd < 10) {
|
||||||
|
stringDD = '0' + stringDD
|
||||||
|
};
|
||||||
|
if (mm < 10) {
|
||||||
|
strinMM = '0' + strinMM;
|
||||||
|
}
|
||||||
|
const formattedToday = stringDD + '/' + strinMM + '/' + yyyy;
|
||||||
return {
|
return {
|
||||||
userName: "Vasili Savitski",
|
profileID: dataProfile.id || "",
|
||||||
email: "vasili@gmail.com",
|
userName: dataProfile.first_name || "",
|
||||||
memberSince: "8/12/2020",
|
userSurname: dataProfile.surname || "",
|
||||||
|
email: dataUser.email || "",
|
||||||
|
memberSince: formattedToday,
|
||||||
pfp: "https://picsum.photos/id/22/200/300",
|
pfp: "https://picsum.photos/id/22/200/300",
|
||||||
is2FAEnabled: false,
|
is2FAEnabled: false,
|
||||||
};
|
};
|
||||||
|
|
@ -16,8 +43,9 @@ export const useUserStore = defineStore("user", {
|
||||||
this.is2FAEnabled = !this.is2FAEnabled;
|
this.is2FAEnabled = !this.is2FAEnabled;
|
||||||
},
|
},
|
||||||
|
|
||||||
changeUserName(userName: string) {
|
changeUserName(userFirst: string, userSurname: string) {
|
||||||
this.userName = userName;
|
this.userName = userFirst;
|
||||||
|
this.userSurname = userSurname;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue