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": {
|
||||
"autoprefixer": "^10.4.18",
|
||||
"axios": "^1.6.8",
|
||||
"pinia": "^2.1.7",
|
||||
"postcss": "^8.4.35",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"vue": "^3.3.11",
|
||||
@ -2468,6 +2469,56 @@
|
||||
"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": {
|
||||
"version": "4.0.6",
|
||||
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz",
|
||||
|
@ -13,6 +13,7 @@
|
||||
"dependencies": {
|
||||
"autoprefixer": "^10.4.18",
|
||||
"axios": "^1.6.8",
|
||||
"pinia": "^2.1.7",
|
||||
"postcss": "^8.4.35",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"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 router from '@/router';
|
||||
import App from "@/App.vue";
|
||||
|
||||
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)
|
||||
.use(createPinia())
|
||||
.use(router)
|
||||
.directive("click-outside", click_outside)
|
||||
.mount('#app');
|
||||
|
||||
|
@ -3,10 +3,22 @@ import { createRouter, createWebHistory } from "vue-router";
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes: [
|
||||
{ path: "/", component: () => import("@/views/Home.vue") },
|
||||
{ path: "/user/login", component: () => import("@/views/SignIn.vue") },
|
||||
{ path: "/:user", name: "User", component: () => import("@/views/User.vue") },
|
||||
{ path: "/:pathMatch(.*)*", component: () => import("@/views/Error.vue") }
|
||||
{ path: "/", name: "Home", component: () => import("@/views/Home.vue") },
|
||||
|
||||
{ path: "/user/login", name: "SignIn", component: () => import("@/views/user/SignIn.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";
|
||||
|
||||
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> {
|
||||
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> {
|
||||
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">
|
||||
import Meerkat from '@/components/icons/Meerkat.vue';
|
||||
import NavBar from '@/components/NavBar.vue';
|
||||
import Meerkat from "@/components/icons/Meerkat.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 { ref, onMounted } from 'vue';
|
||||
import { useUserStore } from "@/stores/user";
|
||||
|
||||
const user = ref(null);
|
||||
const userStore = useUserStore();
|
||||
const error = ref<string>(null);
|
||||
|
||||
onMounted(async () => {
|
||||
await User.current()
|
||||
@ -13,13 +20,31 @@ onMounted(async () => {
|
||||
return Promise.reject(response.data && response.data.message || response.status);
|
||||
};
|
||||
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 => {
|
||||
console.error("Error occured:", e);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -29,10 +54,35 @@ onMounted(async () => {
|
||||
<Meerkat />
|
||||
</template>
|
||||
<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"
|
||||
:to="{ name: 'User', params: { user: user.login } }">{{ user.name }}</RouterLink>
|
||||
<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"
|
||||
to="/user/login">
|
||||
<DropdownMenu v-if="userStore.login">
|
||||
<template #button>
|
||||
<span
|
||||
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>
|
||||
</template>
|
||||
</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">
|
||||
import Base from "@/views/Base.vue";
|
||||
import Error from "@/components/Error.vue";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Base>
|
||||
<Error>Not Found</Error>
|
||||
<div>
|
||||
|
||||
</div>
|
||||
</Base>
|
||||
</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">
|
||||
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";
|
||||
import { useUserStore } from "@/stores/user";
|
||||
|
||||
const email = defineModel("email");
|
||||
const password = defineModel("password");
|
||||
const errorMessage = ref(null);
|
||||
|
||||
const error = ref(null);
|
||||
const userStore = useUserStore();
|
||||
|
||||
async function login() {
|
||||
await User.login(email.value, password.value)
|
||||
@ -14,17 +21,18 @@ async function login() {
|
||||
return Promise.reject(response.data && response.data.message || response.status);
|
||||
}
|
||||
|
||||
const login = response.data.user.login;
|
||||
router.push({ path: `/${login}` });
|
||||
userStore.login = response.data.user.login;
|
||||
router.push({ path: `/${userStore.login}` });
|
||||
})
|
||||
.catch(error => {
|
||||
errorMessage.value = error;
|
||||
console.error(error);
|
||||
.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 In</h4>
|
||||
<form @submit.prevent class="m-auto pt-5 pb-5">
|
||||
@ -40,11 +48,17 @@ async function login() {
|
||||
</div>
|
||||
<div class="mb-5 ml-auto mr-auto">
|
||||
<label class="text-right w-64 inline-block mr-5"></label>
|
||||
<button @click="login" class="rounded bg-zinc-500 hover:bg-zinc-400 pb-2 pt-2 pl-5 pr-5">Sign
|
||||
In</button>
|
||||
<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
|
||||
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>
|
||||
</form>
|
||||
<p v-if="errorMessage" class="text-center pt-3 pb-3 bg-orange-900 rounded border border-orange-700">{{
|
||||
errorMessage }}</p>
|
||||
<Error v-if="error">{{ error }}</Error>
|
||||
</div>
|
||||
</Base>
|
||||
</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> {
|
||||
fn into_response(self) -> axum::response::Response {
|
||||
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::MissingCredentials => {
|
||||
(StatusCode::BAD_REQUEST, "Missing credentials".to_string())
|
||||
@ -28,11 +28,14 @@ impl<E: std::error::Error> IntoResponse for AuthError<E> {
|
||||
Self::MissingUser => (StatusCode::UNAUTHORIZED, "User not exists".to_string()),
|
||||
};
|
||||
|
||||
Json(json!({
|
||||
"status": status.to_string(),
|
||||
"message": message
|
||||
}))
|
||||
.into_response()
|
||||
(
|
||||
status,
|
||||
Json(json!({
|
||||
"status": status.to_string(),
|
||||
"message": message
|
||||
})),
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -22,9 +22,7 @@ use super::token::TokenClaims;
|
||||
pub struct RegisterUser {
|
||||
pub login: String,
|
||||
pub password: String,
|
||||
pub name: String,
|
||||
pub email: String,
|
||||
pub is_admin: bool,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
@ -65,11 +63,11 @@ pub async fn register(
|
||||
) -> Result<impl IntoResponse, AuthError<impl std::error::Error>> {
|
||||
let user = User::register(
|
||||
&state.database,
|
||||
body.login,
|
||||
body.login.to_owned(),
|
||||
body.password,
|
||||
body.name,
|
||||
body.login, //body.name,
|
||||
body.email,
|
||||
body.is_admin,
|
||||
false, //body.is_admin,
|
||||
)
|
||||
.await
|
||||
.map_err(AuthError::InternalError)?;
|
||||
|
Loading…
Reference in New Issue
Block a user