first lines
This commit is contained in:
commit
91999b0699
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
node_modules
|
||||
package-lock.json
|
||||
db/database.db
|
||||
.env
|
0
db/.gitkeep
Normal file
0
db/.gitkeep
Normal file
236
index.js
Normal file
236
index.js
Normal file
@ -0,0 +1,236 @@
|
||||
import express from 'express';
|
||||
import sqlite3 from 'sqlite3';
|
||||
import { open } from 'sqlite';
|
||||
import bcrypt from 'bcrypt';
|
||||
import nodemailer from 'nodemailer';
|
||||
import dotenv from 'dotenv';
|
||||
import fs, { stat } from 'fs';
|
||||
|
||||
dotenv.config();
|
||||
|
||||
const transporter = nodemailer.createTransport({
|
||||
host: 'email.thepenguinontheweb.tech',
|
||||
port: 587,
|
||||
secure: false, // false pour STARTTLS
|
||||
auth: {
|
||||
user: process.env.EMAIL_USER,
|
||||
pass: process.env.EMAIL_PASS
|
||||
}
|
||||
});
|
||||
|
||||
function sendMail(to, subject, html) {
|
||||
const mailOptions = {
|
||||
from: process.env.EMAIL_USER,
|
||||
to: to,
|
||||
subject: subject,
|
||||
html: html
|
||||
};
|
||||
|
||||
return transporter.sendMail(mailOptions)
|
||||
.then(() => console.log('Email sent successfully'))
|
||||
.catch(error => console.error('Error sending email:', error));
|
||||
}
|
||||
|
||||
const db = await open({
|
||||
filename: './db/database.db',
|
||||
driver: sqlite3.Database
|
||||
});
|
||||
|
||||
function initializeDatabase() {
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username TEXT NOT NULL UNIQUE,
|
||||
email TEXT NOT NULL UNIQUE,
|
||||
password TEXT NOT NULL,
|
||||
historyToDefault INTEGER DEFAULT 0
|
||||
);
|
||||
`);
|
||||
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS verify (
|
||||
id INTEGER PRIMARY KEY,
|
||||
token TEXT NOT NULL UNIQUE,
|
||||
username TEXT NOT NULL UNIQUE,
|
||||
email TEXT NOT NULL UNIQUE,
|
||||
password TEXT NOT NULL,
|
||||
createdAt DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
`);
|
||||
}
|
||||
|
||||
initializeDatabase();
|
||||
|
||||
const app = express();
|
||||
const port = 20909;
|
||||
|
||||
app.use(express.json());
|
||||
app.use(express.static('public'));
|
||||
|
||||
app.get('/login', (req, res) => {
|
||||
res.sendFile('index.html', { root: 'public' });
|
||||
}
|
||||
);
|
||||
app.get('/register', (req, res) => {
|
||||
res.sendFile('register.html', { root: 'public' });
|
||||
}
|
||||
);
|
||||
app.get('/', (req, res) => {
|
||||
// redirect to login page
|
||||
res.redirect('/login');
|
||||
}
|
||||
);
|
||||
|
||||
app.post('/api/login', (req, res) => {
|
||||
const { username, password } = req.body;
|
||||
|
||||
if (!username || !password) {
|
||||
return res.status(400).json({ message: 'Username and password are required' });
|
||||
}
|
||||
|
||||
db.get('SELECT * FROM users WHERE username = ? OR email = ?', [username, username])
|
||||
.then(user => {
|
||||
if (!user) {
|
||||
return res.status(401).json({ message: 'Invalid username or password' });
|
||||
}
|
||||
|
||||
// Check password (replace with real password check logic)
|
||||
const isPasswordValid = bcrypt.compareSync(password, user.password);
|
||||
if (!isPasswordValid) {
|
||||
return res.status(401).json({ message: 'Invalid username or password' });
|
||||
}
|
||||
|
||||
res.status(200).json({ message: 'Login successful' });
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('Database error:', err);
|
||||
res.status(500).json({ message: 'Internal server error' });
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
app.post('/api/register', (req, res) => {
|
||||
const { username, email, password } = req.body;
|
||||
|
||||
if (!username || !email || !password) {
|
||||
return res.status(400).json({ message: 'Username, email, and password are required' });
|
||||
}
|
||||
|
||||
const hashedPassword = bcrypt.hashSync(password, 10);
|
||||
|
||||
let isAlreadyRegistered = false;
|
||||
db.get('SELECT * FROM users WHERE username = ? OR email = ?', [username, email])
|
||||
.then(user => {
|
||||
if (user) {
|
||||
isAlreadyRegistered = true;
|
||||
return res.status(409).json({ message: 'Username or email already exists' });
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('Database error:', err);
|
||||
return res.status(500).json({ message: 'Internal server error' });
|
||||
});
|
||||
|
||||
if (isAlreadyRegistered) {
|
||||
return;
|
||||
}
|
||||
|
||||
db.get('SELECT * FROM verify WHERE username = ? OR email = ?', [username, email])
|
||||
.then(verify => {
|
||||
if (verify) {
|
||||
// Verify if the last verification token is still valid
|
||||
if (Date.now() - new Date(verify.createdAt).getTime() < 24 * 60 * 60 * 1000) {
|
||||
isAlreadyRegistered = true;
|
||||
return res.status(409).json({ message: 'Verification already sent, please check your email' });
|
||||
}
|
||||
else {
|
||||
// If the token is expired, delete it
|
||||
db.run('DELETE FROM verify WHERE id = ?', [verify.id])
|
||||
.catch(err => {
|
||||
console.error('Database error:', err);
|
||||
return res.status(500).json({ message: 'Internal server error' });
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('Database error:', err);
|
||||
return res.status(500).json({ message: 'Internal server error' });
|
||||
});
|
||||
|
||||
if (isAlreadyRegistered) {
|
||||
return;
|
||||
}
|
||||
|
||||
const verificationToken = Math.random().toString(36).substring(2, 15);
|
||||
db.run('INSERT INTO verify (token, username, email, password) VALUES (?, ?, ?, ?)', [verificationToken, username, email, hashedPassword])
|
||||
.then(() => {
|
||||
// read the email template
|
||||
const emailTemplate = fs.readFileSync('mailFile/mail.html', 'utf8');
|
||||
|
||||
// replace placeholders in the email template
|
||||
const emailContent = emailTemplate.replace('{{verification_link}}', `${process.env.URL}/verify?token=${verificationToken}`);
|
||||
|
||||
sendMail(email, 'Welcome to Our Service', emailContent);
|
||||
res.status(201).json({ message: 'email send' });
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('Database error:', err);
|
||||
return res.status(500).json({ message: 'Internal server error' });
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
app.get('/verify', (req, res) => {
|
||||
const { token } = req.query;
|
||||
|
||||
if (!token) {
|
||||
return res.status(400).json({ message: 'Token is required' });
|
||||
}
|
||||
|
||||
res.sendFile('verify.html', { root: 'public' });
|
||||
}
|
||||
);
|
||||
|
||||
app.post('/api/verify', (req, res) => {
|
||||
const { token } = req.body;
|
||||
|
||||
if (!token) {
|
||||
return res.status(400).json({ message: 'Token is required' });
|
||||
}
|
||||
|
||||
db.get('SELECT * FROM verify WHERE token = ?', [token])
|
||||
.then(verify => {
|
||||
if (!verify) {
|
||||
return res.status(404).json({ message: 'Invalid or expired token' });
|
||||
}
|
||||
|
||||
// Insert user into users table
|
||||
db.run('INSERT INTO users (username, email, password) VALUES (?, ?, ?)', [verify.username, verify.email, verify.password])
|
||||
.then(() => {
|
||||
// Delete the verification record
|
||||
db.run('DELETE FROM verify WHERE id = ?', [verify.id])
|
||||
.then(() => {
|
||||
res.status(200).json({ message: 'Email verified successfully' });
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('Database error:', err);
|
||||
res.status(500).json({ message: 'Internal server error' });
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('Database error:', err);
|
||||
res.status(500).json({ message: 'Internal server error' });
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('Database error:', err);
|
||||
res.status(500).json({ message: 'Internal server error' });
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
app.listen(port, "127.0.0.1", () => {
|
||||
console.log(`Server is running on localhost:${port}`);
|
||||
}
|
||||
);
|
25
mailFile/mail.html
Normal file
25
mailFile/mail.html
Normal file
@ -0,0 +1,25 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Email Address Verification</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; background: #f6f6f6; margin: 0; padding: 0; }
|
||||
.container { background: #fff; max-width: 600px; margin: 40px auto; padding: 30px; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.05);}
|
||||
.btn { display: inline-block; padding: 12px 24px; background: #007bff; color: #fff; text-decoration: none; border-radius: 4px; margin-top: 20px;}
|
||||
.footer { margin-top: 30px; color: #888; font-size: 12px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h2>Welcome to PengLogin</h2>
|
||||
<p>Hello,</p>
|
||||
<p>Thank you for signing up. To complete your registration, please verify your email address by clicking the button below:</p>
|
||||
<a href="{{verification_link}}" class="btn">Verify my email address</a>
|
||||
<p>If you did not request this, please ignore this message.</p>
|
||||
<div class="footer">
|
||||
© 2025 PengLogin. All rights reserved.
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
22
package.json
Normal file
22
package.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "penglogin",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "ssh://git@git.thepenguinontheweb.tech:5900/adminflsu/PengLogin.git"
|
||||
},
|
||||
"license": "ISC",
|
||||
"author": "",
|
||||
"type": "module",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "nodemon index.js",
|
||||
"run": "node index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"dotenv": "^16.5.0",
|
||||
"nodemailer": "^7.0.3",
|
||||
"nodemon": "^3.1.10"
|
||||
}
|
||||
}
|
2
public/footer-snippet.html
Normal file
2
public/footer-snippet.html
Normal file
@ -0,0 +1,2 @@
|
||||
<p>© 2025 The land of the penguin</p>
|
||||
<p>Contact us at: <a href="mailto:florian@thepenguninontheweb.tech">florian@thepenguinontheweb.tech</a></p>
|
13
public/header-snippet.html
Normal file
13
public/header-snippet.html
Normal file
@ -0,0 +1,13 @@
|
||||
<a href="https://thepenguinontheweb.tech/index.html" class="header-link">
|
||||
The land of the penguin
|
||||
</a>
|
||||
|
||||
<div class="back-button"">
|
||||
<svg width=" 24" height="8" viewBox="0 0 16 8" fill="none" class="arrow-icon">
|
||||
<path d="M15 4H4V1" stroke="#C99CCF" />
|
||||
<path d="M14.5 4H3.5H0" stroke="#C99CCF" />
|
||||
<path
|
||||
d="M15.8536 4.35355C16.0488 4.15829 16.0488 3.84171 15.8536 3.64645L12.6716 0.464466C12.4763 0.269204 12.1597 0.269204 11.9645 0.464466C11.7692 0.659728 11.7692 0.976311 11.9645 1.17157L14.7929 4L11.9645 6.82843C11.7692 7.02369 11.7692 7.34027 11.9645 7.53553C12.1597 7.7308 12.4763 7.7308 12.6716 7.53553L15.8536 4.35355ZM15 4.5L15.5 4.5L15.5 3.5L15 3.5L15 4.5Z"
|
||||
fill="#C99CCF" />
|
||||
</svg>
|
||||
</div>
|
BIN
public/imgs/favicon.ico
Normal file
BIN
public/imgs/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.5 MiB |
40
public/index.html
Normal file
40
public/index.html
Normal file
@ -0,0 +1,40 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>PengLogin</title>
|
||||
<link rel="stylesheet" href="styles/common.css">
|
||||
<link rel="stylesheet" href="styles/index.css">
|
||||
<link rel="icon" href="imgs/favicon.ico" type="image/x-icon">
|
||||
<script src="scripts/common.js" defer></script>
|
||||
<script src="scripts/index.js" defer></script>
|
||||
</head>
|
||||
|
||||
<body id="body" class="body">
|
||||
<header id="header" class="blur">
|
||||
|
||||
</header>
|
||||
<main>
|
||||
<div class="card blur">
|
||||
<h2>LOGIN</h2>
|
||||
<form id="login-form" class="form">
|
||||
<label for="username or email">Username or email:</label>
|
||||
<input type="text" id="username" name="username" required>
|
||||
|
||||
<label for="password">Password:</label>
|
||||
<input type="password" id="password" name="password" required>
|
||||
|
||||
<button type="submit">Login</button>
|
||||
</form>
|
||||
|
||||
<a href="/register">Create account.</a>
|
||||
</div>
|
||||
</main>
|
||||
<footer id="footer" class="blur">
|
||||
|
||||
</footer>
|
||||
</body>
|
||||
|
||||
</html>
|
46
public/register.html
Normal file
46
public/register.html
Normal file
@ -0,0 +1,46 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>PengRegister</title>
|
||||
<link rel="stylesheet" href="styles/common.css">
|
||||
<link rel="stylesheet" href="styles/register.css">
|
||||
<link rel="icon" href="imgs/favicon.ico" type="image/x-icon">
|
||||
<script src="scripts/common.js" defer></script>
|
||||
<script src="scripts/register.js" defer></script>
|
||||
</head>
|
||||
|
||||
<body id="body" class="body">
|
||||
<header id="header" class="blur">
|
||||
|
||||
</header>
|
||||
<main>
|
||||
<div class="card blur">
|
||||
<h2>Register</h2>
|
||||
<form id="register-form" class="form">
|
||||
<label for="username">Username:</label>
|
||||
<input type="text" id="username" name="username" required>
|
||||
|
||||
<label for="email">Email:</label>
|
||||
<input type="email" id="email" name="email" required>
|
||||
|
||||
<label for="password">Password:</label>
|
||||
<input type="password" id="password" name="password" required>
|
||||
|
||||
<label for="confirm-password">Confirm Password:</label>
|
||||
<input type="password" id="confirm-password" name="confirmPassword" required>
|
||||
|
||||
<button type="submit">Register</button>
|
||||
</form>
|
||||
|
||||
<a href="/login">Login Page.</a>
|
||||
</div>
|
||||
</main>
|
||||
<footer id="footer" class="blur">
|
||||
|
||||
</footer>
|
||||
</body>
|
||||
|
||||
</html>
|
20
public/scripts/common.js
Normal file
20
public/scripts/common.js
Normal file
@ -0,0 +1,20 @@
|
||||
const headerElement = document.querySelector('header');
|
||||
const footerElement = document.querySelector('footer');
|
||||
|
||||
async function loadHeaderFooter() {
|
||||
try {
|
||||
const headerResponse = await fetch('/header-snippet.html');
|
||||
if (!headerResponse.ok) throw new Error('Failed to load header');
|
||||
const headerHTML = await headerResponse.text();
|
||||
headerElement.innerHTML = headerHTML;
|
||||
|
||||
const footerResponse = await fetch('/footer-snippet.html');
|
||||
if (!footerResponse.ok) throw new Error('Failed to load footer');
|
||||
const footerHTML = await footerResponse.text();
|
||||
footerElement.innerHTML = footerHTML;
|
||||
} catch (error) {
|
||||
console.error('Error loading header/footer:', error);
|
||||
}
|
||||
}
|
||||
|
||||
loadHeaderFooter();
|
29
public/scripts/index.js
Normal file
29
public/scripts/index.js
Normal file
@ -0,0 +1,29 @@
|
||||
const loginForm = document.getElementById('login-form');
|
||||
|
||||
loginForm.addEventListener('submit', async (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
const formData = new FormData(loginForm);
|
||||
const data = Object.fromEntries(formData.entries());
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/login', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
alert('Login successful!');
|
||||
} else {
|
||||
const errorData = await response.json();
|
||||
alert(`Login failed: ${errorData.message}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error during login:', error);
|
||||
alert('An error occurred while trying to log in.');
|
||||
}
|
||||
}
|
||||
);
|
35
public/scripts/register.js
Normal file
35
public/scripts/register.js
Normal file
@ -0,0 +1,35 @@
|
||||
const registerForm = document.getElementById('register-form');
|
||||
|
||||
registerForm.addEventListener('submit', async (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
const formData = new FormData(registerForm);
|
||||
const data = Object.fromEntries(formData.entries());
|
||||
|
||||
if (data.password !== data.confirmPassword) {
|
||||
alert('Passwords do not match. Please try again.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/register', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
alert('Mail send, verify your mail.');
|
||||
window.location.href = '/login'; // Redirect to login page
|
||||
} else {
|
||||
const errorData = await response.json();
|
||||
alert(`Registration failed: ${errorData.message}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error during registration:', error);
|
||||
alert('An error occurred while trying to register.');
|
||||
}
|
||||
}
|
||||
);
|
36
public/scripts/verify.js
Normal file
36
public/scripts/verify.js
Normal file
@ -0,0 +1,36 @@
|
||||
const verifyCard = document.getElementById('verify-card');
|
||||
const successMessage = document.getElementById('success-message');
|
||||
|
||||
const verifyButton = document.getElementById('verify-button');
|
||||
|
||||
verifyButton.addEventListener('click', async () => {
|
||||
|
||||
|
||||
//token in query string
|
||||
const token = new URLSearchParams(window.location.search).get('token');
|
||||
if (!token) {
|
||||
alert('Verification token is missing. Please check your email for the verification link.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/verify', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ token: token })
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
successMessage.classList.remove('hidden');
|
||||
verifyCard.classList.add('hidden');
|
||||
} else {
|
||||
const errorData = await response.json();
|
||||
alert(`Verification failed: ${errorData.message}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error during verification:', error);
|
||||
alert('An error occurred while trying to verify your account.');
|
||||
}
|
||||
});
|
331
public/styles/common.css
Normal file
331
public/styles/common.css
Normal file
@ -0,0 +1,331 @@
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
/* Fait en sorte que html et body prennent toute la hauteur */
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
/* Affichage en colonne */
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
/* Centre le contenu verticalement et horizontalement */
|
||||
|
||||
min-height: 100vh;
|
||||
|
||||
/* S'assure que le body prend au minimum toute la hauteur de la vue */
|
||||
a {
|
||||
text-decoration: none;
|
||||
/* Enlève le soulignement des liens */
|
||||
color: inherit;
|
||||
/* Hérite de la couleur du parent */
|
||||
}
|
||||
}
|
||||
|
||||
.body {
|
||||
background: repeating-conic-gradient(from 45deg,
|
||||
#a37fb1 0% 25%,
|
||||
#604566 0% 50%);
|
||||
background-size: max(10vw, 10svh) max(10vw, 10svh);
|
||||
|
||||
display: flex;
|
||||
/* Transforme le body en conteneur flex */
|
||||
flex-direction: column;
|
||||
/* Affichage en colonne */
|
||||
min-height: 100vh;
|
||||
/* S'assure que le body prend au minimum toute la hauteur de la vue */
|
||||
|
||||
animation: background 0.5s ease-in-out;
|
||||
/* Animation de transition pour le fond */
|
||||
}
|
||||
|
||||
|
||||
header {
|
||||
font-size: 20px;
|
||||
height: 90px;
|
||||
min-height: 90px;
|
||||
width: calc(100% - 80px);
|
||||
color: #C99CCF;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0 20px;
|
||||
flex-direction: row;
|
||||
background: #433147d8;
|
||||
margin: 20px;
|
||||
border-radius: 10px;
|
||||
-webkit-border-radius: 10px;
|
||||
-moz-border-radius: 10px;
|
||||
-ms-border-radius: 10px;
|
||||
-o-border-radius: 10px;
|
||||
}
|
||||
|
||||
.blur::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
backdrop-filter: blur(8px);
|
||||
-webkit-backdrop-filter: blur(8px);
|
||||
/* Pour Safari */
|
||||
z-index: -1;
|
||||
/* Assure que le fond est derrière le contenu */
|
||||
}
|
||||
|
||||
.blur {
|
||||
position: relative;
|
||||
/* Nécessaire pour le positionnement du pseudo-élément */
|
||||
}
|
||||
|
||||
footer {
|
||||
font-size: 20px;
|
||||
height: 90px;
|
||||
min-height: 90px;
|
||||
width: calc(100% - 80px);
|
||||
|
||||
|
||||
color: rgb(201, 156, 207);
|
||||
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0 20px;
|
||||
flex-direction: row;
|
||||
|
||||
background: #433147d8;
|
||||
|
||||
margin: 20px;
|
||||
border-radius: 10px;
|
||||
-webkit-border-radius: 10px;
|
||||
-moz-border-radius: 10px;
|
||||
-ms-border-radius: 10px;
|
||||
-o-border-radius: 10px;
|
||||
|
||||
font-size: 0.875rem;
|
||||
|
||||
p {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.card-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
|
||||
gap: 20px;
|
||||
/* Espace entre les cartes */
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.card-container {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
.card {
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
min-width: 300px;
|
||||
width: auto;
|
||||
min-height: 400px;
|
||||
height: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
border-radius: 10px;
|
||||
|
||||
text-decoration: none;
|
||||
color: rgb(201, 156, 207);
|
||||
|
||||
background: #433147d8;
|
||||
|
||||
transition: background 0.3s ease-in-out, transform 0.3s ease-in-out, margin 0.3s ease-in-out;
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
transform: scale(1.05);
|
||||
transition: background 0.3s ease-in-out, transform 0.3s ease-in-out, margin 0.3s ease-in-out;
|
||||
|
||||
margin: 7px;
|
||||
}
|
||||
|
||||
.card::before {
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.card-image {
|
||||
height: 200px;
|
||||
width: 200px;
|
||||
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.presentation {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
height: auto;
|
||||
width: 1000px;
|
||||
cursor: default;
|
||||
|
||||
gap: 30px;
|
||||
/* Espace entre les éléments de la carte */
|
||||
|
||||
h1 {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.info.card {
|
||||
min-width: 500px;
|
||||
width: auto;
|
||||
height: auto;
|
||||
|
||||
cursor: default;
|
||||
|
||||
h2 {
|
||||
font-size: 20px;
|
||||
color: rgb(255, 255, 255);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.card-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
/* 2 colonnes de largeur égale */
|
||||
gap: 20px;
|
||||
width: 1400px;
|
||||
|
||||
.card {
|
||||
cursor: default;
|
||||
|
||||
h3 {
|
||||
font-size: 18px;
|
||||
color: rgb(255, 255, 255);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
background-color: #FFFFFF00;
|
||||
width: calc(100% - 40px);
|
||||
/* Les cartes prennent toute la largeur de la colonne */
|
||||
height: 180px;
|
||||
/* Hauteur automatique pour s'adapter au contenu */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
width: 100%;
|
||||
height: auto;
|
||||
|
||||
gap: 20px;
|
||||
/* Espace entre les éléments du formulaire */
|
||||
|
||||
label {
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
input,
|
||||
textarea {
|
||||
width: 90%;
|
||||
max-width: 500px;
|
||||
/* Largeur maximale pour les champs de saisie */
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
border: none;
|
||||
background-color: #C99CCF30;
|
||||
color: white;
|
||||
font-size: 16px;
|
||||
margin-bottom: 10px;
|
||||
/* Espace entre les champs */
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 10px 20px;
|
||||
border-radius: 5px;
|
||||
border: none;
|
||||
background-color: #C99CCF;
|
||||
color: white;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
|
||||
transition: background-color 0.3s ease-in-out;
|
||||
|
||||
&:hover {
|
||||
background-color: #A37FB1;
|
||||
/* Couleur au survol */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.arrow-icon {
|
||||
width: 50px;
|
||||
height: auto;
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
path:nth-child(2) {
|
||||
d: path('M14.5 4H3.5H4');
|
||||
}
|
||||
|
||||
path {
|
||||
transition: 0.25s ease;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
path:nth-child(1) {
|
||||
d: path('M15 4H4V4');
|
||||
}
|
||||
|
||||
path:nth-child(2) {
|
||||
d: path('M14.5 4H3.5H0');
|
||||
transform: translateX(4px);
|
||||
}
|
||||
|
||||
path:nth-child(3) {
|
||||
transform: translateX(4px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
/* Cache l'élément */
|
||||
}
|
0
public/styles/index.css
Normal file
0
public/styles/index.css
Normal file
0
public/styles/register.css
Normal file
0
public/styles/register.css
Normal file
38
public/verify.html
Normal file
38
public/verify.html
Normal file
@ -0,0 +1,38 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>PengRegister</title>
|
||||
<link rel="stylesheet" href="styles/common.css">
|
||||
<link rel="icon" href="imgs/favicon.ico" type="image/x-icon">
|
||||
<script src="scripts/common.js" defer></script>
|
||||
<script src="scripts/verify.js" defer></script>
|
||||
</head>
|
||||
|
||||
<body id="body" class="body">
|
||||
<header id="header" class="blur">
|
||||
|
||||
</header>
|
||||
<main>
|
||||
<div class="card blur" id="verify-card">
|
||||
<h2>Verify account</h2>
|
||||
<p>To verify your account, please enter the verification code sent to your email.</p>
|
||||
<button id="verify-button">Verify</button>
|
||||
|
||||
<a href="/register">Register Page.</a>
|
||||
</div>
|
||||
|
||||
<div class="card blur hidden" id="success-message">
|
||||
<h2>Verification Successful!</h2>
|
||||
<p>Your email has been successfully verified.</p>
|
||||
<a href="/login">Go to Login Page</a>
|
||||
</div>
|
||||
</main>
|
||||
<footer id="footer" class="blur">
|
||||
|
||||
</footer>
|
||||
</body>
|
||||
|
||||
</html>
|
Loading…
x
Reference in New Issue
Block a user