first commit

This commit is contained in:
2025-02-26 23:42:19 +01:00
commit 777ea98be1
283 changed files with 88266 additions and 0 deletions
@@ -0,0 +1,279 @@
import React, { useState, useEffect } from "react";
import { Api } from "../client/auth_client";
import {UnAuthorized} from './Auth.tsx'
import PhoneInput from 'react-phone-input-2';
import 'react-phone-input-2/lib/style.css';
import './AccountSettings.css';
import {useNavigate} from "react-router-dom";
const AccountSettings: React.FC = () => {
const [email, setEmail] = useState<string | null>();
const [dialCodeAndPhoneNumber, setDialCodeAndPhoneNumber] = useState<string>("");
const [dialCode, setDialCode] = useState<string | null>();
const [phoneNumber, setPhoneNumber] = useState<string |null>();
const [currentPassword, setCurrentPassword] = useState("");
const [newPassword, setNewPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
const [problem, setProblem] = useState<string | null>(null);
const [view, setView] = useState<"profile" | "password">("profile");
const [isOk, setOk] = useState<boolean>(false);
const navigate = useNavigate();
useEffect(() => {
const getUserDetail = async () => {
const token = localStorage.getItem("authToken");
const client = new Api({
baseUrl: "/auth",
securityWorker: () => ({ headers: { Authorization: `Bearer ${token}` } }),
});
const result = await client.account.getMyDetailCreate();
if (result?.data) {
const dialCode = result.data.dialCode ?? "";
const phoneNumber = result.data.phone ?? "";
setDialCode(dialCode);
setPhoneNumber(phoneNumber);
setDialCodeAndPhoneNumber(dialCode + phoneNumber);
setEmail(result.data.email);
}
setOk(true);
};
getUserDetail().then();
}, []);
const handleSaveEmailChanges = async () => {
try {
setProblem(null);
const token = localStorage.getItem("authToken");
const client = new Api({
baseUrl: "/auth",
securityWorker: () => ({ headers: { Authorization: `Bearer ${token}` } }),
});
const emailResponse = await client.account.updateUserEmailCreate({ newEmail: email });
if(emailResponse.status === 401) await UnAuthorized(navigate)
if (!emailResponse.data.updateSuccess) {
setProblem("Failed to update email: " + JSON.stringify(emailResponse.data.problems));
return;
}
alert("Email changes saved successfully!");
} catch (error) {
setProblem("An error occurred while saving email changes. Please try again.");
console.error("Error saving email changes", error);
}
};
const handleSavePhoneChanges = async () => {
try {
setProblem(null);
const token = localStorage.getItem("authToken");
const client = new Api({
baseUrl: "/auth",
securityWorker: () => ({ headers: { Authorization: `Bearer ${token}` } }),
});
const phoneResponse = await client.account.updatePhoneNumberCreate({
prefix: dialCode,
number: phoneNumber,
});
if(phoneResponse.status === 401) await UnAuthorized(navigate)
if (!phoneResponse.data.updateSuccess) {
setProblem("Failed to update phone: " + JSON.stringify(phoneResponse.data.problems));
return;
}
alert("Phone changes saved successfully!");
} catch (error) {
setProblem("An error occurred while saving phone changes. Please try again.");
console.error("Error saving phone changes", error);
}
};
const handleChangePassword = async () => {
const token = localStorage.getItem("authToken");
const client = new Api({
baseUrl: "/auth",
securityWorker: () => ({ headers: { Authorization: `Bearer ${token}` } }),
});
try {
setProblem(null);
if (newPassword && newPassword === confirmPassword) {
const passwordResponse = await client.account.changePasswordCreate({
password: currentPassword,
newPassword1: newPassword,
newPassword2: confirmPassword,
logOutAllSession: false
});
setCurrentPassword("")
setConfirmPassword("")
setNewPassword("")
if(passwordResponse.status === 401) await UnAuthorized(navigate)
if (!passwordResponse.data.isSuccess) {
setProblem("Failed to change password: " + passwordResponse.data.mess);
return;
}
alert("Password changed successfully!");
} else if (newPassword !== confirmPassword) {
setProblem("New password and confirm password do not match.");
}
} catch (error) {
setProblem("An error occurred while changing the password. Please try again.");
console.error("Error changing password", error);
}
};
if (!isOk) {
return (
<div className="loading-screen">
<div className="spinner">
<div className="lds-ring">
<div></div>
<div></div>
<div></div>
<div></div>
</div>
</div>
</div>
);
}
return (
<div className="settings-container">
<div className="settings-sidebar">
<h2>Account Settings</h2>
<div className="toggle-buttons">
<button
className={`btn ${view === "profile" ? "btn-selected" : ""}`}
onClick={() => setView("profile")}
>
Profile Changes
</button>
<button
className={`btn ${view === "password" ? "btn-selected" : ""}`}
onClick={() => setView("password")}
>
Password Changes
</button>
</div>
</div>
<div className="settings-content">
<div className="toggle-form-container">
<div className="divider-line-vertical"></div>
{view === "profile" && (
<div className="settings-section">
<h4>Profile Information</h4>
<div className="form-group">
<label>Email</label>
<div className="input-button-container">
<input
type="email"
value={email ?? ""}
onChange={(e) => setEmail(e.target.value)}
className="form-control compact-input"
/>
<button className="btn btn-primary" onClick={handleSaveEmailChanges}>
Save Email Changes
</button>
</div>
</div>
<div className="form-group">
<label>Phone</label>
<PhoneInput
country={'pl'}
value={dialCodeAndPhoneNumber}
onChange={(phone, data) => {
let lenOfDialCode : number = 0;
if ("dialCode" in data) {
lenOfDialCode = data.dialCode.length;
setDialCode(data.dialCode);
}
const phoneNumber = phone.slice(lenOfDialCode, phone.length);
setPhoneNumber(phoneNumber);
setDialCodeAndPhoneNumber(phone)
}}
inputProps={{
name: 'phone',
required: true,
}}
/>
<button className="btn btn-primary" onClick={handleSavePhoneChanges}>
Save Phone Changes
</button>
</div>
</div>
)}
{view === "password" && (
<div className="settings-section">
<h4>Change Password</h4>
<div className="form-group">
<label>Current Password</label>
<input
type="password"
value={currentPassword}
onChange={(e) => setCurrentPassword(e.target.value)}
className="form-control compact-input"
/>
</div>
<div className="form-group">
<label>New Password</label>
<input
type="password"
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
className="form-control compact-input"
/>
</div>
<div className="form-group">
<label>Confirm New Password</label>
<input
type="password"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
className="form-control compact-input"
/>
</div>
<button className="btn btn-primary compact-button" onClick={handleChangePassword}>
Change Password
</button>
{problem && <div className="problem-field">{problem}</div>}
</div>
)}
</div>
</div>
</div>
);
};
export default AccountSettings;
@@ -0,0 +1,129 @@
.settings-container {
display: flex;
max-width: 100%;
margin: 20px;
padding: 0;
color: #edf2f7;
align-items: flex-start;
border-radius: 8px;
}
.settings-sidebar {
display: flex;
flex-direction: column;
align-items: flex-start;
min-width: 250px;
padding: 20px;
}
.toggle-buttons {
display: flex;
flex-direction: column;
gap: 10px;
margin-top: 20px;
width: 100%;
}
.btn {
background-color: #2d3748;
color: #edf2f7;
border: none;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
transition: background-color 0.3s;
width: 100%;
max-width: 400px;
text-align: center;
}
.btn:hover {
background-color: #4a5568;
}
.btn-selected {
background-color: #3182ce;
}
.btn-selected:hover {
background-color: #5e97d0;
}
.settings-content {
flex: 1;
padding: 20px;
max-width: 800px;
margin-left: 20px;
display: flex;
}
.toggle-form-container {
display: flex;
flex: 1;
}
.divider-line-vertical {
width: 2px;
height: auto;
background-color: #4a5568;
margin: 0 20px;
}
.settings-section {
width: 100%;
margin-bottom: 30px;
}
.settings-section h4 {
margin-bottom: 15px;
color: #63b3ed;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
.form-control {
padding: 10px;
border: 1px solid #4a5568;
border-radius: 5px;
background-color: #2d3748;
color: #edf2f7;
}
.compact-input {
max-width: 400px;
width: 100%;
}
.form-control:focus {
border-color: #63b3ed;
outline: none;
box-shadow: 0 0 0 2px rgba(99, 179, 237, 0.5);
}
.compact-button {
max-width: 400px;
width: 100%;
}
.btn-primary {
background-color: #3182ce;
border-color: #3182ce;
color: #edf2f7;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
margin-top: 20px;
}
.btn-primary:hover {
background-color: #63b3ed;
border-color: #63b3ed;
}
@@ -0,0 +1,77 @@
import {createRemoteJWKSet, jwtVerify} from 'jose';
import {Api as Auth} from '../client/auth_client';
import {useNavigate} from "react-router-dom";
export interface TokenPayload {
unique_name: string;
exp: number;
[key: string]: any;
}
export const validateToken = async (token: string): Promise<TokenPayload | null> => {
try {
const JWKS = createRemoteJWKSet(
new URL('auth/.well-known/jwks', window.location.origin)
);
const { payload } = await jwtVerify(token, JWKS);
const tokenPayload = payload as TokenPayload;
const currentTime = Math.floor(Date.now() / 1000);
if (tokenPayload.exp && tokenPayload.exp > currentTime) {
return tokenPayload;
} else {
return null;
}
} catch (error) {
console.error('Token validation failed', error);
return null;
}
};
export const handleLogout = async (navigate: ReturnType<typeof useNavigate>) => {
const token = localStorage.getItem('authToken');
if (token) {
const client = new Auth({
baseUrl: 'auth',
securityWorker: () => ({ headers: { Authorization: `Bearer ${token}` } }),
});
try {
const result = await client.logOut.logOutList();
if (result?.data?.logOutSuccess) {
localStorage.removeItem('authToken');
navigate('/login');
}
} catch (error) {
console.error('Logout failed', error);
}
} else {
navigate('/login');
}
};
export const GetUserPrivileges = async (navigate: ReturnType<typeof useNavigate>) => {
const token = localStorage.getItem('authToken');
if (token) {
const client = new Auth({
baseUrl: 'auth',
securityWorker: () => ({ headers: { Authorization: `Bearer ${token}` } }),
});
try {
const result = await client.privilege.getUserPrivilegeCreate();
return result.data;
} catch (error) {
localStorage.removeItem('authToken');
navigate('/login');
}
} else {
navigate('/login');
}
};
export const UnAuthorized = async (navigate: ReturnType<typeof useNavigate>) => {
localStorage.removeItem('authToken');
navigate('/login');
};
@@ -0,0 +1,58 @@
body {
margin: 0;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #2d3748; /* Modern dark background */
color: #edf2f7; /* Light text color for readability */
}
.login-container {
max-width: 400px;
margin: 0 auto;
background-color: #1a202c; /* Dark background for login card */
border-radius: 8px;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.25);
padding: 20px;
color: #edf2f7;
}
.card {
background-color: #1a202c;
border: none;
color: #edf2f7;
}
.card .form-label {
color: #a0aec0; /* Subtle color for form labels */
}
.form-control {
background-color: #2d3748;
color: #edf2f7;
border: 1px solid #4a5568;
border-radius: 5px;
}
.form-control:focus {
border-color: #63b3ed;
outline: none;
box-shadow: 0 0 0 2px rgba(99, 179, 237, 0.5);
}
.btn-primary {
background-color: #3182ce;
border-color: #3182ce;
color: #edf2f7;
border-radius: 5px;
}
.btn-primary:hover {
background-color: #63b3ed;
border-color: #63b3ed;
}
.text-danger {
color: #e53e3e; /* Bright red for error messages */
font-size: 0.9rem;
margin-bottom: 10px;
text-align: center;
}
@@ -0,0 +1,89 @@
import React, { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { Api as auth } from '../client/auth_client.ts';
import '../LoadingScreen.css';
import './Login.css'
const LoginForm: React.FC = () => {
const [login, setLogin] = useState('');
const [password, setPassword] = useState('');
const [problem, setProblem] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(false);
const navigate = useNavigate();
const client = new auth({ baseUrl: 'auth' });
useEffect(() => {
document.title = 'Login - My App';
}, []);
const handleEnterKey = (e: React.KeyboardEvent) => {
if (e.key === 'Enter') {
handleLogin();
}
};
const handleLogin = async () => {
setIsLoading(true);
try {
const response = await client.login.loginCreate({ login: login, pass: password });
if (response.status === 200 && response.data && response.data.token) {
const token = response.data.token;
localStorage.setItem('authToken', token);
navigate('/');
}
} catch (error) {
setProblem('Login failed. Please check your credentials.');
} finally {
setIsLoading(false);
}
};
if (isLoading) {
return (
<div className="loading-screen">
<div className="spinner">
<div className="lds-ring"><div></div><div></div><div></div><div></div></div>
</div>
</div>
);
}
return (
<div className="d-flex justify-content-center align-items-center vh-100">
<div className="">
<div className="card p-4 shadow-lg login-container">
<h3 className="text-center mb-4">Login</h3>
<div className="text-danger">{problem}</div>
<div className="mb-3">
<label htmlFor="username" className="form-label">Username</label>
<input
id="username"
type="text"
onKeyDown={handleEnterKey}
value={login}
onChange={(e) => setLogin(e.target.value)}
className="form-control"
required
/>
</div>
<div className="mb-3">
<label htmlFor="password" className="form-label">Password</label>
<input
id="password"
type="password"
onKeyDown={handleEnterKey}
value={password}
onChange={(e) => setPassword(e.target.value)}
className="form-control"
required
/>
</div>
<button className="btn btn-primary btn-lg btn-block w-100" onClick={handleLogin}>Login</button>
</div>
</div>
</div>
);
};
export default LoginForm;
@@ -0,0 +1,330 @@
.react-tel-input {
font-family: 'Roboto', sans-serif;
font-size: 15px;
position: relative;
width: 100%;
}
.react-tel-input :disabled {
cursor: not-allowed;
}
.react-tel-input .flag {
width: 16px;
height: 11px;
}
.react-tel-input .form-control {
position: relative;
font-size: 14px;
letter-spacing: 0.01rem;
margin-top: 0 !important;
margin-bottom: 0 !important;
padding-left: 48px;
margin-left: 0;
background: #ffffff;
border: 1px solid #cacaca;
border-radius: 5px;
line-height: 25px;
height: 35px;
width: 300px;
outline: none;
}
.react-tel-input .form-control.invalid-number {
border: 1px solid #d79f9f;
background-color: #faf0f0;
border-left-color: #cacaca;
}
.react-tel-input .form-control.invalid-number:focus {
border: 1px solid #d79f9f;
border-left-color: #cacaca;
background-color: #faf0f0;
}
.react-tel-input .flag-dropdown {
position: absolute;
top: 0;
bottom: 0;
padding: 0;
background-color: #f5f5f5;
border: 1px solid #cacaca;
border-radius: 3px 0 0 3px;
}
.react-tel-input .flag-dropdown:hover,
.react-tel-input .flag-dropdown:focus {
cursor: pointer;
}
.react-tel-input .flag-dropdown.invalid-number {
border-color: #d79f9f;
}
.react-tel-input .flag-dropdown.open {
z-index: 2;
background: #ffffff;
border-radius: 3px 0 0 0;
}
.react-tel-input .flag-dropdown.open .selected-flag {
background: #ffffff;
border-radius: 3px 0 0 0;
}
.react-tel-input input[disabled] + .flag-dropdown:hover {
cursor: default;
}
.react-tel-input input[disabled] + .flag-dropdown:hover .selected-flag {
background-color: transparent;
}
.react-tel-input .selected-flag {
outline: none;
position: relative;
width: 38px;
height: 100%;
padding: 0 0 0 8px;
border-radius: 3px 0 0 3px;
}
.react-tel-input .selected-flag:hover,
.react-tel-input .selected-flag:focus {
background-color: #ffffff;
}
.react-tel-input .selected-flag .flag {
position: absolute;
top: 50%;
margin-top: -5px;
}
.react-tel-input .selected-flag .arrow {
position: relative;
top: 50%;
margin-top: -2px;
left: 20px;
width: 0;
height: 0;
border-left: 3px solid transparent;
border-right: 3px solid transparent;
border-top: 4px solid #555;
}
.react-tel-input .selected-flag .arrow.up {
border-top: none;
border-bottom: 4px solid #555;
}
.react-tel-input .country-list {
outline: none;
z-index: 1;
list-style: none;
position: absolute;
padding: 0;
margin: 10px 0 10px -1px;
box-shadow: 1px 2px 10px rgba(0, 0, 0, 0.35);
background-color: #ffffff;
width: 300px;
max-height: 200px;
overflow-y: scroll;
border-radius: 0 0 3px 3px;
}
.react-tel-input .country-list .flag {
display: inline-block;
}
.react-tel-input .country-list .divider {
padding-bottom: 5px;
margin-bottom: 5px;
border-bottom: 1px solid #cccccc;
}
.react-tel-input .country-list .country {
padding: 7px 9px;
}
.react-tel-input .country-list .country .dial-code {
color: #6b6b6b;
}
.react-tel-input .country-list .country:hover {
background-color: #f1f1f1;
}
.react-tel-input .country-list .country.highlight {
background-color: #f1f1f1;
}
.react-tel-input .country-list .flag {
margin-right: 7px;
margin-top: 2px;
}
.react-tel-input .country-list .country-name {
margin-right: 6px;
}
.react-tel-input .country-list .search {
position: sticky;
top: 0;
background-color: #ffffff;
padding: 10px 0 6px 10px;
}
.react-tel-input .country-list .search-emoji {
font-size: 15px;
}
.react-tel-input .country-list .search-box {
border: 1px solid #cacaca;
border-radius: 3px;
font-size: 15px;
line-height: 15px;
margin-left: 6px;
padding: 3px 8px 5px;
outline: none;
}
.react-tel-input .country-list .no-entries-message {
padding: 7px 10px 11px;
opacity: 0.7;
}
.react-tel-input .invalid-number-message {
position: absolute;
z-index: 1;
font-size: 13px;
left: 46px;
top: -8px;
background: #ffffff;
padding: 0 2px;
color: #de0000;
}
.react-tel-input .special-label {
display: none;
position: absolute;
z-index: 1;
font-size: 13px;
left: 46px;
top: -8px;
background: #ffffff;
padding: 0 2px;
white-space: nowrap;
}
.react-tel-input * {
box-sizing: border-box;
-moz-box-sizing: border-box;
}
.react-tel-input .hide {
display: none;
}
.react-tel-input .v-hide {
visibility: hidden;
}
.react-tel-input {
font-family: 'Roboto', sans-serif;
font-size: 15px;
position: relative;
width: 100%;
}
.react-tel-input :disabled {
cursor: not-allowed;
}
.react-tel-input .form-control {
border: 1px solid #4a5568;
border-radius: 5px;
background-color: #2d3748;
color: #edf2f7;
max-width: 400px;
width: 100%;
}
.react-tel-input .form-control:focus {
border-color: #63b3ed;
outline: none;
box-shadow: 0 0 0 2px rgba(99, 179, 237, 0.5);
}
.react-tel-input .flag-dropdown {
position: absolute;
top: 0;
bottom: 0;
padding: 0;
background-color: #2d3748;
border: 1px solid #4a5568;
border-radius: 5px 0 0 5px;
}
.react-tel-input .flag-dropdown:hover, .react-tel-input .flag-dropdown:focus {
cursor: pointer;
background-color: #4a5568;
}
.react-tel-input .selected-flag {
outline: none;
position: relative;
width: 38px;
height: 100%;
padding: 0 0 0 8px;
border-radius: 5px 0 0 5px;
background-color: #2d3748;
}
.react-tel-input .selected-flag:hover, .react-tel-input .selected-flag:focus {
background-color: #4a5568;
}
.react-tel-input .country-list {
outline: none;
z-index: 1;
list-style: none;
position: absolute;
padding: 0;
margin: 10px 0 10px -1px;
box-shadow: 1px 2px 10px rgba(0, 0, 0, 0.35);
background-color: #2d3748;
width: 300px;
max-height: 200px;
overflow-y: scroll;
border-radius: 0 0 5px 5px;
color: #edf2f7;
}
.react-tel-input .country-list .country:hover {
background-color: #4a5568;
}
.react-tel-input .country-list .search-box {
border: 1px solid #4a5568;
border-radius: 5px;
font-size: 15px;
line-height: 15px;
padding: 3px 8px 5px;
outline: none;
background-color: #2d3748;
color: #edf2f7;
}
.react-tel-input .country-list .search-box:focus {
border-color: #63b3ed;
box-shadow: 0 0 0 2px rgba(99, 179, 237, 0.5);
}
.react-tel-input .country-list .country.highlight {
background-color: #343c4b;
}
.react-tel-input .flag-dropdown.open .selected-flag {
background: #404752;
}