Registration and activation
This commit is contained in:
@@ -2,15 +2,19 @@ import express from 'express';
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
import chatRouter from './routers/chatRouter.js';
|
import chatRouter from './routers/chatRouter.js';
|
||||||
import bodyParser from 'body-parser';
|
import authRouter from './routers/authRouter.js';
|
||||||
|
import cors from 'cors';
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = path.dirname(__filename);
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
app.use(bodyParser.json());
|
app.use(cors());
|
||||||
|
app.use(express.json()); // To handle JSON request bodies
|
||||||
|
|
||||||
app.use('/api/chat', chatRouter);
|
app.use('/api/chat', chatRouter);
|
||||||
|
app.use('/api/auth', authRouter);
|
||||||
app.use('/images', express.static(path.join(__dirname, '../frontend/public/images')));
|
app.use('/images', express.static(path.join(__dirname, '../frontend/public/images')));
|
||||||
|
|
||||||
app.get('*', (req, res) => {
|
app.get('*', (req, res) => {
|
||||||
|
|||||||
23
backend/config/config.json
Normal file
23
backend/config/config.json
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"development": {
|
||||||
|
"username": "yourpart",
|
||||||
|
"password": "hitomisan",
|
||||||
|
"database": "yp3",
|
||||||
|
"host": "127.0.0.1",
|
||||||
|
"dialect": "postgres"
|
||||||
|
},
|
||||||
|
"test": {
|
||||||
|
"username": "root",
|
||||||
|
"password": null,
|
||||||
|
"database": "database_test",
|
||||||
|
"host": "127.0.0.1",
|
||||||
|
"dialect": "mysql"
|
||||||
|
},
|
||||||
|
"production": {
|
||||||
|
"username": "root",
|
||||||
|
"password": null,
|
||||||
|
"database": "database_production",
|
||||||
|
"host": "127.0.0.1",
|
||||||
|
"dialect": "mysql"
|
||||||
|
}
|
||||||
|
}
|
||||||
97
backend/controllers/authController.js
Normal file
97
backend/controllers/authController.js
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
import bcrypt from 'bcrypt';
|
||||||
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
import User from '../models/community/user.js';
|
||||||
|
import UserParam from '../models/community/user_param.js';
|
||||||
|
import UserParamType from '../models/type/user_param.js';
|
||||||
|
import { sendAccountActivationEmail, sendPasswordResetEmail } from '../services/emailService.js';
|
||||||
|
import i18n from '../utils/i18n.js';
|
||||||
|
|
||||||
|
const saltRounds = 10;
|
||||||
|
|
||||||
|
export const register = async (req, res) => {
|
||||||
|
const { email, username, password, language } = req.body;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const hashedPassword = await bcrypt.hash(password, saltRounds);
|
||||||
|
const resetToken = uuidv4();
|
||||||
|
const user = await User.create({
|
||||||
|
email,
|
||||||
|
username,
|
||||||
|
password: hashedPassword,
|
||||||
|
resetToken: resetToken,
|
||||||
|
active: false,
|
||||||
|
registration_date: new Date()
|
||||||
|
});
|
||||||
|
const languageType = await UserParamType.findOne({ where: { description: 'language' } });
|
||||||
|
if (!languageType) {
|
||||||
|
return res.status(500).json({ error: 'Language type not found' });
|
||||||
|
}
|
||||||
|
console.log(user.id, languageType.id);
|
||||||
|
await UserParam.create({
|
||||||
|
userId: user.id,
|
||||||
|
paramTypeId: languageType.id,
|
||||||
|
value: language
|
||||||
|
});
|
||||||
|
const activationLink = `${process.env.FRONTEND_URL}/activate?token=${resetToken}`;
|
||||||
|
await sendAccountActivationEmail(email, activationLink, username, resetToken, language);
|
||||||
|
res.status(201).json({ id: user.hashedId, username: user.username, active: user.active });
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
res.status(500).json({ error: error.message });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const login = async (req, res) => {
|
||||||
|
const { email, password } = req.body;
|
||||||
|
try {
|
||||||
|
const user = await User.findOne({ where: { email } });
|
||||||
|
if (!user) {
|
||||||
|
return res.status(401).json({ error: 'Invalid email or password' });
|
||||||
|
}
|
||||||
|
if (!user.active) {
|
||||||
|
return res.status(403).json({ error: 'Account not activated' });
|
||||||
|
}
|
||||||
|
const match = await bcrypt.compare(password, user.password);
|
||||||
|
if (!match) {
|
||||||
|
return res.status(401).json({ error: 'Invalid email or password' });
|
||||||
|
}
|
||||||
|
res.status(200).json({ id: user.hashed_id, username: user.username });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ error: 'Error logging in' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const forgotPassword = async (req, res) => {
|
||||||
|
const { email } = req.body;
|
||||||
|
try {
|
||||||
|
const user = await User.findOne({ where: { email } });
|
||||||
|
if (!user) {
|
||||||
|
return res.status(404).json({ error: 'Email not found' });
|
||||||
|
}
|
||||||
|
const resetToken = uuidv4();
|
||||||
|
const resetLink = `${process.env.FRONTEND_URL}/reset-password?token=${resetToken}`;
|
||||||
|
await user.update({ reset_token: resetToken });
|
||||||
|
|
||||||
|
const languageParam = await UserParam.findOne({ where: { user_id: user.id, param_type_id: languageType.id } });
|
||||||
|
const userLanguage = languageParam ? languageParam.value : 'en';
|
||||||
|
|
||||||
|
await sendPasswordResetEmail(email, resetLink, userLanguage);
|
||||||
|
res.status(200).json({ message: 'Password reset email sent' });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ error: 'Error processing forgot password' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const activateAccount = async (req, res) => {
|
||||||
|
const { token } = req.body;
|
||||||
|
try {
|
||||||
|
const user = await User.findOne({ where: { reset_token: token } });
|
||||||
|
if (!user) {
|
||||||
|
return res.status(404).json({ error: 'Invalid token' });
|
||||||
|
}
|
||||||
|
await user.update({ active: true, reset_token: null });
|
||||||
|
res.status(200).json({ message: 'Account activated' });
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ error: 'Error activating account' });
|
||||||
|
}
|
||||||
|
};
|
||||||
6
backend/locales/de.json
Normal file
6
backend/locales/de.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"account_activation_subject": "Aktivierung Deines yourPart-Zugangs",
|
||||||
|
"account_activation_html": "<p>Hallo {{username}},</p><p>Herzlichen Dank für Deine Registrierung bei yourPart. Um Deinen Zugang zu erhalten, musst Du Ihn noch aktivieren. Dazu klicke bitte folgenden Link an:</p><p><a href='{{activationLink}}'>{{activationLink}}</a></p><p>Alternativ kannst Du auch nachfolgenden Code eingeben, wenn Du danach gefragt wirst:</p><p>{{resetToken}}</p><p>Dein yourPart-Team</p>",
|
||||||
|
"account_activation_text": "Hallo {{username}},\n\nHerzlichen Dank für Deine Registrierung bei yourPart. Um Deinen Zugang zu erhalten, musst Du Ihn noch aktivieren. Dazu klicke bitte folgenden Link an:\n\n{{activationLink}}\n\nAlternativ kannst Du auch nachfolgenden Code eingeben, wenn Du danach gefragt wirst:\n\n{{resetToken}}\n\nDein yourPart-Team",
|
||||||
|
"welcome": "welcome"
|
||||||
|
}
|
||||||
6
backend/locales/en.json
Normal file
6
backend/locales/en.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"account_activation_subject": "account_activation_subject",
|
||||||
|
"account_activation_text": "account_activation_text",
|
||||||
|
"welcome": "welcome",
|
||||||
|
"account_activation_html": "account_activation_html"
|
||||||
|
}
|
||||||
30
backend/migrations/add_trigger_for_hashedId.cjs
Normal file
30
backend/migrations/add_trigger_for_hashedId.cjs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
up: async (queryInterface, Sequelize) => {
|
||||||
|
await queryInterface.sequelize.query(`
|
||||||
|
CREATE OR REPLACE FUNCTION community.update_hashed_id() RETURNS TRIGGER AS $$
|
||||||
|
BEGIN
|
||||||
|
NEW.hashed_id = encode(digest(NEW.id::text, 'sha256'), 'hex');
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
`);
|
||||||
|
|
||||||
|
await queryInterface.sequelize.query(`
|
||||||
|
CREATE TRIGGER update_hashed_id_trigger
|
||||||
|
BEFORE INSERT OR UPDATE ON community.user
|
||||||
|
FOR EACH ROW EXECUTE FUNCTION community.update_hashed_id();
|
||||||
|
`);
|
||||||
|
},
|
||||||
|
|
||||||
|
down: async (queryInterface, Sequelize) => {
|
||||||
|
await queryInterface.sequelize.query(`
|
||||||
|
DROP TRIGGER IF EXISTS update_hashed_id_trigger ON community.user;
|
||||||
|
`);
|
||||||
|
|
||||||
|
await queryInterface.sequelize.query(`
|
||||||
|
DROP FUNCTION IF EXISTS community.update_hashed_id();
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
};
|
||||||
49
backend/models/community/user.js
Normal file
49
backend/models/community/user.js
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import { sequelize } from '../../utils/sequelize.js';
|
||||||
|
import { DataTypes } from 'sequelize';
|
||||||
|
import bcrypt from 'bcrypt';
|
||||||
|
|
||||||
|
const User = sequelize.define('user', {
|
||||||
|
email: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false,
|
||||||
|
unique: true,
|
||||||
|
set(value) {
|
||||||
|
this.setDataValue('email', bcrypt.hashSync(value, 10));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
username: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false,
|
||||||
|
unique: true
|
||||||
|
},
|
||||||
|
password: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false,
|
||||||
|
set(value) {
|
||||||
|
this.setDataValue('password', bcrypt.hashSync(value, 10));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
registrationDate: {
|
||||||
|
type: DataTypes.DATE,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: DataTypes.NOW
|
||||||
|
},
|
||||||
|
active: {
|
||||||
|
type: DataTypes.BOOLEAN,
|
||||||
|
defaultValue: false
|
||||||
|
},
|
||||||
|
resetToken: {
|
||||||
|
type: DataTypes.UUID,
|
||||||
|
allowNull: true
|
||||||
|
},
|
||||||
|
hashedId: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: true
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
tableName: 'user',
|
||||||
|
schema: 'community',
|
||||||
|
underscored: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default User;
|
||||||
37
backend/models/community/user_param.js
Normal file
37
backend/models/community/user_param.js
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import { sequelize } from '../../utils/sequelize.js';
|
||||||
|
import { DataTypes } from 'sequelize';
|
||||||
|
import User from './user.js';
|
||||||
|
import UserParamType from '../type/user_param.js';
|
||||||
|
|
||||||
|
const UserParam = sequelize.define('user_param', {
|
||||||
|
userId: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
allowNull: false,
|
||||||
|
references: {
|
||||||
|
model: User,
|
||||||
|
key: 'id'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
paramTypeId: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
allowNull: false,
|
||||||
|
references: {
|
||||||
|
model: UserParamType,
|
||||||
|
key: 'id'
|
||||||
|
},
|
||||||
|
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
tableName: 'user_param',
|
||||||
|
schema: 'community',
|
||||||
|
underscored: true
|
||||||
|
});
|
||||||
|
|
||||||
|
UserParam.belongsTo(User, { foreignKey: 'userId' });
|
||||||
|
UserParam.belongsTo(UserParamType, { foreignKey: 'param_type_id' });
|
||||||
|
|
||||||
|
export default UserParam;
|
||||||
13
backend/models/index.js
Normal file
13
backend/models/index.js
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import User from './community/user.js';
|
||||||
|
import UserParam from './community/user_param.js';
|
||||||
|
import UserParamType from './type/user_param.js';
|
||||||
|
import Login from './logs/login.js';
|
||||||
|
|
||||||
|
const models = {
|
||||||
|
User,
|
||||||
|
UserParam,
|
||||||
|
UserParamType,
|
||||||
|
Login
|
||||||
|
};
|
||||||
|
|
||||||
|
export default models;
|
||||||
19
backend/models/logs/login.js
Normal file
19
backend/models/logs/login.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { sequelize } from '../../utils/sequelize.js';
|
||||||
|
import { DataTypes } from 'sequelize';
|
||||||
|
|
||||||
|
const Login = sequelize.define('login', {
|
||||||
|
userId: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
timestamp: {
|
||||||
|
type: DataTypes.DATE,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: DataTypes.NOW
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
schema: 'logs',
|
||||||
|
tableName: 'login'
|
||||||
|
});
|
||||||
|
|
||||||
|
export default Login;
|
||||||
15
backend/models/type/user_param.js
Normal file
15
backend/models/type/user_param.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { sequelize } from '../../utils/sequelize.js';
|
||||||
|
import { DataTypes } from 'sequelize';
|
||||||
|
|
||||||
|
const UserParamType = sequelize.define('user_param_type', {
|
||||||
|
description: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
tableName: 'user_param',
|
||||||
|
schema: 'type',
|
||||||
|
underscored: true
|
||||||
|
});
|
||||||
|
|
||||||
|
export default UserParamType;
|
||||||
1998
backend/package-lock.json
generated
1998
backend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -12,8 +12,20 @@
|
|||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"amqplib": "^0.10.4",
|
"amqplib": "^0.10.4",
|
||||||
|
"bcrypt": "^5.1.1",
|
||||||
|
"cors": "^2.8.5",
|
||||||
|
"dotenv": "^16.4.5",
|
||||||
"express": "^4.19.2",
|
"express": "^4.19.2",
|
||||||
|
"i18n": "^0.15.1",
|
||||||
|
"mysql2": "^3.10.3",
|
||||||
|
"nodemailer": "^6.9.14",
|
||||||
|
"pg": "^8.12.0",
|
||||||
|
"pg-hstore": "^2.3.4",
|
||||||
|
"sequelize": "^6.37.3",
|
||||||
"socket.io": "^4.7.5",
|
"socket.io": "^4.7.5",
|
||||||
"uuid": "^10.0.0"
|
"uuid": "^10.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"sequelize-cli": "^6.6.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
11
backend/routers/authRouter.js
Normal file
11
backend/routers/authRouter.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { Router } from 'express';
|
||||||
|
import { register, login, forgotPassword, activateAccount } from '../controllers/authController.js';
|
||||||
|
|
||||||
|
const router = Router();
|
||||||
|
|
||||||
|
router.post('/register', register);
|
||||||
|
router.post('/login', login);
|
||||||
|
router.post('/forgot-password', forgotPassword);
|
||||||
|
router.post('/activate', activateAccount);
|
||||||
|
|
||||||
|
export default router;
|
||||||
@@ -2,8 +2,7 @@ import http from 'http';
|
|||||||
import { Server } from 'socket.io';
|
import { Server } from 'socket.io';
|
||||||
import amqp from 'amqplib/callback_api.js';
|
import amqp from 'amqplib/callback_api.js';
|
||||||
import app from './app.js';
|
import app from './app.js';
|
||||||
import path from 'path';
|
import { syncDatabase } from './utils/syncDatabase.js';
|
||||||
import express from 'express';
|
|
||||||
|
|
||||||
const server = http.createServer(app);
|
const server = http.createServer(app);
|
||||||
const io = new Server(server);
|
const io = new Server(server);
|
||||||
@@ -11,14 +10,6 @@ const io = new Server(server);
|
|||||||
const RABBITMQ_URL = 'amqp://localhost';
|
const RABBITMQ_URL = 'amqp://localhost';
|
||||||
const QUEUE = 'chat_messages';
|
const QUEUE = 'chat_messages';
|
||||||
|
|
||||||
const __dirname = path.resolve();
|
|
||||||
const frontendPath = path.join(__dirname, 'path/to/your/frontend/build/folder');
|
|
||||||
app.use(express.static(frontendPath));
|
|
||||||
|
|
||||||
app.get('*', (req, res) => {
|
|
||||||
res.sendFile(path.join(frontendPath, 'index.html'));
|
|
||||||
});
|
|
||||||
|
|
||||||
amqp.connect(RABBITMQ_URL, (err, connection) => {
|
amqp.connect(RABBITMQ_URL, (err, connection) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
@@ -48,8 +39,14 @@ amqp.connect(RABBITMQ_URL, (err, connection) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Sync database before starting the server
|
||||||
|
syncDatabase().then(() => {
|
||||||
server.listen(3001, () => {
|
server.listen(3001, () => {
|
||||||
console.log('Server is running on port 3001');
|
console.log('Server is running on port 3001');
|
||||||
});
|
});
|
||||||
|
}).catch(err => {
|
||||||
|
console.error('Failed to sync database:', err);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
37
backend/services/emailService.js
Normal file
37
backend/services/emailService.js
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import nodemailer from 'nodemailer';
|
||||||
|
import i18n from '../utils/i18n.js';
|
||||||
|
|
||||||
|
const transporter = nodemailer.createTransport({
|
||||||
|
host: process.env.SMTP_HOST,
|
||||||
|
port: process.env.SMTP_PORT,
|
||||||
|
secure: process.env.SMTP_SECURE === 'true',
|
||||||
|
auth: {
|
||||||
|
user: process.env.SMTP_USER,
|
||||||
|
pass: process.env.SMTP_PASSWORD
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const sendPasswordResetEmail = async (email, resetLink, language) => {
|
||||||
|
i18n.setLocale(language);
|
||||||
|
const mailOptions = {
|
||||||
|
from: process.env.SMTP_FROM,
|
||||||
|
to: email,
|
||||||
|
subject: i18n.__('password_reset_subject'),
|
||||||
|
text: i18n.__('password_reset_text', { resetLink })
|
||||||
|
};
|
||||||
|
|
||||||
|
await transporter.sendMail(mailOptions);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const sendAccountActivationEmail = async (email, activationLink, username, resetToken, language) => {
|
||||||
|
i18n.setLocale(language);
|
||||||
|
const mailOptions = {
|
||||||
|
from: process.env.SMTP_FROM,
|
||||||
|
to: email,
|
||||||
|
subject: i18n.__('account_activation_subject'),
|
||||||
|
text: i18n.__('account_activation_text', { activationLink, username, resetToken }),
|
||||||
|
html: i18n.__('account_activation_html', { username, activationLink, resetToken })
|
||||||
|
};
|
||||||
|
|
||||||
|
await transporter.sendMail(mailOptions);
|
||||||
|
};
|
||||||
16
backend/utils/crypto.js
Normal file
16
backend/utils/crypto.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import crypto from 'crypto';
|
||||||
|
|
||||||
|
const algorithm = 'aes-256-ctr';
|
||||||
|
const secretKey = process.env.SECRET_KEY;
|
||||||
|
|
||||||
|
export const encrypt = (text) => {
|
||||||
|
const cipher = crypto.createCipheriv(algorithm, secretKey, Buffer.alloc(16, 0));
|
||||||
|
const encrypted = Buffer.concat([cipher.update(text), cipher.final()]);
|
||||||
|
return encrypted.toString('hex');
|
||||||
|
};
|
||||||
|
|
||||||
|
export const decrypt = (hash) => {
|
||||||
|
const decipher = crypto.createDecipheriv(algorithm, secretKey, Buffer.alloc(16, 0));
|
||||||
|
const decrpyted = Buffer.concat([decipher.update(Buffer.from(hash, 'hex')), decipher.final()]);
|
||||||
|
return decrpyted.toString();
|
||||||
|
};
|
||||||
22
backend/utils/i18n.js
Normal file
22
backend/utils/i18n.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import i18n from 'i18n';
|
||||||
|
import path from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
|
i18n.configure({
|
||||||
|
locales: ['en', 'de'],
|
||||||
|
directory: path.join(__dirname, '../locales'),
|
||||||
|
defaultLocale: 'de',
|
||||||
|
register: global,
|
||||||
|
autoReload: true,
|
||||||
|
syncFiles: true,
|
||||||
|
cookie: 'lang',
|
||||||
|
api: {
|
||||||
|
'__': 'translate',
|
||||||
|
'__n': 'translateN'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default i18n;
|
||||||
10
backend/utils/initializeTypes.js
Normal file
10
backend/utils/initializeTypes.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import UserParamType from '../models/type/user_param.js';
|
||||||
|
|
||||||
|
const initializeTypes = async () => {
|
||||||
|
await UserParamType.findOrCreate({
|
||||||
|
where: { description: 'language' },
|
||||||
|
defaults: { description: 'language' }
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default initializeTypes;
|
||||||
7
backend/utils/locales/de.json
Normal file
7
backend/utils/locales/de.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"Password Reset": "Passwort zurücksetzen",
|
||||||
|
"Please click the following link to reset your password: {{resetLink}}": "Bitte klicken Sie auf den folgenden Link, um Ihr Passwort zurückzusetzen: {{resetLink}}",
|
||||||
|
"Account Activation": "Kontoaktivierung",
|
||||||
|
"Please click the following link to activate your account: {{activationLink}}": "Bitte klicken Sie auf den folgenden Link, um Ihr Konto zu aktivieren: {{activationLink}}",
|
||||||
|
"welcome": "Willkommen!"
|
||||||
|
}
|
||||||
7
backend/utils/locales/en.json
Normal file
7
backend/utils/locales/en.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"Password Reset": "Password Reset",
|
||||||
|
"Please click the following link to reset your password: {{resetLink}}": "Please click the following link to reset your password: {{resetLink}}",
|
||||||
|
"Account Activation": "Account Activation",
|
||||||
|
"Please click the following link to activate your account: {{activationLink}}": "Please click the following link to activate your account: {{activationLink}}",
|
||||||
|
"welcome": "Welcome!"
|
||||||
|
}
|
||||||
26
backend/utils/sequelize.js
Normal file
26
backend/utils/sequelize.js
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { Sequelize } from 'sequelize';
|
||||||
|
import dotenv from 'dotenv';
|
||||||
|
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
|
const sequelize = new Sequelize(process.env.DB_NAME, process.env.DB_USER, process.env.DB_PASSWORD, {
|
||||||
|
host: process.env.DB_HOST,
|
||||||
|
dialect: 'postgres',
|
||||||
|
define: {
|
||||||
|
timestamps: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const createSchemas = async () => {
|
||||||
|
await sequelize.query('CREATE SCHEMA IF NOT EXISTS community');
|
||||||
|
await sequelize.query('CREATE SCHEMA IF NOT EXISTS logs');
|
||||||
|
await sequelize.query('CREATE SCHEMA IF NOT EXISTS type');
|
||||||
|
};
|
||||||
|
|
||||||
|
const initializeDatabase = async () => {
|
||||||
|
await createSchemas();
|
||||||
|
const models = await import('../models/index.js');
|
||||||
|
await sequelize.sync({ alter: true });
|
||||||
|
};
|
||||||
|
|
||||||
|
export { sequelize, initializeDatabase };
|
||||||
14
backend/utils/syncDatabase.js
Normal file
14
backend/utils/syncDatabase.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { initializeDatabase } from './sequelize.js';
|
||||||
|
import initializeTypes from './initializeTypes.js';
|
||||||
|
|
||||||
|
const syncDatabase = async () => {
|
||||||
|
try {
|
||||||
|
await initializeDatabase();
|
||||||
|
await initializeTypes();
|
||||||
|
console.log('All models were synchronized successfully.');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Unable to synchronize the database:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export { syncDatabase };
|
||||||
22
frontend/package-lock.json
generated
22
frontend/package-lock.json
generated
@@ -9,6 +9,7 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^1.7.2",
|
"axios": "^1.7.2",
|
||||||
|
"dotenv": "^16.4.5",
|
||||||
"vue": "~3.4.31",
|
"vue": "~3.4.31",
|
||||||
"vue-i18n": "^10.0.0-beta.2",
|
"vue-i18n": "^10.0.0-beta.2",
|
||||||
"vue-router": "^4.0.13",
|
"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": {
|
"node_modules/@vue/cli-shared-utils": {
|
||||||
"version": "5.0.8",
|
"version": "5.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/cli-shared-utils/-/cli-shared-utils-5.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/cli-shared-utils/-/cli-shared-utils-5.0.8.tgz",
|
||||||
@@ -2835,12 +2845,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/dotenv": {
|
"node_modules/dotenv": {
|
||||||
"version": "10.0.0",
|
"version": "16.4.5",
|
||||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz",
|
||||||
"integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==",
|
"integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==",
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10"
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://dotenvx.com"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/dotenv-expand": {
|
"node_modules/dotenv-expand": {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^1.7.2",
|
"axios": "^1.7.2",
|
||||||
|
"dotenv": "^16.4.5",
|
||||||
"vue": "~3.4.31",
|
"vue": "~3.4.31",
|
||||||
"vue-i18n": "^10.0.0-beta.2",
|
"vue-i18n": "^10.0.0-beta.2",
|
||||||
"vue-router": "^4.0.13",
|
"vue-router": "^4.0.13",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="app">
|
<div id="app">
|
||||||
<AppHeader />
|
<AppHeader />
|
||||||
<AppNavigation v-if="isLoggedIn" />
|
<AppNavigation v-if="isLoggedIn && user.active" />
|
||||||
<AppContent />
|
<AppContent />
|
||||||
<AppFooter />
|
<AppFooter />
|
||||||
</div>
|
</div>
|
||||||
@@ -20,14 +20,18 @@ export default {
|
|||||||
document.title = 'yourPart';
|
document.title = 'yourPart';
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters(['isLoggedIn'])
|
...mapGetters(['isLoggedIn', 'user'])
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
AppHeader,
|
AppHeader,
|
||||||
AppNavigation,
|
AppNavigation,
|
||||||
AppContent,
|
AppContent,
|
||||||
AppFooter
|
AppFooter
|
||||||
}
|
},
|
||||||
|
created() {
|
||||||
|
this.$store.dispatch('loadLoginState');
|
||||||
|
this.$i18n.locale = this.$store.getters.language;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -37,5 +41,4 @@ export default {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -47,3 +47,8 @@ button:hover {
|
|||||||
color: #0000ff;
|
color: #0000ff;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.link {
|
||||||
|
color: #F9A22C;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
@@ -43,12 +43,13 @@ export default {
|
|||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import '../assets/styles.scss';
|
@import '../assets/styles.scss';
|
||||||
|
|
||||||
nav {
|
nav > ul{
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
background-color: #343a40;
|
background-color: #343a40;
|
||||||
color: white;
|
color: white;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
|
flex-direction: row;
|
||||||
}
|
}
|
||||||
ul {
|
ul {
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="visible" :class="['dialog-overlay', { 'non-modal': !modal }]" @click.self="handleOverlayClick">
|
<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">
|
<div class="dialog-header">
|
||||||
<span v-if="icon" class="dialog-icon">
|
<span v-if="icon" class="dialog-icon">
|
||||||
<img :src="'/images/icons/' + icon" alt="Icon" />
|
<img :src="icon" alt="Icon" />
|
||||||
</span>
|
</span>
|
||||||
<span class="dialog-title">{{ isTitleTranslated ? $t(title) : title }}</span>
|
<span class="dialog-title">{{ isTitleTranslated ? $t(title) : title }}</span>
|
||||||
<span v-if="!modal" class="dialog-minimize" @click="minimize">_</span>
|
<span v-if="!modal" class="dialog-minimize" @click="minimize">_</span>
|
||||||
@@ -13,9 +14,8 @@
|
|||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
<div class="dialog-footer">
|
<div class="dialog-footer">
|
||||||
<button v-for="button in buttons" :key="button.text" @click="buttonClick(button.text)"
|
<button v-for="button in buttons" :key="button.text" @click="buttonClick(button.action)" class="dialog-button">
|
||||||
class="dialog-button">
|
{{ isTitleTranslated ? $t(button.text) : button.text }}
|
||||||
{{ button.text }}
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -40,7 +40,7 @@ export default {
|
|||||||
},
|
},
|
||||||
buttons: {
|
buttons: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => [{ text: 'Ok' }]
|
default: () => [{ text: 'Ok', action: 'close' }]
|
||||||
},
|
},
|
||||||
modal: {
|
modal: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
@@ -87,7 +87,7 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
open() {
|
open() {
|
||||||
this.visible = true;
|
this.visible = true;
|
||||||
if (!this.modal) {
|
if (this.modal === false) {
|
||||||
this.$store.dispatch('dialogs/addOpenDialog', {
|
this.$store.dispatch('dialogs/addOpenDialog', {
|
||||||
status: 'open',
|
status: 'open',
|
||||||
dialog: this
|
dialog: this
|
||||||
@@ -98,9 +98,11 @@ export default {
|
|||||||
this.visible = false;
|
this.visible = false;
|
||||||
this.$store.dispatch('dialogs/removeOpenDialog', this.name);
|
this.$store.dispatch('dialogs/removeOpenDialog', this.name);
|
||||||
},
|
},
|
||||||
buttonClick(buttonText) {
|
buttonClick(action) {
|
||||||
this.$emit(buttonText.toLowerCase());
|
this.$emit(action);
|
||||||
this.close();
|
if (action === 'close') {
|
||||||
|
this.close(); // Close dialog after button click if action is close
|
||||||
|
}
|
||||||
},
|
},
|
||||||
handleOverlayClick() {
|
handleOverlayClick() {
|
||||||
if (!this.modal) {
|
if (!this.modal) {
|
||||||
@@ -186,7 +188,7 @@ export default {
|
|||||||
|
|
||||||
.dialog-body {
|
.dialog-body {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
padding: 10px;
|
padding: 20px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,4 +199,18 @@ export default {
|
|||||||
border-top: 1px solid #ddd;
|
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>
|
</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>
|
<template>
|
||||||
<DialogWidget ref="dialog" title="randomchat.title" icon="dice24.png" :show-close="true" :buttons="buttons"
|
<DialogWidget ref="dialog" title="randomchat.title" icon="dice24.png" :show-close=true :buttons="buttons"
|
||||||
:modal="false" :isTitleTranslated="true" @close="closeDialog">
|
:modal=false :isTitleTranslated=true @close="closeDialog" name="RandomChat">
|
||||||
<div v-if="chatIsRunning" class="randomchat">
|
<div v-if="chatIsRunning" class="randomchat">
|
||||||
<div class="headline">
|
<div class="headline">
|
||||||
{{ $t("randomchat.agerange") }}
|
{{ $t("randomchat.agerange") }}
|
||||||
|
|||||||
@@ -2,11 +2,11 @@
|
|||||||
<DialogWidget
|
<DialogWidget
|
||||||
ref="dialog"
|
ref="dialog"
|
||||||
title="dataPrivacy.title"
|
title="dataPrivacy.title"
|
||||||
isTitleTranslated=true
|
:isTitleTranslated=true
|
||||||
icon="privacy24.png"
|
icon="privacy24.png"
|
||||||
:show-close="true"
|
:show-close=true
|
||||||
:buttons="[{ text: 'Ok' }]"
|
:buttons="[{ text: 'Ok' }]"
|
||||||
:modal="false"
|
:modal=false
|
||||||
@close="closeDialog"
|
@close="closeDialog"
|
||||||
@ok="handleOk"
|
@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
|
<DialogWidget
|
||||||
ref="dialog"
|
ref="dialog"
|
||||||
title="imprint.title"
|
title="imprint.title"
|
||||||
isTitleTranslated=true
|
:isTitleTranslated=true
|
||||||
icon="imprint24.png"
|
icon="imprint24.png"
|
||||||
:show-close="true"
|
:show-close="true"
|
||||||
:buttons="[{ text: 'Ok' }]"
|
:buttons="[{ text: 'Ok' }]"
|
||||||
:modal="false"
|
:modal=false
|
||||||
@close="closeDialog"
|
@close="closeDialog"
|
||||||
@ok="handleOk"
|
@ok="handleOk"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,16 +1,23 @@
|
|||||||
import { createI18n } from 'vue-i18n';
|
import { createI18n } from 'vue-i18n';
|
||||||
|
import store from '../store/index.js';
|
||||||
|
|
||||||
import enGeneral from './locales/en/general.json';
|
import enGeneral from './locales/en/general.json';
|
||||||
import enHeader from './locales/en/header.json';
|
import enHeader from './locales/en/header.json';
|
||||||
import enNavigation from './locales/en/navigation.json';
|
import enNavigation from './locales/en/navigation.json';
|
||||||
import enHome from './locales/en/home.json';
|
import enHome from './locales/en/home.json';
|
||||||
import enChat from './locales/en/chat.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 deGeneral from './locales/de/general.json';
|
||||||
import deHeader from './locales/de/header.json';
|
import deHeader from './locales/de/header.json';
|
||||||
import deNavigation from './locales/de/navigation.json';
|
import deNavigation from './locales/de/navigation.json';
|
||||||
import deHome from './locales/de/home.json';
|
import deHome from './locales/de/home.json';
|
||||||
import deChat from './locales/de/chat.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 = {
|
const messages = {
|
||||||
en: {
|
en: {
|
||||||
@@ -19,6 +26,9 @@ const messages = {
|
|||||||
...enNavigation,
|
...enNavigation,
|
||||||
...enHome,
|
...enHome,
|
||||||
...enChat,
|
...enChat,
|
||||||
|
...enRegister,
|
||||||
|
...enError,
|
||||||
|
...enActivate,
|
||||||
},
|
},
|
||||||
de: {
|
de: {
|
||||||
...deGeneral,
|
...deGeneral,
|
||||||
@@ -26,11 +36,14 @@ const messages = {
|
|||||||
...deNavigation,
|
...deNavigation,
|
||||||
...deHome,
|
...deHome,
|
||||||
...deChat,
|
...deChat,
|
||||||
|
...deRegister,
|
||||||
|
...deError,
|
||||||
|
...deActivate,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const i18n = createI18n({
|
const i18n = createI18n({
|
||||||
locale: 'de',
|
locale: store.state.language,
|
||||||
fallbackLocale: 'de',
|
fallbackLocale: 'de',
|
||||||
messages
|
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",
|
"name": "Login-Name",
|
||||||
"namedescription": "Gib hier Deinen Benutzernamen ein",
|
"namedescription": "Gib hier Deinen Benutzernamen ein",
|
||||||
"password": "Paßwort",
|
"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": {
|
"home": {
|
||||||
"nologin": {
|
"nologin": {
|
||||||
"welcome": "Welcome at yourPart",
|
"welcome": "Welcome at yourPart",
|
||||||
"description": "<platzhalter>",
|
"description": "---platzhalter---",
|
||||||
"randomchat": "Random chat",
|
"randomchat": "Random chat",
|
||||||
"startrandomchat": "Start 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 './assets/styles.scss';
|
||||||
import i18n from './i18n';
|
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);
|
const app = createApp(App);
|
||||||
|
|
||||||
app.use(store);
|
app.use(store);
|
||||||
|
|||||||
@@ -1,11 +1,17 @@
|
|||||||
import { createRouter, createWebHistory } from 'vue-router';
|
import { createRouter, createWebHistory } from 'vue-router';
|
||||||
import HomeView from '../views/HomeView.vue';
|
import HomeView from '../views/HomeView.vue';
|
||||||
|
import ActivateView from '../views/auth/ActivateView.vue';
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
name: 'Home',
|
name: 'Home',
|
||||||
component: HomeView
|
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 (to.matched.some(record => record.meta.requiresAuth)) {
|
||||||
if (!store.getters.isLoggedIn) {
|
if (!store.getters.isLoggedIn) {
|
||||||
next('/');
|
next('/');
|
||||||
} else {
|
} else if (!store.user.active) {
|
||||||
next();
|
next();
|
||||||
|
} else {
|
||||||
|
next('/activate');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
next();
|
next();
|
||||||
|
|||||||
@@ -4,16 +4,17 @@ import dialogs from './modules/dialogs';
|
|||||||
const store = createStore({
|
const store = createStore({
|
||||||
state: {
|
state: {
|
||||||
isLoggedIn: false,
|
isLoggedIn: false,
|
||||||
user: null
|
user: null,
|
||||||
|
language: navigator.language.startsWith('de') ? 'de' : 'en',
|
||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
login(state, user) {
|
dologin(state, user) {
|
||||||
state.isLoggedIn = true;
|
state.isLoggedIn = true;
|
||||||
state.user = user;
|
state.user = user;
|
||||||
localStorage.setItem('isLoggedIn', 'true');
|
localStorage.setItem('isLoggedIn', 'true');
|
||||||
localStorage.setItem('user', JSON.stringify(user));
|
localStorage.setItem('user', JSON.stringify(user));
|
||||||
},
|
},
|
||||||
logout(state) {
|
dologout(state) {
|
||||||
state.isLoggedIn = false;
|
state.isLoggedIn = false;
|
||||||
state.user = null;
|
state.user = null;
|
||||||
localStorage.removeItem('isLoggedIn');
|
localStorage.removeItem('isLoggedIn');
|
||||||
@@ -30,22 +31,29 @@ const store = createStore({
|
|||||||
const user = userData;
|
const user = userData;
|
||||||
state.isLoggedIn = isLoggedIn;
|
state.isLoggedIn = isLoggedIn;
|
||||||
state.user = user;
|
state.user = user;
|
||||||
|
},
|
||||||
|
setLanguage(state, language) {
|
||||||
|
state.language = language;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
login({ commit }, user) {
|
login({ commit }, user) {
|
||||||
commit('login', user);
|
commit('dologin', user);
|
||||||
},
|
},
|
||||||
logout({ commit }) {
|
logout({ commit }) {
|
||||||
commit('logout');
|
commit('dologout');
|
||||||
},
|
},
|
||||||
loadLoginState({ commit }) {
|
loadLoginState({ commit }) {
|
||||||
commit('loadLoginState');
|
commit('loadLoginState');
|
||||||
}
|
},
|
||||||
|
setLanguage({ commit }, language) {
|
||||||
|
commit('setLanguage', language);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
getters: {
|
getters: {
|
||||||
isLoggedIn: state => state.isLoggedIn,
|
isLoggedIn: state => state.isLoggedIn,
|
||||||
user: state => state.user
|
user: state => state.user,
|
||||||
|
language: state => state.language,
|
||||||
},
|
},
|
||||||
modules: {
|
modules: {
|
||||||
dialogs,
|
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>
|
<div>
|
||||||
<h1>Welcome to Home (Logged In)</h1>
|
<h1>Welcome to Home (Logged In)</h1>
|
||||||
<p>Here are your exclusive features.</p>
|
<p>Here are your exclusive features.</p>
|
||||||
|
<button @click="handleLogout">Logout</button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { mapActions } from 'vuex';
|
||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'HomeLoggedInView',
|
name: 'HomeLoggedInView',
|
||||||
|
methods: {
|
||||||
|
...mapActions(['logout']),
|
||||||
|
handleLogout() {
|
||||||
|
this.logout();
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -12,18 +12,19 @@
|
|||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
<input data-object-name="user-name" size="20" type="text"
|
<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>
|
||||||
<div>
|
<div>
|
||||||
<input data-object-name="password" size="20" type="password"
|
<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>
|
||||||
<div>
|
<div>
|
||||||
<label id="o1p5irxv" name="o1p5irxv" class="Wt-valid" title=""><input id="ino1p5irxv"
|
<label id="o1p5irxv" name="o1p5irxv" class="Wt-valid" title=""><input id="ino1p5irxv"
|
||||||
data-object-name="remember-me" name="ino1p5irxv" type="checkbox"
|
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
|
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
|
id="to1p5irxv" name="to1p5irxv" style="white-space:normal;">Eingeloggt bleiben</span></label>
|
||||||
(ACHTUNG!!! Dafür wird ein Cookie gesetzt!)</span></label>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="Wt-buttons">
|
<div class="Wt-buttons">
|
||||||
@@ -32,30 +33,41 @@
|
|||||||
class="Wt-btn with-label">Einloggen</button>
|
class="Wt-btn with-label">Einloggen</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="Wt-buttons">
|
<div class="Wt-buttons">
|
||||||
<span id="o1p5iry0" data-object-name="lost-password"
|
<span id="o1p5iry0" data-object-name="lost-password" @click="openPasswordResetDialog"
|
||||||
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
|
class="link">{{
|
||||||
habe mein Paßwort vergessen</span> | <span id="o1p5iry1" data-object-name="register"
|
$t('home.nologin.login.lostpassword') }}</span> | <span id="o1p5iry1"
|
||||||
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
|
@click="openRegisterDialog" class="link">{{ $t('home.nologin.login.register') }}</span>
|
||||||
möchte mich neu anmelden</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mascot"><img src="/images/mascot/mascot_female.png" /></div>
|
<div class="mascot"><img src="/images/mascot/mascot_female.png" /></div>
|
||||||
<RandomChatDialog ref="randomChatDialog" />
|
<RandomChatDialog ref="randomChatDialog" />
|
||||||
|
<RegisterDialog ref="registerDialog" />
|
||||||
|
<PasswordResetDialog ref="passwordResetDialog" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import RandomChatDialog from '@/dialogues/chat/RandomChatDialog.vue';
|
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',
|
name: 'HomeNoLoginView',
|
||||||
components: {
|
components: {
|
||||||
RandomChatDialog,
|
RandomChatDialog,
|
||||||
|
RegisterDialog,
|
||||||
|
PasswordResetDialog,
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
openRandomChat() {
|
openRandomChat() {
|
||||||
this.$refs.randomChatDialog.open();
|
this.$refs.randomChatDialog.open();
|
||||||
|
},
|
||||||
|
openRegisterDialog() {
|
||||||
|
this.$refs.registerDialog.open();
|
||||||
|
},
|
||||||
|
openPasswordResetDialog() {
|
||||||
|
this.$refs.passwordResetDialog.open();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -70,21 +82,25 @@ export default {
|
|||||||
gap: 2em;
|
gap: 2em;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.home-structure>div {
|
.home-structure>div {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mascot {
|
.mascot {
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background-color: #fdf1db;
|
background-color: #fdf1db;
|
||||||
}
|
}
|
||||||
|
|
||||||
.actions {
|
.actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 2em;
|
gap: 2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.actions>div {
|
.actions>div {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
background-color: #fdf1db;
|
background-color: #fdf1db;
|
||||||
@@ -94,6 +110,7 @@ export default {
|
|||||||
color: #7E471B;
|
color: #7E471B;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.actions>div>h2 {
|
.actions>div>h2 {
|
||||||
color: #F9A22C;
|
color: #F9A22C;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user