frontend: sign up, logout, basic preferencies
This commit is contained in:
parent
e868c87a7e
commit
76e6d5183f
51
crates/frontend/package-lock.json
generated
51
crates/frontend/package-lock.json
generated
@ -10,6 +10,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"autoprefixer": "^10.4.18",
|
"autoprefixer": "^10.4.18",
|
||||||
"axios": "^1.6.8",
|
"axios": "^1.6.8",
|
||||||
|
"pinia": "^2.1.7",
|
||||||
"postcss": "^8.4.35",
|
"postcss": "^8.4.35",
|
||||||
"tailwindcss": "^3.4.1",
|
"tailwindcss": "^3.4.1",
|
||||||
"vue": "^3.3.11",
|
"vue": "^3.3.11",
|
||||||
@ -2468,6 +2469,56 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/pinia": {
|
||||||
|
"version": "2.1.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/pinia/-/pinia-2.1.7.tgz",
|
||||||
|
"integrity": "sha512-+C2AHFtcFqjPih0zpYuvof37SFxMQ7OEG2zV9jRI12i9BOy3YQVAHwdKtyyc8pDcDyIc33WCIsZaCFWU7WWxGQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@vue/devtools-api": "^6.5.0",
|
||||||
|
"vue-demi": ">=0.14.5"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/posva"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@vue/composition-api": "^1.4.0",
|
||||||
|
"typescript": ">=4.4.4",
|
||||||
|
"vue": "^2.6.14 || ^3.3.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@vue/composition-api": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"typescript": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pinia/node_modules/vue-demi": {
|
||||||
|
"version": "0.14.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.7.tgz",
|
||||||
|
"integrity": "sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"bin": {
|
||||||
|
"vue-demi-fix": "bin/vue-demi-fix.js",
|
||||||
|
"vue-demi-switch": "bin/vue-demi-switch.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/antfu"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@vue/composition-api": "^1.0.0-rc.1",
|
||||||
|
"vue": "^3.0.0-0 || ^2.6.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@vue/composition-api": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/pirates": {
|
"node_modules/pirates": {
|
||||||
"version": "4.0.6",
|
"version": "4.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz",
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"autoprefixer": "^10.4.18",
|
"autoprefixer": "^10.4.18",
|
||||||
"axios": "^1.6.8",
|
"axios": "^1.6.8",
|
||||||
|
"pinia": "^2.1.7",
|
||||||
"postcss": "^8.4.35",
|
"postcss": "^8.4.35",
|
||||||
"tailwindcss": "^3.4.1",
|
"tailwindcss": "^3.4.1",
|
||||||
"vue": "^3.3.11",
|
"vue": "^3.3.11",
|
||||||
|
0
crates/frontend/src/components/DropdownItem.vue
Normal file
0
crates/frontend/src/components/DropdownItem.vue
Normal file
22
crates/frontend/src/components/DropdownMenu.vue
Normal file
22
crates/frontend/src/components/DropdownMenu.vue
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from "vue";
|
||||||
|
|
||||||
|
const active = ref<bool>(false);
|
||||||
|
|
||||||
|
function activate() {
|
||||||
|
active.value = !active.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function deactivate() {
|
||||||
|
active.value = false;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div @click="activate" v-click-outside="deactivate">
|
||||||
|
<slot name="button"></slot>
|
||||||
|
<div v-if="active">
|
||||||
|
<slot name="content"></slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
@ -1,5 +0,0 @@
|
|||||||
<template>
|
|
||||||
<h1>
|
|
||||||
<slot></slot>
|
|
||||||
</h1>
|
|
||||||
</template>
|
|
5
crates/frontend/src/components/error/Error.vue
Normal file
5
crates/frontend/src/components/error/Error.vue
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<template>
|
||||||
|
<h1 class="text-center pt-3 pb-3 bg-orange-900 rounded border border-orange-700">
|
||||||
|
<slot></slot>
|
||||||
|
</h1>
|
||||||
|
</template>
|
14
crates/frontend/src/directives/click-outside.ts
Normal file
14
crates/frontend/src/directives/click-outside.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
export const click_outside = {
|
||||||
|
beforeMount: function(element: any, binding: any) {
|
||||||
|
element.clickOutsideEvent = function(event: any) {
|
||||||
|
if (!(element == event.target || element.contains(event.target))) {
|
||||||
|
binding.value(event);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.body.addEventListener("click", element.clickOutsideEvent);
|
||||||
|
},
|
||||||
|
unmounted: function(element: any) {
|
||||||
|
document.body.removeEventListener("click", element.clickOutsideEvent);
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,15 @@
|
|||||||
import { createApp } from 'vue';
|
import App from "@/App.vue";
|
||||||
import App from '@/App.vue';
|
|
||||||
import router from '@/router';
|
|
||||||
|
|
||||||
import '@/assets/style.css';
|
import { createApp } from "vue";
|
||||||
|
import { createPinia } from "pinia";
|
||||||
|
|
||||||
|
import router from "@/router";
|
||||||
|
import { click_outside } from "@/directives/click-outside";
|
||||||
|
import "@/assets/style.css";
|
||||||
|
|
||||||
createApp(App)
|
createApp(App)
|
||||||
|
.use(createPinia())
|
||||||
.use(router)
|
.use(router)
|
||||||
|
.directive("click-outside", click_outside)
|
||||||
.mount('#app');
|
.mount('#app');
|
||||||
|
|
||||||
|
@ -3,10 +3,22 @@ import { createRouter, createWebHistory } from "vue-router";
|
|||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(),
|
history: createWebHistory(),
|
||||||
routes: [
|
routes: [
|
||||||
{ path: "/", component: () => import("@/views/Home.vue") },
|
{ path: "/", name: "Home", component: () => import("@/views/Home.vue") },
|
||||||
{ path: "/user/login", component: () => import("@/views/SignIn.vue") },
|
|
||||||
{ path: "/:user", name: "User", component: () => import("@/views/User.vue") },
|
{ path: "/user/login", name: "SignIn", component: () => import("@/views/user/SignIn.vue") },
|
||||||
{ path: "/:pathMatch(.*)*", component: () => import("@/views/Error.vue") }
|
{ path: "/user/register", name: "SignUp", component: () => import("@/views/user/SignUp.vue") },
|
||||||
|
{
|
||||||
|
path: "/user/preferencies", name: "Preferencies", redirect: { name: "Preferencies-Profile" }, component: () => import("@/views/user/Preferencies.vue"), children: [
|
||||||
|
{ path: "profile", name: "Preferencies-Profile", component: () => import("@/views/user/preferencies/Profile.vue") },
|
||||||
|
{ path: "account", name: "Preferencies-Account", component: () => import("@/views/user/preferencies/Account.vue") },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
{ path: "/:user", name: "Profile", component: () => import("@/views/user/Profile.vue") },
|
||||||
|
|
||||||
|
{ path: "/admin/settings", name: "Settings", component: () => import("@/views/admin/Settings.vue") },
|
||||||
|
|
||||||
|
{ path: "/:pathMatch(.*)*", name: "NotFound", component: () => import("@/views/error/NotFound.vue") }
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,10 +1,18 @@
|
|||||||
import api_client from "@/api-client";
|
import api_client from "@/api-client";
|
||||||
|
|
||||||
class User {
|
class User {
|
||||||
|
async register(login: string, email: string, password: string): Promise<JSON> {
|
||||||
|
return await api_client.post("/user/register", JSON.stringify({ login: login, email: email, password: password }));
|
||||||
|
}
|
||||||
|
|
||||||
async login(email: string, password: string): Promise<JSON> {
|
async login(email: string, password: string): Promise<JSON> {
|
||||||
return await api_client.post("/user/login", JSON.stringify({ email: email, password: password }));
|
return await api_client.post("/user/login", JSON.stringify({ email: email, password: password }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async logout(): Promise<JSON> {
|
||||||
|
return await api_client.get("/user/logout");
|
||||||
|
}
|
||||||
|
|
||||||
async get(login: any): Promise<JSON> {
|
async get(login: any): Promise<JSON> {
|
||||||
return await api_client.get(`/user/${login}`);
|
return await api_client.get(`/user/${login}`);
|
||||||
}
|
}
|
||||||
|
5
crates/frontend/src/stores/preferencies.ts
Normal file
5
crates/frontend/src/stores/preferencies.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { defineStore } from "pinia";
|
||||||
|
|
||||||
|
export const usePreferenciesStore = defineStore("preferencies", {
|
||||||
|
state: () => ({ current_tab: null }),
|
||||||
|
});
|
5
crates/frontend/src/stores/user.ts
Normal file
5
crates/frontend/src/stores/user.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { defineStore } from "pinia";
|
||||||
|
|
||||||
|
export const useUserStore = defineStore("user", {
|
||||||
|
state: () => ({ login: null }),
|
||||||
|
});
|
@ -1,10 +1,17 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import Meerkat from '@/components/icons/Meerkat.vue';
|
import Meerkat from "@/components/icons/Meerkat.vue";
|
||||||
import NavBar from '@/components/NavBar.vue';
|
import NavBar from "@/components/NavBar.vue";
|
||||||
|
import DropdownMenu from "@/components/DropdownMenu.vue";
|
||||||
|
|
||||||
|
import { ref, onMounted } from "vue";
|
||||||
|
|
||||||
|
import router from "@/router";
|
||||||
import User from "@/services/user";
|
import User from "@/services/user";
|
||||||
import { ref, onMounted } from 'vue';
|
import { useUserStore } from "@/stores/user";
|
||||||
|
|
||||||
const user = ref(null);
|
const user = ref(null);
|
||||||
|
const userStore = useUserStore();
|
||||||
|
const error = ref<string>(null);
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await User.current()
|
await User.current()
|
||||||
@ -13,13 +20,31 @@ onMounted(async () => {
|
|||||||
return Promise.reject(response.data && response.data.message || response.status);
|
return Promise.reject(response.data && response.data.message || response.status);
|
||||||
};
|
};
|
||||||
if (response.data.hasOwnProperty("user")) {
|
if (response.data.hasOwnProperty("user")) {
|
||||||
user.value = response.data.user;
|
userStore.login = response.data.user.login;
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
|
.catch(e => {
|
||||||
|
console.log(`${e.name}[${e.code}]: ${e.message}`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
async function user_logout() {
|
||||||
|
await User.logout()
|
||||||
|
.then(async response => {
|
||||||
|
error.value = null;
|
||||||
|
|
||||||
|
if (response.status != 200) {
|
||||||
|
return Promise.reject(response.data && response.data.message || response.status);
|
||||||
|
};
|
||||||
|
|
||||||
|
userStore.login = null;
|
||||||
|
router.push({ path: "/" });
|
||||||
|
})
|
||||||
.catch(e => {
|
.catch(e => {
|
||||||
console.error("Error occured:", e);
|
console.error("Error occured:", e);
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -29,10 +54,35 @@ onMounted(async () => {
|
|||||||
<Meerkat />
|
<Meerkat />
|
||||||
</template>
|
</template>
|
||||||
<template #right>
|
<template #right>
|
||||||
<RouterLink v-if="user" class="flex min-w-9 min-h-9 pt-1 pb-1 pl-3 pr-3 rounded hover:bg-zinc-600"
|
<DropdownMenu v-if="userStore.login">
|
||||||
:to="{ name: 'User', params: { user: user.login } }">{{ user.name }}</RouterLink>
|
<template #button>
|
||||||
<RouterLink v-if="!user" class="flex min-w-9 min-h-9 pt-1 pb-1 pl-3 pr-3 rounded hover:bg-zinc-600"
|
<span
|
||||||
to="/user/login">
|
class="flex min-w-9 min-h-9 pt-1 pb-1 pl-3 pr-3 rounded hover:bg-zinc-600 cursor-pointer">{{
|
||||||
|
userStore.login }}</span>
|
||||||
|
</template>
|
||||||
|
<template #content>
|
||||||
|
<div
|
||||||
|
class="absolute flex flex-col left-auto right-0 mt-4 bg-zinc-700 border rounded border-zinc-500 mr-3">
|
||||||
|
<RouterLink :to="{ name: 'Profile', params: { user: userStore.login } }"
|
||||||
|
class="flex min-w-7 pl-5 pr-5 pt-1 pb-1 hover:bg-zinc-600">
|
||||||
|
Profile</RouterLink>
|
||||||
|
<RouterLink :to="{ name: 'Preferencies' }"
|
||||||
|
class="flex min-w-7 pl-5 pr-5 pt-1 pb-1 hover:bg-zinc-600">
|
||||||
|
Preferencies</RouterLink>
|
||||||
|
<div class="border-t border-zinc-500 ml-0 mr-0"></div>
|
||||||
|
<RouterLink :to="{ name: 'Settings' }"
|
||||||
|
class="flex min-w-7 pl-5 pr-5 pt-1 pb-1 hover:bg-zinc-600">
|
||||||
|
Settings</RouterLink>
|
||||||
|
<div class="border-t border-zinc-500 ml-0 mr-0"></div>
|
||||||
|
<div @click="user_logout"
|
||||||
|
class="flex min-w-7 pl-5 pr-5 pt-1 pb-1 hover:bg-zinc-600 cursor-pointer">
|
||||||
|
Sign Out</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</DropdownMenu>
|
||||||
|
|
||||||
|
<RouterLink v-if="!userStore.login"
|
||||||
|
class="flex min-w-9 min-h-9 pt-1 pb-1 pl-3 pr-3 rounded hover:bg-zinc-600" to="/user/login">
|
||||||
Sign In</RouterLink>
|
Sign In</RouterLink>
|
||||||
</template>
|
</template>
|
||||||
</NavBar>
|
</NavBar>
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import Base from '@/views/Base.vue';
|
|
||||||
import Login from '@/components/Login.vue';
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Base>
|
|
||||||
<Login />
|
|
||||||
</Base>
|
|
||||||
</template>
|
|
@ -1,54 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import Base from '@/views/Base.vue';
|
|
||||||
import Error from "@/components/Error.vue";
|
|
||||||
import { ref, onMounted, watch } from 'vue';
|
|
||||||
import { onBeforeRouteUpdate, useRoute } from 'vue-router'
|
|
||||||
import User from "@/services/user";
|
|
||||||
|
|
||||||
const route = useRoute();
|
|
||||||
const name = ref(null);
|
|
||||||
const error = ref(null);
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
await User.get(route.params.user)
|
|
||||||
.then(async response => {
|
|
||||||
if (response.status != 200) {
|
|
||||||
return Promise.reject(response.data && response.data.message || response.status);
|
|
||||||
};
|
|
||||||
if (response.data.hasOwnProperty("user")) {
|
|
||||||
name.value = response.data.user.name;
|
|
||||||
} else {
|
|
||||||
error.value = "404 Not Found";
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.catch(e => {
|
|
||||||
console.error("Error occured:", e);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
watch(() => route.params.user, async (to, from) => {
|
|
||||||
await User.get(route.params.user)
|
|
||||||
.then(async response => {
|
|
||||||
if (response.status != 200) {
|
|
||||||
return Promise.reject(response.data && response.data.message || response.status);
|
|
||||||
};
|
|
||||||
if (response.data.hasOwnProperty("user")) {
|
|
||||||
name.value = response.data.user.name;
|
|
||||||
} else {
|
|
||||||
error.value = "404 Not Found";
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.catch(e => {
|
|
||||||
console.error("Error occured:", e);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Base>
|
|
||||||
<div v-if="error">
|
|
||||||
<Error>{{ error }}</Error>
|
|
||||||
</div>
|
|
||||||
<p v-else>{{ name }}</p>
|
|
||||||
</Base>
|
|
||||||
</template>
|
|
@ -1,10 +1,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import Base from "@/views/Base.vue";
|
import Base from "@/views/Base.vue";
|
||||||
import Error from "@/components/Error.vue";
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Base>
|
<Base>
|
||||||
<Error>Not Found</Error>
|
<div>
|
||||||
|
|
||||||
|
</div>
|
||||||
</Base>
|
</Base>
|
||||||
</template>
|
</template>
|
10
crates/frontend/src/views/error/NotFound.vue
Normal file
10
crates/frontend/src/views/error/NotFound.vue
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import Base from "@/views/Base.vue";
|
||||||
|
import Error from "@/components/error/Error.vue";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Base>
|
||||||
|
<Error>404 Not Found</Error>
|
||||||
|
</Base>
|
||||||
|
</template>
|
38
crates/frontend/src/views/user/Preferencies.vue
Normal file
38
crates/frontend/src/views/user/Preferencies.vue
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import Base from "@/views/Base.vue";
|
||||||
|
|
||||||
|
import { ref, onMounted, watch, getCurrentInstance } from "vue";
|
||||||
|
|
||||||
|
import { usePreferenciesStore } from "@/stores/preferencies.ts";
|
||||||
|
|
||||||
|
const preferenciesStore = usePreferenciesStore();
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Base>
|
||||||
|
<div class="flex gap-4 mt-4 ml-auto mr-auto content ">
|
||||||
|
<Router-View />
|
||||||
|
<div>
|
||||||
|
<div class="border rounded border-zinc-500 flex-col w-64">
|
||||||
|
<h1 class="pl-5 pr-5 pt-2 pb-2">User Preferencies</h1>
|
||||||
|
<RouterLink :to="{ name: 'Preferencies-Profile' }"
|
||||||
|
:class="{ 'bg-zinc-600': preferenciesStore.current_tab === 0 }"
|
||||||
|
class="flex min-w-7 pl-5 pr-5 pt-2 pb-2 hover:bg-zinc-600 border-t border-zinc-500">
|
||||||
|
Profile</RouterLink>
|
||||||
|
<RouterLink :to="{ name: 'Preferencies-Account' }"
|
||||||
|
:class="{ 'bg-zinc-600': preferenciesStore.current_tab === 1 }"
|
||||||
|
class="flex min-w-7 pl-5 pr-5 pt-2 pb-2 hover:bg-zinc-600 border-t border-zinc-500">
|
||||||
|
Account</RouterLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Base>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.content {
|
||||||
|
width: 1280px;
|
||||||
|
max-width: calc(100% - 64px);
|
||||||
|
}
|
||||||
|
</style>
|
53
crates/frontend/src/views/user/Profile.vue
Normal file
53
crates/frontend/src/views/user/Profile.vue
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import Base from "@/views/Base.vue";
|
||||||
|
import Error from "@/components/error/Error.vue";
|
||||||
|
|
||||||
|
import { ref, onMounted, watch, getCurrentInstance } from "vue";
|
||||||
|
import { onBeforeRouteUpdate, useRoute } from "vue-router"
|
||||||
|
|
||||||
|
import User from "@/services/user";
|
||||||
|
import { useUserStore } from "@/stores/user";
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const name = ref<string>(null);
|
||||||
|
const userStore = useUserStore();
|
||||||
|
const error = ref<string>(null);
|
||||||
|
|
||||||
|
async function user_profile(login: string) {
|
||||||
|
await User.get(login)
|
||||||
|
.then(async response => {
|
||||||
|
error.value = null;
|
||||||
|
|
||||||
|
if (response.status != 200) {
|
||||||
|
return Promise.reject(response.data && response.data.message || response.status);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (response.data.hasOwnProperty("user")) {
|
||||||
|
name.value = response.data.user.name;
|
||||||
|
} else {
|
||||||
|
error.value = "404 Not Found";
|
||||||
|
};
|
||||||
|
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
console.error("Error occured:", e);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await user_profile(route.params.user);
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(route, async (to, from) => {
|
||||||
|
await user_profile(to.params.user);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Base>
|
||||||
|
<div class="ml-auto mr-auto w-1/2 pt-5 pb-5">
|
||||||
|
<Error v-if="error">{{ error }}</Error>
|
||||||
|
<p v-else>{{ name }}</p>
|
||||||
|
</div>
|
||||||
|
</Base>
|
||||||
|
</template>
|
@ -1,11 +1,18 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import Base from "@/views/Base.vue";
|
||||||
|
import Error from "@/components/error/Error.vue";
|
||||||
|
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
|
|
||||||
import router from "@/router";
|
import router from "@/router";
|
||||||
import User from "@/services/user";
|
import User from "@/services/user";
|
||||||
|
import { useUserStore } from "@/stores/user";
|
||||||
|
|
||||||
const email = defineModel("email");
|
const email = defineModel("email");
|
||||||
const password = defineModel("password");
|
const password = defineModel("password");
|
||||||
const errorMessage = ref(null);
|
|
||||||
|
const error = ref(null);
|
||||||
|
const userStore = useUserStore();
|
||||||
|
|
||||||
async function login() {
|
async function login() {
|
||||||
await User.login(email.value, password.value)
|
await User.login(email.value, password.value)
|
||||||
@ -14,17 +21,18 @@ async function login() {
|
|||||||
return Promise.reject(response.data && response.data.message || response.status);
|
return Promise.reject(response.data && response.data.message || response.status);
|
||||||
}
|
}
|
||||||
|
|
||||||
const login = response.data.user.login;
|
userStore.login = response.data.user.login;
|
||||||
router.push({ path: `/${login}` });
|
router.push({ path: `/${userStore.login}` });
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(e => {
|
||||||
errorMessage.value = error;
|
error.value = e;
|
||||||
console.error(error);
|
console.log(`${e.name}[${e.code}]: ${e.message}`);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<Base>
|
||||||
<div class="ml-auto mr-auto w-1/2 pt-5 pb-5">
|
<div class="ml-auto mr-auto w-1/2 pt-5 pb-5">
|
||||||
<h4 class="text-center pt-5 pb-5 border-b border-zinc-500">Sign In</h4>
|
<h4 class="text-center pt-5 pb-5 border-b border-zinc-500">Sign In</h4>
|
||||||
<form @submit.prevent class="m-auto pt-5 pb-5">
|
<form @submit.prevent class="m-auto pt-5 pb-5">
|
||||||
@ -40,11 +48,17 @@ async function login() {
|
|||||||
</div>
|
</div>
|
||||||
<div class="mb-5 ml-auto mr-auto">
|
<div class="mb-5 ml-auto mr-auto">
|
||||||
<label class="text-right w-64 inline-block mr-5"></label>
|
<label class="text-right w-64 inline-block mr-5"></label>
|
||||||
|
<div class="flex justify-between items-center w-1/2 m-auto">
|
||||||
<button @click="login" class="rounded bg-zinc-500 hover:bg-zinc-400 pb-2 pt-2 pl-5 pr-5">Sign
|
<button @click="login" class="rounded bg-zinc-500 hover:bg-zinc-400 pb-2 pt-2 pl-5 pr-5">Sign
|
||||||
In</button>
|
In</button>
|
||||||
|
<p>or</p>
|
||||||
|
<button @click="$router.push('/user/register')"
|
||||||
|
class="rounded bg-zinc-500 hover:bg-zinc-400 pb-2 pt-2 pl-5 pr-5">Sign
|
||||||
|
Up</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<p v-if="errorMessage" class="text-center pt-3 pb-3 bg-orange-900 rounded border border-orange-700">{{
|
<Error v-if="error">{{ error }}</Error>
|
||||||
errorMessage }}</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
</Base>
|
||||||
</template>
|
</template>
|
61
crates/frontend/src/views/user/SignUp.vue
Normal file
61
crates/frontend/src/views/user/SignUp.vue
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import Base from "@/views/Base.vue";
|
||||||
|
import Error from "@/components/error/Error.vue";
|
||||||
|
|
||||||
|
import { ref } from "vue";
|
||||||
|
|
||||||
|
import router from "@/router";
|
||||||
|
import User from "@/services/user";
|
||||||
|
|
||||||
|
const login = defineModel("login");
|
||||||
|
const email = defineModel("email");
|
||||||
|
const password = defineModel("password");
|
||||||
|
|
||||||
|
const error = ref(null);
|
||||||
|
|
||||||
|
async function register() {
|
||||||
|
await User.register(login.value, email.value, password.value)
|
||||||
|
.then(async response => {
|
||||||
|
if (response.status != 200) {
|
||||||
|
return Promise.reject(response.data && response.data.message || response.status);
|
||||||
|
}
|
||||||
|
|
||||||
|
router.push({ path: "/user/login" });
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
error.value = e;
|
||||||
|
console.log(`${e.name}[${e.code}]: ${e.message}`);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Base>
|
||||||
|
<div class="ml-auto mr-auto w-1/2 pt-5 pb-5">
|
||||||
|
<h4 class="text-center pt-5 pb-5 border-b border-zinc-500">Sign Up</h4>
|
||||||
|
<form @submit.prevent class="m-auto pt-5 pb-5">
|
||||||
|
<div class="mb-5 ml-auto mr-auto">
|
||||||
|
<label for="login" class="text-right w-64 inline-block mr-5">Login</label>
|
||||||
|
<input v-model="login" type="" placeholder="" name="login" required
|
||||||
|
class="w-1/2 bg-zinc-800 pl-3 pr-3 pt-2 pb-2 outline-none rounded border border-zinc-500 hover:border-zinc-400 focus:border-green-800">
|
||||||
|
</div>
|
||||||
|
<div class="mb-5 ml-auto mr-auto">
|
||||||
|
<label for="email" class="text-right w-64 inline-block mr-5">Email Address</label>
|
||||||
|
<input v-model="email" type="email" placeholder="" name="email" required
|
||||||
|
class="w-1/2 bg-zinc-800 pl-3 pr-3 pt-2 pb-2 outline-none rounded border border-zinc-500 hover:border-zinc-400 focus:border-green-800">
|
||||||
|
</div>
|
||||||
|
<div class="mb-5 ml-auto mr-auto">
|
||||||
|
<label for="password" class="text-right w-64 inline-block mr-5">Password</label>
|
||||||
|
<input v-model="password" placeholder="" type="password" name="password" required
|
||||||
|
class="w-1/2 bg-zinc-800 pl-3 pr-3 pt-2 pb-2 outline-none rounded border border-zinc-500 hover:border-zinc-400 focus:border-green-800">
|
||||||
|
</div>
|
||||||
|
<div class="mb-5 ml-auto mr-auto">
|
||||||
|
<label class="text-right w-64 inline-block mr-5"></label>
|
||||||
|
<button @click="register" class="rounded bg-zinc-500 hover:bg-zinc-400 pb-2 pt-2 pl-5 pr-5">Sign
|
||||||
|
Up</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<Error v-if="error">{{ error }}</Error>
|
||||||
|
</div>
|
||||||
|
</Base>
|
||||||
|
</template>
|
108
crates/frontend/src/views/user/preferencies/Account.vue
Normal file
108
crates/frontend/src/views/user/preferencies/Account.vue
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import Base from "@/views/Base.vue";
|
||||||
|
|
||||||
|
import { ref, onMounted, watch, getCurrentInstance } from "vue";
|
||||||
|
|
||||||
|
import router from "@/router";
|
||||||
|
import User from "@/services/user";
|
||||||
|
import { useUserStore } from "@/stores/user";
|
||||||
|
import { usePreferenciesStore } from "@/stores/preferencies.ts";
|
||||||
|
|
||||||
|
const password = defineModel("password");
|
||||||
|
const new_password = defineModel("new-password");
|
||||||
|
const confirm_new_password = defineModel("confirm-new-password");
|
||||||
|
|
||||||
|
const email = defineModel("email");
|
||||||
|
const new_email = defineModel("new-email");
|
||||||
|
|
||||||
|
const confirm_password = defineModel("confirm-password");
|
||||||
|
|
||||||
|
const error = ref(null);
|
||||||
|
const userStore = useUserStore();
|
||||||
|
const preferenciesStore = usePreferenciesStore();
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
preferenciesStore.current_tab = 1;
|
||||||
|
|
||||||
|
!userStore.login ? router.push({ name: "SignIn" }) : await User.current()
|
||||||
|
.then(async response => {
|
||||||
|
error.value = null;
|
||||||
|
|
||||||
|
if (response.status != 200) {
|
||||||
|
return Promise.reject(response.data && response.data.message || response.status);
|
||||||
|
};
|
||||||
|
|
||||||
|
email.value = response.data.user.email;
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
error.value = e;
|
||||||
|
console.log(`${e.name}[${e.code}]: ${e.message}`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="flex flex-col gap-4 ml-auto mr-auto w-full">
|
||||||
|
<div class="border rounded border-zinc-500 w-full flex-col">
|
||||||
|
<h1 class="pl-5 pr-5 pt-2 pb-2">Password</h1>
|
||||||
|
<div class="border-t border-zinc-500 p-5">
|
||||||
|
<form @submit.prevent class="">
|
||||||
|
<div>
|
||||||
|
<label class="block mb-2" for="password">Current password</label>
|
||||||
|
<input v-model="password" name="password" type="password"
|
||||||
|
class="w-full bg-zinc-800 pl-3 pr-3 pt-2 pb-2 mb-4 outline-none rounded border border-zinc-500 hover:border-zinc-400 focus:border-green-800">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block mb-2" for="new-password">New password</label>
|
||||||
|
<input v-model="new_password" name="new-password" type="password"
|
||||||
|
class="w-full bg-zinc-800 pl-3 pr-3 pt-2 pb-2 mb-4 outline-none rounded border border-zinc-500 hover:border-zinc-400 focus:border-green-800">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block mb-2" for="confirm-new-password">Confirm new password</label>
|
||||||
|
<input v-model="confirm_new_password" name="confirm-new-password" type="password"
|
||||||
|
class="w-full bg-zinc-800 pl-3 pr-3 pt-2 pb-2 mb-4 outline-none rounded border border-zinc-500 hover:border-zinc-400 focus:border-green-800">
|
||||||
|
</div>
|
||||||
|
<div class="border-t border-zinc-500 ml-0 mr-0 mt-3 mb-3"></div>
|
||||||
|
<button class="rounded bg-zinc-500 hover:bg-zinc-400 pb-2 pt-2 pl-5 pr-5 ml-auto mr-0 block">Update
|
||||||
|
password</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="border rounded border-zinc-500 w-full flex-col">
|
||||||
|
<h1 class="pl-5 pr-5 pt-2 pb-2">Email</h1>
|
||||||
|
<div class="border-t border-zinc-500 p-5">
|
||||||
|
<form @submit.prevent class="">
|
||||||
|
<div>
|
||||||
|
<label class="block mb-2" for="email">Email</label>
|
||||||
|
<strong class="block w-full mb-4">{{ email }}</strong>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block mb-2" for="new-email">New email</label>
|
||||||
|
<input v-model="new_email" name="new-email" type="email"
|
||||||
|
class="w-full bg-zinc-800 pl-3 pr-3 pt-2 pb-2 mb-4 outline-none rounded border border-zinc-500 hover:border-zinc-400 focus:border-green-800">
|
||||||
|
</div>
|
||||||
|
<div class="border-t border-zinc-500 ml-0 mr-0 mt-3 mb-3"></div>
|
||||||
|
<button class="rounded bg-zinc-500 hover:bg-zinc-400 pb-2 pt-2 pl-5 pr-5 ml-auto mr-0 block">Update
|
||||||
|
email</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="border rounded border-red-500 w-full flex-col">
|
||||||
|
<h1 class="pl-5 pr-5 pt-2 pb-2">Delete account</h1>
|
||||||
|
<div class="border-t border-red-500 p-5">
|
||||||
|
<form @submit.prevent class="">
|
||||||
|
<div>
|
||||||
|
<label class="block mb-2" for="confirm-password">Password</label>
|
||||||
|
<input v-model="confirm_password" name="confirm-password" type="password"
|
||||||
|
class="w-full bg-zinc-800 pl-3 pr-3 pt-2 pb-2 mb-4 outline-none rounded border border-zinc-500 hover:border-zinc-400 focus:border-green-800">
|
||||||
|
</div>
|
||||||
|
<div class="border-t border-zinc-500 ml-0 mr-0 mt-3 mb-3"></div>
|
||||||
|
<button
|
||||||
|
class="rounded bg-red-500 hover:bg-red-400 pb-2 pt-2 pl-5 pr-5 ml-auto mr-0 block">Confirm</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
67
crates/frontend/src/views/user/preferencies/Profile.vue
Normal file
67
crates/frontend/src/views/user/preferencies/Profile.vue
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import Base from "@/views/Base.vue";
|
||||||
|
|
||||||
|
import { ref, onMounted, watch, getCurrentInstance } from "vue";
|
||||||
|
|
||||||
|
import router from "@/router";
|
||||||
|
import User from "@/services/user";
|
||||||
|
import { useUserStore } from "@/stores/user";
|
||||||
|
import { usePreferenciesStore } from "@/stores/preferencies.ts";
|
||||||
|
|
||||||
|
const login = defineModel("login");
|
||||||
|
const name = defineModel("name");
|
||||||
|
const email = defineModel("email");
|
||||||
|
|
||||||
|
const error = ref(null);
|
||||||
|
const userStore = useUserStore();
|
||||||
|
const preferenciesStore = usePreferenciesStore();
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
preferenciesStore.current_tab = 0;
|
||||||
|
|
||||||
|
!userStore.login ? router.push({ name: "SignIn" }) : await User.current()
|
||||||
|
.then(async response => {
|
||||||
|
error.value = null;
|
||||||
|
|
||||||
|
if (response.status != 200) {
|
||||||
|
return Promise.reject(response.data && response.data.message || response.status);
|
||||||
|
};
|
||||||
|
|
||||||
|
login.value = response.data.user.login;
|
||||||
|
name.value = response.data.user.name;
|
||||||
|
email.value = response.data.user.email;
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
error.value = e;
|
||||||
|
console.log(`${e.name}[${e.code}]: ${e.message}`);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="border rounded border-zinc-500 w-full flex-col">
|
||||||
|
<h1 class="pl-5 pr-5 pt-2 pb-2">Profile Info</h1>
|
||||||
|
<div class="border-t border-zinc-500 p-5">
|
||||||
|
<form @submit.prevent class="">
|
||||||
|
<div>
|
||||||
|
<label class="block mb-2" for="login">Login</label>
|
||||||
|
<input v-model="login" name="login"
|
||||||
|
class="w-full bg-zinc-800 pl-3 pr-3 pt-2 pb-2 mb-4 outline-none rounded border border-zinc-500 hover:border-zinc-400 focus:border-green-800">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block mb-2" for="name">Username</label>
|
||||||
|
<input v-model="name" name="name"
|
||||||
|
class="w-full bg-zinc-800 pl-3 pr-3 pt-2 pb-2 mb-4 outline-none rounded border border-zinc-500 hover:border-zinc-400 focus:border-green-800">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block mb-2 " for="email">Email</label>
|
||||||
|
<input v-model="email" email="email" disabled
|
||||||
|
class="w-full bg-zinc-800 pl-3 pr-3 pt-2 pb-2 mb-4 outline-none rounded border border-zinc-500 hover:border-zinc-400 focus:border-green-800">
|
||||||
|
</div>
|
||||||
|
<div class="border-t border-zinc-500 ml-0 mr-0 mt-3 mb-3"></div>
|
||||||
|
<button
|
||||||
|
class="rounded bg-zinc-500 hover:bg-zinc-400 pb-2 pt-2 pl-5 pr-5 ml-auto mr-0 block">Update</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
@ -15,7 +15,7 @@ pub enum AuthError<E> {
|
|||||||
impl<E: std::error::Error> IntoResponse for AuthError<E> {
|
impl<E: std::error::Error> IntoResponse for AuthError<E> {
|
||||||
fn into_response(self) -> axum::response::Response {
|
fn into_response(self) -> axum::response::Response {
|
||||||
let (status, message) = match self {
|
let (status, message) = match self {
|
||||||
Self::InternalError(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()),
|
Self::InternalError(e) => (StatusCode::INTERNAL_SERVER_ERROR, format!("Internal Error: {}", e.to_string())),
|
||||||
Self::InternalE => (StatusCode::INTERNAL_SERVER_ERROR, "Internal E".to_string()),
|
Self::InternalE => (StatusCode::INTERNAL_SERVER_ERROR, "Internal E".to_string()),
|
||||||
Self::MissingCredentials => {
|
Self::MissingCredentials => {
|
||||||
(StatusCode::BAD_REQUEST, "Missing credentials".to_string())
|
(StatusCode::BAD_REQUEST, "Missing credentials".to_string())
|
||||||
@ -28,10 +28,13 @@ impl<E: std::error::Error> IntoResponse for AuthError<E> {
|
|||||||
Self::MissingUser => (StatusCode::UNAUTHORIZED, "User not exists".to_string()),
|
Self::MissingUser => (StatusCode::UNAUTHORIZED, "User not exists".to_string()),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
(
|
||||||
|
status,
|
||||||
Json(json!({
|
Json(json!({
|
||||||
"status": status.to_string(),
|
"status": status.to_string(),
|
||||||
"message": message
|
"message": message
|
||||||
}))
|
})),
|
||||||
|
)
|
||||||
.into_response()
|
.into_response()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,9 +22,7 @@ use super::token::TokenClaims;
|
|||||||
pub struct RegisterUser {
|
pub struct RegisterUser {
|
||||||
pub login: String,
|
pub login: String,
|
||||||
pub password: String,
|
pub password: String,
|
||||||
pub name: String,
|
|
||||||
pub email: String,
|
pub email: String,
|
||||||
pub is_admin: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Serialize)]
|
#[derive(serde::Serialize)]
|
||||||
@ -65,11 +63,11 @@ pub async fn register(
|
|||||||
) -> Result<impl IntoResponse, AuthError<impl std::error::Error>> {
|
) -> Result<impl IntoResponse, AuthError<impl std::error::Error>> {
|
||||||
let user = User::register(
|
let user = User::register(
|
||||||
&state.database,
|
&state.database,
|
||||||
body.login,
|
body.login.to_owned(),
|
||||||
body.password,
|
body.password,
|
||||||
body.name,
|
body.login, //body.name,
|
||||||
body.email,
|
body.email,
|
||||||
body.is_admin,
|
false, //body.is_admin,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(AuthError::InternalError)?;
|
.map_err(AuthError::InternalError)?;
|
||||||
|
Loading…
Reference in New Issue
Block a user