Registration and activation
This commit is contained in:
22
frontend/package-lock.json
generated
22
frontend/package-lock.json
generated
@@ -9,6 +9,7 @@
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"axios": "^1.7.2",
|
||||
"dotenv": "^16.4.5",
|
||||
"vue": "~3.4.31",
|
||||
"vue-i18n": "^10.0.0-beta.2",
|
||||
"vue-router": "^4.0.13",
|
||||
@@ -762,6 +763,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/cli-service/node_modules/dotenv": {
|
||||
"version": "10.0.0",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz",
|
||||
"integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/cli-shared-utils": {
|
||||
"version": "5.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@vue/cli-shared-utils/-/cli-shared-utils-5.0.8.tgz",
|
||||
@@ -2835,12 +2845,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/dotenv": {
|
||||
"version": "10.0.0",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz",
|
||||
"integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==",
|
||||
"dev": true,
|
||||
"version": "16.4.5",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz",
|
||||
"integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://dotenvx.com"
|
||||
}
|
||||
},
|
||||
"node_modules/dotenv-expand": {
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.7.2",
|
||||
"dotenv": "^16.4.5",
|
||||
"vue": "~3.4.31",
|
||||
"vue-i18n": "^10.0.0-beta.2",
|
||||
"vue-router": "^4.0.13",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<AppHeader />
|
||||
<AppNavigation v-if="isLoggedIn" />
|
||||
<AppNavigation v-if="isLoggedIn && user.active" />
|
||||
<AppContent />
|
||||
<AppFooter />
|
||||
</div>
|
||||
@@ -20,14 +20,18 @@ export default {
|
||||
document.title = 'yourPart';
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['isLoggedIn'])
|
||||
...mapGetters(['isLoggedIn', 'user'])
|
||||
},
|
||||
components: {
|
||||
AppHeader,
|
||||
AppNavigation,
|
||||
AppContent,
|
||||
AppFooter
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.$store.dispatch('loadLoginState');
|
||||
this.$i18n.locale = this.$store.getters.language;
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -37,5 +41,4 @@ export default {
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -46,4 +46,9 @@ button:hover {
|
||||
.rc-partner {
|
||||
color: #0000ff;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.link {
|
||||
color: #F9A22C;
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -43,12 +43,13 @@ export default {
|
||||
<style lang="scss" scoped>
|
||||
@import '../assets/styles.scss';
|
||||
|
||||
nav {
|
||||
nav > ul{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
background-color: #343a40;
|
||||
color: white;
|
||||
padding: 10px;
|
||||
flex-direction: row;
|
||||
}
|
||||
ul {
|
||||
list-style-type: none;
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<template>
|
||||
<div v-if="visible" :class="['dialog-overlay', { 'non-modal': !modal }]" @click.self="handleOverlayClick">
|
||||
<div class="dialog" :class="{ minimized: minimized }" :style="{ width: dialogWidth, height: dialogHeight }" v-if="!minimized">
|
||||
<div class="dialog" :class="{ minimized: minimized }" :style="{ width: dialogWidth, height: dialogHeight }"
|
||||
v-if="!minimized">
|
||||
<div class="dialog-header">
|
||||
<span v-if="icon" class="dialog-icon">
|
||||
<img :src="'/images/icons/' + icon" alt="Icon" />
|
||||
<img :src="icon" alt="Icon" />
|
||||
</span>
|
||||
<span class="dialog-title">{{ isTitleTranslated ? $t(title) : title }}</span>
|
||||
<span v-if="!modal" class="dialog-minimize" @click="minimize">_</span>
|
||||
@@ -13,9 +14,8 @@
|
||||
<slot></slot>
|
||||
</div>
|
||||
<div class="dialog-footer">
|
||||
<button v-for="button in buttons" :key="button.text" @click="buttonClick(button.text)"
|
||||
class="dialog-button">
|
||||
{{ button.text }}
|
||||
<button v-for="button in buttons" :key="button.text" @click="buttonClick(button.action)" class="dialog-button">
|
||||
{{ isTitleTranslated ? $t(button.text) : button.text }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -40,7 +40,7 @@ export default {
|
||||
},
|
||||
buttons: {
|
||||
type: Array,
|
||||
default: () => [{ text: 'Ok' }]
|
||||
default: () => [{ text: 'Ok', action: 'close' }]
|
||||
},
|
||||
modal: {
|
||||
type: Boolean,
|
||||
@@ -87,7 +87,7 @@ export default {
|
||||
methods: {
|
||||
open() {
|
||||
this.visible = true;
|
||||
if (!this.modal) {
|
||||
if (this.modal === false) {
|
||||
this.$store.dispatch('dialogs/addOpenDialog', {
|
||||
status: 'open',
|
||||
dialog: this
|
||||
@@ -98,9 +98,11 @@ export default {
|
||||
this.visible = false;
|
||||
this.$store.dispatch('dialogs/removeOpenDialog', this.name);
|
||||
},
|
||||
buttonClick(buttonText) {
|
||||
this.$emit(buttonText.toLowerCase());
|
||||
this.close();
|
||||
buttonClick(action) {
|
||||
this.$emit(action);
|
||||
if (action === 'close') {
|
||||
this.close(); // Close dialog after button click if action is close
|
||||
}
|
||||
},
|
||||
handleOverlayClick() {
|
||||
if (!this.modal) {
|
||||
@@ -186,7 +188,7 @@ export default {
|
||||
|
||||
.dialog-body {
|
||||
flex-grow: 1;
|
||||
padding: 10px;
|
||||
padding: 20px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
@@ -197,4 +199,18 @@ export default {
|
||||
border-top: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.dialog-button {
|
||||
margin-left: 10px;
|
||||
padding: 10px 20px;
|
||||
cursor: pointer;
|
||||
background: #007bff;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
transition: background 0.3s;
|
||||
}
|
||||
|
||||
.dialog-button:hover {
|
||||
background: #0056b3;
|
||||
}
|
||||
</style>
|
||||
|
||||
45
frontend/src/dialogues/auth/PasswordResetDialog.vue
Normal file
45
frontend/src/dialogues/auth/PasswordResetDialog.vue
Normal file
@@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<DialogWidget ref="dialog" title="passwordReset.title" :show-close=true :buttons="buttons" @close="closeDialog" name="PasswordReset">
|
||||
<div>
|
||||
<label>{{ $t("passwordReset.email") }} <input type="email" v-model="email" required /></label>
|
||||
</div>
|
||||
</DialogWidget>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import apiClient from '@/utils/axios.js';
|
||||
import DialogWidget from '@/components/DialogWidget.vue';
|
||||
|
||||
export default {
|
||||
name: 'PasswordResetDialog',
|
||||
components: {
|
||||
DialogWidget,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
email: '',
|
||||
buttons: [{ text: this.$t("passwordReset.reset") }]
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
open() {
|
||||
this.$refs.dialog.open();
|
||||
},
|
||||
closeDialog() {
|
||||
this.$refs.dialog.close();
|
||||
},
|
||||
async resetPassword() {
|
||||
try {
|
||||
await apiClient.post('/api/users/requestPasswordReset', {
|
||||
email: this.email
|
||||
});
|
||||
this.$refs.dialog.close();
|
||||
alert(this.$t("passwordReset.success"));
|
||||
} catch (error) {
|
||||
console.error('Error resetting password:', error);
|
||||
alert(this.$t("passwordReset.failure"));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
134
frontend/src/dialogues/auth/RegisterDialog.vue
Normal file
134
frontend/src/dialogues/auth/RegisterDialog.vue
Normal file
@@ -0,0 +1,134 @@
|
||||
<template>
|
||||
<DialogWidget ref="dialog" title="register.title" :show-close="true" :buttons="buttons" :modal="true"
|
||||
@close="closeDialog" @register="register" width="35em" height="33em" name="RegisterDialog"
|
||||
:isTitleTranslated="true">
|
||||
<div class="form-content">
|
||||
<div>
|
||||
<label>{{ $t("register.email") }}<input type="email" v-model="email" /></label>
|
||||
</div>
|
||||
<div>
|
||||
<label>{{ $t("register.username") }}<input type="text" v-model="username" /></label>
|
||||
</div>
|
||||
<div>
|
||||
<label>{{ $t("register.password") }}<input type="password" v-model="password" /></label>
|
||||
</div>
|
||||
<div>
|
||||
<label>{{ $t("register.repeatPassword") }}<input type="password" v-model="repeatPassword" /></label>
|
||||
</div>
|
||||
<div>
|
||||
<label>{{ $t("register.language") }}<select v-model="language">
|
||||
<option value="en">{{ $t("register.languages.en") }}</option>
|
||||
<option value="de">{{ $t("register.languages.de") }}</option>
|
||||
</select></label>
|
||||
</div>
|
||||
</div>
|
||||
<ErrorDialog ref="errorDialog" />
|
||||
</DialogWidget>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapActions } from 'vuex';
|
||||
import apiClient from '@/utils/axios.js';
|
||||
import DialogWidget from '@/components/DialogWidget.vue';
|
||||
import ErrorDialog from '@/dialogues/standard/ErrorDialog.vue';
|
||||
|
||||
export default {
|
||||
name: 'RegisterDialog',
|
||||
components: {
|
||||
DialogWidget,
|
||||
ErrorDialog,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
email: '',
|
||||
username: '',
|
||||
password: '',
|
||||
repeatPassword: '',
|
||||
language: this.getBrowserLanguage(),
|
||||
buttons: [
|
||||
{ text: 'register.close', action: 'close' },
|
||||
{ text: 'register.register', action: 'register', disabled: !this.canRegister }
|
||||
]
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
canRegister() {
|
||||
return this.password && this.repeatPassword && this.password === this.repeatPassword;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
canRegister(newValue) {
|
||||
this.buttons[1].disabled = !newValue;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['login']),
|
||||
getBrowserLanguage() {
|
||||
const browserLanguage = navigator.language || navigator.languages[0];
|
||||
if (browserLanguage.startsWith('de')) {
|
||||
return 'de';
|
||||
} else {
|
||||
return 'en';
|
||||
}
|
||||
},
|
||||
open() {
|
||||
this.$refs.dialog.open();
|
||||
},
|
||||
closeDialog() {
|
||||
this.$refs.dialog.close();
|
||||
},
|
||||
async register() {
|
||||
if (!this.canRegister) {
|
||||
console.log('pw-fehler');
|
||||
this.$refs.errorDialog.open('tr:register.passwordMismatch');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await apiClient.post('/api/auth/register', {
|
||||
email: this.email,
|
||||
username: this.username,
|
||||
password: this.password,
|
||||
language: this.language
|
||||
});
|
||||
|
||||
if (response.status === 201) {
|
||||
console.log(response.data);
|
||||
this.login(response.data);
|
||||
this.$refs.dialog.close();
|
||||
this.$router.push('/activate');
|
||||
} else {
|
||||
this.$refs.errorDialog.open("tr:register.failure");
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.response && error.response.status === 409) {
|
||||
this.$refs.errorDialog.open('tr:register.' + error.response.data.error);
|
||||
} else {
|
||||
console.error('Error registering user:', error);
|
||||
this.$refs.errorDialog.open('tr:register.' + error.response.data.error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.form-content>div {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
input[type="email"],
|
||||
input[type="text"],
|
||||
input[type="password"],
|
||||
select {
|
||||
width: 100%;
|
||||
padding: 0.5em;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
</style>
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<DialogWidget ref="dialog" title="randomchat.title" icon="dice24.png" :show-close="true" :buttons="buttons"
|
||||
:modal="false" :isTitleTranslated="true" @close="closeDialog">
|
||||
<DialogWidget ref="dialog" title="randomchat.title" icon="dice24.png" :show-close=true :buttons="buttons"
|
||||
:modal=false :isTitleTranslated=true @close="closeDialog" name="RandomChat">
|
||||
<div v-if="chatIsRunning" class="randomchat">
|
||||
<div class="headline">
|
||||
{{ $t("randomchat.agerange") }}
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
<DialogWidget
|
||||
ref="dialog"
|
||||
title="dataPrivacy.title"
|
||||
isTitleTranslated=true
|
||||
:isTitleTranslated=true
|
||||
icon="privacy24.png"
|
||||
:show-close="true"
|
||||
:show-close=true
|
||||
:buttons="[{ text: 'Ok' }]"
|
||||
:modal="false"
|
||||
:modal=false
|
||||
@close="closeDialog"
|
||||
@ok="handleOk"
|
||||
>
|
||||
|
||||
52
frontend/src/dialogues/standard/ErrorDialog.vue
Normal file
52
frontend/src/dialogues/standard/ErrorDialog.vue
Normal file
@@ -0,0 +1,52 @@
|
||||
<template>
|
||||
<DialogWidget ref="dialog" title="error.title" :show-close="true" :buttons="buttons" :modal="true" width="25em"
|
||||
height="15em" name="ErrorDialog" :isTitleTranslated=true>
|
||||
<div class="error-content">
|
||||
<p>{{ translatedErrorMessage }}</p>
|
||||
</div>
|
||||
</DialogWidget>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import DialogWidget from '@/components/DialogWidget.vue';
|
||||
|
||||
export default {
|
||||
name: 'ErrorDialog',
|
||||
components: {
|
||||
DialogWidget,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
errorMessage: '',
|
||||
buttons: [
|
||||
{ text: 'error.close', action: 'close' }
|
||||
]
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
translatedErrorMessage() {
|
||||
if (this.errorMessage.startsWith('tr:')) {
|
||||
return this.$t(this.errorMessage.substring(3));
|
||||
}
|
||||
return this.errorMessage;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
open(message) {
|
||||
this.errorMessage = message;
|
||||
this.$refs.dialog.open();
|
||||
},
|
||||
close() {
|
||||
this.$refs.dialog.close();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.error-content {
|
||||
padding: 1em;
|
||||
color: red;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
@@ -2,11 +2,11 @@
|
||||
<DialogWidget
|
||||
ref="dialog"
|
||||
title="imprint.title"
|
||||
isTitleTranslated=true
|
||||
:isTitleTranslated=true
|
||||
icon="imprint24.png"
|
||||
:show-close="true"
|
||||
:buttons="[{ text: 'Ok' }]"
|
||||
:modal="false"
|
||||
:modal=false
|
||||
@close="closeDialog"
|
||||
@ok="handleOk"
|
||||
>
|
||||
|
||||
@@ -1,16 +1,23 @@
|
||||
import { createI18n } from 'vue-i18n';
|
||||
import store from '../store/index.js';
|
||||
|
||||
import enGeneral from './locales/en/general.json';
|
||||
import enHeader from './locales/en/header.json';
|
||||
import enNavigation from './locales/en/navigation.json';
|
||||
import enHome from './locales/en/home.json';
|
||||
import enChat from './locales/en/chat.json';
|
||||
import enRegister from './locales/en/register.json';
|
||||
import enError from './locales/en/error.json';
|
||||
import enActivate from './locales/en/activate.json';
|
||||
|
||||
import deGeneral from './locales/de/general.json';
|
||||
import deHeader from './locales/de/header.json';
|
||||
import deNavigation from './locales/de/navigation.json';
|
||||
import deHome from './locales/de/home.json';
|
||||
import deChat from './locales/de/chat.json';
|
||||
import deRegister from './locales/de/register.json';
|
||||
import deError from './locales/de/error.json';
|
||||
import deActivate from './locales/de/activate.json';
|
||||
|
||||
const messages = {
|
||||
en: {
|
||||
@@ -19,6 +26,9 @@ const messages = {
|
||||
...enNavigation,
|
||||
...enHome,
|
||||
...enChat,
|
||||
...enRegister,
|
||||
...enError,
|
||||
...enActivate,
|
||||
},
|
||||
de: {
|
||||
...deGeneral,
|
||||
@@ -26,11 +36,14 @@ const messages = {
|
||||
...deNavigation,
|
||||
...deHome,
|
||||
...deChat,
|
||||
...deRegister,
|
||||
...deError,
|
||||
...deActivate,
|
||||
}
|
||||
};
|
||||
|
||||
const i18n = createI18n({
|
||||
locale: 'de',
|
||||
locale: store.state.language,
|
||||
fallbackLocale: 'de',
|
||||
messages
|
||||
});
|
||||
|
||||
9
frontend/src/i18n/locales/de/activate.json
Normal file
9
frontend/src/i18n/locales/de/activate.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"activate": {
|
||||
"title": "Zugang aktivieren",
|
||||
"message": "Hallo {username}. Bitte gib hier den Code ein, den wir Dir per Email zugesendet haben.",
|
||||
"token": "Token:",
|
||||
"submit": "Absenden",
|
||||
"failure": "Die Aktivierung war nicht erfolgreich."
|
||||
}
|
||||
}
|
||||
6
frontend/src/i18n/locales/de/error.json
Normal file
6
frontend/src/i18n/locales/de/error.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"error": {
|
||||
"title": "Fehler aufgetreten",
|
||||
"close": "Schließen"
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,9 @@
|
||||
"name": "Login-Name",
|
||||
"namedescription": "Gib hier Deinen Benutzernamen ein",
|
||||
"password": "Paßwort",
|
||||
"passworddescription": "Gib hier Dein Paßwort ein"
|
||||
"passworddescription": "Gib hier Dein Paßwort ein",
|
||||
"lostpassword": "Paßwort vergessen",
|
||||
"register": "Bei yourPart registrieren"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
21
frontend/src/i18n/locales/de/register.json
Normal file
21
frontend/src/i18n/locales/de/register.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"register": {
|
||||
"title": "Bei yourPart registrieren",
|
||||
"email": "Email-Adresse",
|
||||
"username": "Benutzername",
|
||||
"password": "Paßwort",
|
||||
"repeatPassword": "Paßwort wiederholen",
|
||||
"language": "Sprache",
|
||||
"languages": {
|
||||
"en": "Englisch",
|
||||
"de": "Deutsch"
|
||||
},
|
||||
"register": "Registrieren",
|
||||
"close": "Schließen",
|
||||
"failure": "Es ist ein Fehler aufgetreten.",
|
||||
"success": "Du wurdest erfolgreich registriert. Bitte schaue jetzt in Dein Email-Postfach zum aktivieren Deines Zugangs.",
|
||||
"passwordMismatch": "Die Paßwörter stimmen nicht überein.",
|
||||
"emailinuse": "Die Email-Adresse wird bereits verwendet.",
|
||||
"usernameinuse": "Der Benutzername ist nicht verfügbar."
|
||||
}
|
||||
}
|
||||
3
frontend/src/i18n/locales/en/activate.json
Normal file
3
frontend/src/i18n/locales/en/activate.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
|
||||
}
|
||||
5
frontend/src/i18n/locales/en/error.json
Normal file
5
frontend/src/i18n/locales/en/error.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"error": {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
"home": {
|
||||
"nologin": {
|
||||
"welcome": "Welcome at yourPart",
|
||||
"description": "<platzhalter>",
|
||||
"description": "---platzhalter---",
|
||||
"randomchat": "Random chat",
|
||||
"startrandomchat": "Start random chat"
|
||||
}
|
||||
|
||||
3
frontend/src/i18n/locales/en/register.json
Normal file
3
frontend/src/i18n/locales/en/register.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
|
||||
}
|
||||
@@ -5,6 +5,18 @@ import router from './router';
|
||||
import './assets/styles.scss';
|
||||
import i18n from './i18n';
|
||||
|
||||
function getBrowserLanguage() {
|
||||
const browserLanguage = navigator.language || navigator.languages[0];
|
||||
console.log(browserLanguage);
|
||||
if (browserLanguage.startsWith('de')) {
|
||||
return 'de';
|
||||
} else {
|
||||
return 'en';
|
||||
}
|
||||
}
|
||||
|
||||
store.dispatch('setLanguage', getBrowserLanguage());
|
||||
|
||||
const app = createApp(App);
|
||||
|
||||
app.use(store);
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router';
|
||||
import HomeView from '../views/HomeView.vue';
|
||||
import ActivateView from '../views/auth/ActivateView.vue';
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: '/',
|
||||
name: 'Home',
|
||||
component: HomeView
|
||||
},
|
||||
{
|
||||
path: '/activate',
|
||||
name: 'Activate page',
|
||||
component: ActivateView
|
||||
}
|
||||
];
|
||||
|
||||
@@ -18,8 +24,10 @@ router.beforeEach((to, from, next) => {
|
||||
if (to.matched.some(record => record.meta.requiresAuth)) {
|
||||
if (!store.getters.isLoggedIn) {
|
||||
next('/');
|
||||
} else {
|
||||
} else if (!store.user.active) {
|
||||
next();
|
||||
} else {
|
||||
next('/activate');
|
||||
}
|
||||
} else {
|
||||
next();
|
||||
|
||||
@@ -4,16 +4,17 @@ import dialogs from './modules/dialogs';
|
||||
const store = createStore({
|
||||
state: {
|
||||
isLoggedIn: false,
|
||||
user: null
|
||||
user: null,
|
||||
language: navigator.language.startsWith('de') ? 'de' : 'en',
|
||||
},
|
||||
mutations: {
|
||||
login(state, user) {
|
||||
dologin(state, user) {
|
||||
state.isLoggedIn = true;
|
||||
state.user = user;
|
||||
localStorage.setItem('isLoggedIn', 'true');
|
||||
localStorage.setItem('user', JSON.stringify(user));
|
||||
},
|
||||
logout(state) {
|
||||
dologout(state) {
|
||||
state.isLoggedIn = false;
|
||||
state.user = null;
|
||||
localStorage.removeItem('isLoggedIn');
|
||||
@@ -30,22 +31,29 @@ const store = createStore({
|
||||
const user = userData;
|
||||
state.isLoggedIn = isLoggedIn;
|
||||
state.user = user;
|
||||
},
|
||||
setLanguage(state, language) {
|
||||
state.language = language;
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
login({ commit }, user) {
|
||||
commit('login', user);
|
||||
commit('dologin', user);
|
||||
},
|
||||
logout({ commit }) {
|
||||
commit('logout');
|
||||
commit('dologout');
|
||||
},
|
||||
loadLoginState({ commit }) {
|
||||
commit('loadLoginState');
|
||||
}
|
||||
},
|
||||
setLanguage({ commit }, language) {
|
||||
commit('setLanguage', language);
|
||||
},
|
||||
},
|
||||
getters: {
|
||||
isLoggedIn: state => state.isLoggedIn,
|
||||
user: state => state.user
|
||||
user: state => state.user,
|
||||
language: state => state.language,
|
||||
},
|
||||
modules: {
|
||||
dialogs,
|
||||
|
||||
10
frontend/src/utils/axios.js
Normal file
10
frontend/src/utils/axios.js
Normal file
@@ -0,0 +1,10 @@
|
||||
import axios from 'axios';
|
||||
|
||||
const apiClient = axios.create({
|
||||
baseURL: process.env.VUE_APP_API_BASE_URL || 'http://localhost:3001',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
export default apiClient;
|
||||
95
frontend/src/views/auth/ActivateView.vue
Normal file
95
frontend/src/views/auth/ActivateView.vue
Normal file
@@ -0,0 +1,95 @@
|
||||
<template>
|
||||
<div class="activate-container">
|
||||
<h1>{{ $t('activate.title') }}</h1>
|
||||
<p v-if="user">{{ $t('activate.message', { username: user.username }) }}</p>
|
||||
<form @submit.prevent="activateAccount">
|
||||
<div>
|
||||
<label>{{ $t('activate.token') }}</label>
|
||||
<input type="text" v-model="token" required />
|
||||
</div>
|
||||
<div>
|
||||
<button type="submit">{{ $t('activate.submit') }}</button>
|
||||
</div>
|
||||
</form>
|
||||
<ErrorDialog ref="errorDialog" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import apiClient from '@/utils/axios.js';
|
||||
import ErrorDialog from '@/dialogues/standard/ErrorDialog.vue';
|
||||
|
||||
export default {
|
||||
name: 'ActivateView',
|
||||
data() {
|
||||
return {
|
||||
token: this.$route.query.token || ''
|
||||
};
|
||||
},
|
||||
components: {
|
||||
ErrorDialog,
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['user'])
|
||||
},
|
||||
methods: {
|
||||
async activateAccount() {
|
||||
try {
|
||||
const response = await apiClient.post('/api/auth/activate', { token: this.token });
|
||||
if (response.status === 200) {
|
||||
this.user.active = true;
|
||||
this.$router.push('/'); // Redirect to login after activation
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error activating account:', error);
|
||||
this.$refs.errorDialog.open(this.$t('activate.failure'));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.activate-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
padding: 2em;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
input[type="text"] {
|
||||
width: 100%;
|
||||
padding: 0.5em;
|
||||
margin-bottom: 1em;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 0.5em 1em;
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: background 0.3s;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
</style>
|
||||
@@ -2,12 +2,22 @@
|
||||
<div>
|
||||
<h1>Welcome to Home (Logged In)</h1>
|
||||
<p>Here are your exclusive features.</p>
|
||||
<button @click="handleLogout">Logout</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapActions } from 'vuex';
|
||||
|
||||
|
||||
export default {
|
||||
name: 'HomeLoggedInView',
|
||||
methods: {
|
||||
...mapActions(['logout']),
|
||||
handleLogout() {
|
||||
this.logout();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
@@ -12,18 +12,19 @@
|
||||
<div>
|
||||
<div>
|
||||
<input data-object-name="user-name" size="20" type="text"
|
||||
:placeholder="$t('home.nologin.login.name')" :title="$t('home.nologin.login.namedescription')">
|
||||
:placeholder="$t('home.nologin.login.name')"
|
||||
:title="$t('home.nologin.login.namedescription')">
|
||||
</div>
|
||||
<div>
|
||||
<input data-object-name="password" size="20" type="password"
|
||||
:placeholder="$t('home.nologin.login.password')" :title="$t('home.nologin.login.passworddescription')">
|
||||
:placeholder="$t('home.nologin.login.password')"
|
||||
:title="$t('home.nologin.login.passworddescription')">
|
||||
</div>
|
||||
<div>
|
||||
<label id="o1p5irxv" name="o1p5irxv" class="Wt-valid" title=""><input id="ino1p5irxv"
|
||||
data-object-name="remember-me" name="ino1p5irxv" type="checkbox"
|
||||
onchange="var e=event||window.event,o=this;Wt._p_.update(o,'s53',e,true);"><span
|
||||
id="to1p5irxv" name="to1p5irxv" style="white-space:normal;">Eingeloggt bleiben
|
||||
(ACHTUNG!!! Dafür wird ein Cookie gesetzt!)</span></label>
|
||||
id="to1p5irxv" name="to1p5irxv" style="white-space:normal;">Eingeloggt bleiben</span></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="Wt-buttons">
|
||||
@@ -32,30 +33,41 @@
|
||||
class="Wt-btn with-label">Einloggen</button>
|
||||
</div>
|
||||
<div class="Wt-buttons">
|
||||
<span id="o1p5iry0" data-object-name="lost-password"
|
||||
onclick="var e=event||window.event,o=this;if(o.classList.contains('Wt-disabled')){Wt4_9_1.cancelEvent(e);return;}Wt._p_.update(o,'s57',e,true);">Ich
|
||||
habe mein Paßwort vergessen</span> | <span id="o1p5iry1" data-object-name="register"
|
||||
onclick="var e=event||window.event,o=this;if(o.classList.contains('Wt-disabled')){Wt4_9_1.cancelEvent(e);return;}Wt._p_.update(o,'s58',e,true);">Ich
|
||||
möchte mich neu anmelden</span>
|
||||
<span id="o1p5iry0" data-object-name="lost-password" @click="openPasswordResetDialog"
|
||||
class="link">{{
|
||||
$t('home.nologin.login.lostpassword') }}</span> | <span id="o1p5iry1"
|
||||
@click="openRegisterDialog" class="link">{{ $t('home.nologin.login.register') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mascot"><img src="/images/mascot/mascot_female.png" /></div>
|
||||
<RandomChatDialog ref="randomChatDialog" />
|
||||
<RegisterDialog ref="registerDialog" />
|
||||
<PasswordResetDialog ref="passwordResetDialog" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import RandomChatDialog from '@/dialogues/chat/RandomChatDialog.vue';
|
||||
import RegisterDialog from '@/dialogues/auth/RegisterDialog.vue';
|
||||
import PasswordResetDialog from '@/dialogues/auth/PasswordResetDialog.vue';
|
||||
|
||||
export default {
|
||||
export default {
|
||||
name: 'HomeNoLoginView',
|
||||
components: {
|
||||
RandomChatDialog,
|
||||
RegisterDialog,
|
||||
PasswordResetDialog,
|
||||
},
|
||||
methods: {
|
||||
openRandomChat() {
|
||||
this.$refs.randomChatDialog.open();
|
||||
},
|
||||
openRegisterDialog() {
|
||||
this.$refs.registerDialog.open();
|
||||
},
|
||||
openPasswordResetDialog() {
|
||||
this.$refs.passwordResetDialog.open();
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -68,33 +80,38 @@ export default {
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
gap: 2em;
|
||||
height:100%;
|
||||
height: 100%;
|
||||
}
|
||||
.home-structure > div {
|
||||
|
||||
.home-structure>div {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.mascot {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: #fdf1db;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2em;
|
||||
}
|
||||
.actions > div {
|
||||
|
||||
.actions>div {
|
||||
flex: 1;
|
||||
background-color: #fdf1db;
|
||||
align-items: center;
|
||||
justify-content:center;
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
color: #7E471B;
|
||||
flex-direction: column;
|
||||
}
|
||||
.actions > div > h2 {
|
||||
|
||||
.actions>div>h2 {
|
||||
color: #F9A22C;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user