update some login things
This commit is contained in:
parent
a52be45907
commit
b40c1db189
7 changed files with 333 additions and 37 deletions
|
@ -70,6 +70,10 @@ export function isValidUsername(username: string): {
|
|||
valid: boolean;
|
||||
error?: string;
|
||||
} {
|
||||
if (!username) {
|
||||
return { valid: false, error: "" };
|
||||
}
|
||||
|
||||
if (username.length < userNameRestrictions.length.min) {
|
||||
return { valid: false, error: "Username is too short" };
|
||||
}
|
||||
|
@ -89,6 +93,10 @@ export function isValidPassword(password: string): {
|
|||
valid: boolean;
|
||||
error?: string;
|
||||
} {
|
||||
if (!password) {
|
||||
return { valid: false, error: "" };
|
||||
}
|
||||
|
||||
if (password.length < passwordRestrictions.length.min) {
|
||||
return {
|
||||
valid: false,
|
||||
|
@ -117,6 +125,10 @@ export function isValidEmail(email: string): {
|
|||
valid: boolean;
|
||||
error?: string;
|
||||
} {
|
||||
if (!email) {
|
||||
return { valid: false, error: "" };
|
||||
}
|
||||
|
||||
if (!emailRestrictions.regex.test(email)) {
|
||||
return { valid: false, error: "Invalid email address" };
|
||||
}
|
||||
|
@ -128,6 +140,10 @@ export function isValidInvite(invite: string): {
|
|||
valid: boolean;
|
||||
error?: string;
|
||||
} {
|
||||
if (!invite) {
|
||||
return { valid: false, error: "" };
|
||||
}
|
||||
|
||||
if (invite.length < inviteRestrictions.min) {
|
||||
return {
|
||||
valid: false,
|
||||
|
|
|
@ -9,13 +9,11 @@
|
|||
.content {
|
||||
border: 1px solid var(--border);
|
||||
background-color: var(--background-secondary);
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 2rem;
|
||||
|
||||
width: clamp(200px, 50%, 300px);
|
||||
}
|
||||
|
||||
|
@ -25,3 +23,137 @@
|
|||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.login-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(135deg, rgba(31 30 30 / 90%) 0%, rgba(45 45 45 / 90%) 100%);
|
||||
}
|
||||
|
||||
.login-logo {
|
||||
margin-bottom: 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.login-logo h1 {
|
||||
font-size: 2.5rem;
|
||||
font-weight: bold;
|
||||
color: var(--text);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.login-logo p {
|
||||
color: var(--text-secondary);
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.login-card {
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
border-radius: 8px;
|
||||
box-shadow: var(--card-shadow);
|
||||
background-color: var(--background-secondary);
|
||||
overflow: hidden;
|
||||
animation: fade-in 0.5s ease;
|
||||
}
|
||||
|
||||
.login-header {
|
||||
padding: 1.5rem;
|
||||
background-color: rgba(0 0 0 / 10%);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.login-header h2 {
|
||||
margin: 0;
|
||||
color: var(--text);
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.login-form {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.login-form form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.login-register {
|
||||
text-align: center;
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.form-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.form-footer a {
|
||||
color: var(--accent);
|
||||
text-decoration: none;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.form-footer a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.form-footer label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.login-form button {
|
||||
margin-top: 1rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: var(--error);
|
||||
background-color: rgb(237 66 69 / 10%);
|
||||
border-left: 4px solid var(--error);
|
||||
padding: 0.75rem;
|
||||
margin-bottom: 1rem;
|
||||
border-radius: 4px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
@keyframes fade-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-20px);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.register-link {
|
||||
color: #57f287;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.register-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
@media (width <= 480px) {
|
||||
.login-card {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.login-logo h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,40 +1,112 @@
|
|||
[data-theme="dark"] {
|
||||
--background: rgb(31, 30, 30);
|
||||
--background-secondary: rgb(45, 45, 45);
|
||||
--border: rgb(31, 30, 30);
|
||||
--text: rgb(255, 255, 255);
|
||||
--text-secondary: rgb(255, 255, 255);
|
||||
--background: rgb(31 30 30);
|
||||
--background-secondary: rgb(45 45 45);
|
||||
--border: rgb(70 70 70);
|
||||
--text: rgb(255 255 255);
|
||||
--text-secondary: rgb(200 200 200);
|
||||
--accent: rgb(88 101 242);
|
||||
--accent-hover: rgb(71 82 196);
|
||||
--error: rgb(237 66 69);
|
||||
--success: rgb(87 242 135);
|
||||
--shadow: rgb(0 0 0 / 20%);
|
||||
--card-shadow: 0 2px 10px 0 rgb(0 0 0 / 20%);
|
||||
--input-background: rgb(55 55 55);
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: "Ubuntu", sans-serif;
|
||||
|
||||
font-family: Ubuntu, sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
font-size: 16px;
|
||||
|
||||
background-color: var(--background);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
/* Fonts */
|
||||
@font-face {
|
||||
font-family: "Ubuntu";
|
||||
font-family: Ubuntu;
|
||||
src: url("/public/assets/fonts/Ubuntu/Ubuntu-Regular.ttf") format("truetype");
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Ubuntu Bold";
|
||||
font-family: Ubuntu Bold;
|
||||
src: url("/public/assets/fonts/Ubuntu/Ubuntu-Bold.ttf") format("truetype");
|
||||
font-weight: bold;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Fira Code";
|
||||
font-family: Fira Code;
|
||||
src: url("/public/assets/fonts/Fira_code/FiraCode-Regular.ttf") format("truetype");
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
/* Utility classes */
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
/* Form elements */
|
||||
input, button, textarea, select {
|
||||
font-family: inherit;
|
||||
font-size: 1rem;
|
||||
border-radius: 4px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
button, .button {
|
||||
cursor: pointer;
|
||||
padding: 0.75rem 1.5rem;
|
||||
border: none;
|
||||
background-color: var(--accent);
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
button:hover, .button:hover {
|
||||
background-color: var(--accent-hover);
|
||||
}
|
||||
|
||||
input, textarea, select {
|
||||
padding: 0.75rem;
|
||||
border: 1px solid var(--border);
|
||||
background-color: var(--input-background);
|
||||
color: var(--text);
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
input:focus, textarea:focus, select:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent);
|
||||
box-shadow: 0 0 0 2px rgb(88 101 242 / 30%);
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 1.5rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: bold;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
/* Card style */
|
||||
.card {
|
||||
background-color: var(--background-secondary);
|
||||
border-radius: 8px;
|
||||
box-shadow: var(--card-shadow);
|
||||
padding: 1.5rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
|
37
public/js/auth/login.js
Normal file
37
public/js/auth/login.js
Normal file
|
@ -0,0 +1,37 @@
|
|||
const loginForm = document.getElementById("login-form");
|
||||
const errorMessage = document.getElementById("error-message");
|
||||
|
||||
if (loginForm) {
|
||||
loginForm.addEventListener("submit", async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const email = document.getElementById("email").value;
|
||||
const password = document.getElementById("password").value;
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/auth/login", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
credentials: "same-origin",
|
||||
body: JSON.stringify({ email, password }),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
window.location.href = "/";
|
||||
} else {
|
||||
errorMessage.style.display = "block";
|
||||
errorMessage.textContent =
|
||||
data.error ||
|
||||
"Invalid email or password. Please try again.";
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Login error:", error);
|
||||
errorMessage.style.display = "block";
|
||||
errorMessage.textContent = "An error occurred. Please try again.";
|
||||
}
|
||||
});
|
||||
}
|
|
@ -59,11 +59,16 @@ async function handler(
|
|||
}
|
||||
|
||||
const errors: string[] = [];
|
||||
|
||||
const validations: UserValidation[] = [
|
||||
{ check: isValidUsername(username), field: "Username" },
|
||||
{ check: isValidEmail(email), field: "Email" },
|
||||
{ check: isValidPassword(password), field: "Password" },
|
||||
];
|
||||
username
|
||||
? { check: isValidUsername(username), field: "Username" }
|
||||
: null,
|
||||
email ? { check: isValidEmail(email), field: "Email" } : null,
|
||||
password
|
||||
? { check: isValidPassword(password), field: "Password" }
|
||||
: null,
|
||||
].filter(Boolean) as UserValidation[];
|
||||
|
||||
validations.forEach(({ check }: UserValidation): void => {
|
||||
if (!check.valid && check.error) {
|
||||
|
@ -71,6 +76,10 @@ async function handler(
|
|||
}
|
||||
});
|
||||
|
||||
if (!username && !email) {
|
||||
errors.push("Either a username or an email is required.");
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
return Response.json(
|
||||
{
|
||||
|
@ -86,11 +95,11 @@ async function handler(
|
|||
let user: User | null = null;
|
||||
|
||||
try {
|
||||
user = await reservation`
|
||||
[user] = await reservation`
|
||||
SELECT * FROM users
|
||||
WHERE (username = ${username} OR email = ${email})
|
||||
LIMIT 1;
|
||||
`.then((rows: User[]): User | null => rows[0]);
|
||||
`;
|
||||
|
||||
if (!user) {
|
||||
await bunPassword.verify("fake", await bunPassword.hash("fake"));
|
||||
|
|
|
@ -8,10 +8,12 @@ const routeDef: RouteDef = {
|
|||
};
|
||||
|
||||
async function handler(): Promise<Response> {
|
||||
const instanceName: string =
|
||||
(await getSetting("instance_name")) || "Unnamed Instance";
|
||||
|
||||
const ejsTemplateData: EjsTemplateData = {
|
||||
title: "Hello, World!",
|
||||
instance_name:
|
||||
(await getSetting("instance_name")) || "Unnamed Instance",
|
||||
title: `Login - ${instanceName}`,
|
||||
instance_name: instanceName,
|
||||
};
|
||||
|
||||
return await renderEjsTemplate("auth/login", ejsTemplateData);
|
||||
|
|
|
@ -1,28 +1,56 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<%- include("../global", { styles: ["auth/login"], scripts: [] }) %>
|
||||
<%- include("../global", { styles: ["auth/login"], scripts: ["auth/login"] }) %>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1><%= instance_name %></h1>
|
||||
<div class="login-container">
|
||||
<div class="login-logo">
|
||||
<h1>
|
||||
<%= instance_name %>
|
||||
</h1>
|
||||
<p>Sign in to your account</p>
|
||||
</div>
|
||||
<div class="content">
|
||||
<form id="login-form">
|
||||
<div class="form-group">
|
||||
<label for="email">Email</label>
|
||||
<input type="email" name="email" id="email" required>
|
||||
|
||||
<div class="login-card">
|
||||
<div class="login-header">
|
||||
<h2>Welcome Back</h2>
|
||||
</div>
|
||||
|
||||
<div class="login-form">
|
||||
<div class="error-message" id="error-message">
|
||||
Invalid email or password. Please try again.
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password">Password</label>
|
||||
<input type="password" name="password" id="password" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
|
||||
<form id="login-form">
|
||||
<div class="form-group">
|
||||
<label for="email">Email</label>
|
||||
<input type="email" name="email" id="email" required placeholder="Enter your email">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="password">Password</label>
|
||||
<input type="password" name="password" id="password" required placeholder="Enter your password">
|
||||
</div>
|
||||
|
||||
<div class="form-footer">
|
||||
<label>
|
||||
<input type="checkbox" name="remember" id="remember">Remember me
|
||||
</label>
|
||||
<a href="/auth/forgot-password">Forgot password?</a>
|
||||
</div>
|
||||
|
||||
<button type="submit">Login</button>
|
||||
</form>
|
||||
|
||||
<div class="login-register">
|
||||
<p>Don't have an account? <a href="/auth/register" class="register-link">Register</a></p>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
Loading…
Add table
Reference in a new issue