feat(forms & services): prepare by chatgpt changes in application + own adjustments

improve login form & add more simpler axios services + cookie usage
feat/x_gpu/chat_gpt_new_version
TBS093A 2025-02-06 20:32:58 +01:00
parent fe05313ce2
commit 45a3415e52
21 changed files with 4301 additions and 3710 deletions

7253
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -15,11 +15,13 @@
"clean": "gatsby clean"
},
"dependencies": {
"axios": "^1.7.9",
"gatsby": "^5.13.3",
"gatsby-plugin-sass": "^6.13.1",
"gatsby-plugin-sass": "^3.2.0",
"react": "^18",
"react-dom": "^18",
"react-particles-js": "^3.4.1",
"react-router-dom": "^7.1.5",
"react-tsparticles": "^2.12.2",
"sass": "^1.32.7"
}
}

52
src/App.js 100644
View File

@ -0,0 +1,52 @@
// src/App.js
import React from 'react';
import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom';
import LoginPage from './pages/user/login';
import FormLogin from './pages/FormLogin';
import FormRegister from './pages/FormRegister';
import LandingPage from './pages/Landing';
import FormModels from './pages/FormModels';
import FormRenders from './pages/FormRenders';
import FormAi from './pages/FormAi';
function App() {
return (
<Router>
<nav style={{ margin: '1rem' }}>
<Link to="/login" style={{ marginRight: '10px' }}>
Login
</Link>
<Link to="/register" style={{ marginRight: '10px' }}>
Register
</Link>
<Link to="/landing" style={{ marginRight: '10px' }}>
Offer
</Link>
<Link to="/models" style={{ marginRight: '10px' }}>
Models
</Link>
<Link to="/renders" style={{ marginRight: '10px' }}>
Renders
</Link>
<Link to="/ai-tasks" style={{ marginRight: '10px' }}>
AI Tasks
</Link>
</nav>
<Routes>
<Route path="/" element={<FormLogin />} />
<Route path="/login" element={<LoginPage />} />
<Route path="/register" element={<FormRegister />} />
<Route path="/landing" element={<LandingPage />} />
<Route path="/models" element={<FormModels />} />
<Route path="/renders" element={<FormRenders />} />
<Route path="/ai-tasks" element={<FormAi />} />
<Route path="*" element={<div>Not Found</div>} />
</Routes>
</Router>
);
}
export default App;

View File

@ -13,6 +13,52 @@ const UserLoginForm = () => {
const usernameInput = React.createRef()
const passwordInput = React.createRef()
const emailRegex = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
const passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/
const [usernameValidationInfo, setUsernameValidationInfo] = useState("Empty")
const [passwordValidationInfo, setPasswordValidationInfo] = useState("Empty")
const [password, setPassword] = useState("")
const [allowButtonAction, setAllowButtonAction] = useState(false)
const usernameValidation = (event) => {
if (event.target.value === "") {
setUsernameValidationInfo("Email is required.")
} else if(!emailRegex.test(event.target.value)) {
setUsernameValidationInfo("Please provide correct email")
} else {
setUsernameValidationInfo("Success")
}
}
const passwordValidation = (event) => {
setPassword(event.target.value)
if (event.target.value === "") {
setPasswordValidationInfo("Password is required.")
} else if(!passwordRegex.test(event.target.value)) {
setPasswordValidationInfo("Password require:\n - At least 8 characters,\n - At least one uppercase letter,\n - At least one lowercase letter,\n - At least one digit,\n - At least one special character.")
} else {
setPasswordValidationInfo("Success")
}
}
useEffect(() => {
setAllowButtonAction(
usernameValidationInfo === "Success"
&& passwordValidationInfo === "Success"
)
}, [
allowButtonAction,
usernameValidationInfo,
passwordValidationInfo
]
)
// const dispatch = useDispatch()
// const { info } = useSelector( userAuthSelector )
const info = "" // if redux is integrated - delete this line
@ -27,17 +73,21 @@ const UserLoginForm = () => {
type: 'info',
action: 'Create',
endpint: 'user/auth',
button_value: 'CONTINUE'
button_value: 'SIGN IN'
},
{
type: 'text',
name: 'EMAIL',
ref: usernameInput
ref: usernameInput,
onChange: usernameValidation,
validationInfo: usernameValidationInfo
},
{
type: 'password',
name: 'PASSWORD',
ref: passwordInput
ref: passwordInput,
onChange: passwordValidation,
validationInfo: passwordValidationInfo
}
]

View File

@ -5,8 +5,8 @@ import '../styles/large.cube.scss';
const LargeCubeComponent = () => {
return (
<div className="graphic-container">
<div className="cube">
<div className="large-graphic-container">
<div className="large-cube">
<div className="front"></div>
<div className="back"></div>
<div className="right"></div>

27
src/index.js 100644
View File

@ -0,0 +1,27 @@
// import React from 'react';
//
// import LoginPage from './pages/user/login.js';
// import LandingPage from './pages/Landing.js';
//
// const IndexPage = () => {
// return (
// <LandingPage />
// )
// }
//
//
// export default IndexPage
// src/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<BrowserRouter>
<App />
</BrowserRouter>
);

View File

@ -0,0 +1,70 @@
import React, { useEffect, useState } from 'react';
import { getAiTasks, createAiTask, updateAiTask, deleteAiTask } from '../services/task.service';
function FormAi() {
const [tasks, setTasks] = useState([]);
const fetchTasks = async () => {
try {
const data = await getAiTasks();
setTasks(data || []);
} catch (err) {
console.error(err);
alert('Failed to load AI tasks');
}
};
useEffect(() => {
fetchTasks();
}, []);
const handleCreate = async () => {
const userId = 123; // Example user ID
try {
await createAiTask(userId);
alert('AI task created');
fetchTasks();
} catch (err) {
console.error(err);
alert('Failed to create AI task');
}
};
const handleStop = async (taskId) => {
try {
await updateAiTask(taskId, 'stop');
fetchTasks();
} catch (err) {
console.error(err);
alert('Failed to stop AI task');
}
};
const handleDelete = async (taskId) => {
try {
await deleteAiTask(taskId);
fetchTasks();
} catch (err) {
console.error(err);
alert('Failed to delete AI task');
}
};
return (
<div style={{ margin: '1rem' }}>
<h2>AI Tasks</h2>
<button onClick={handleCreate}>Create AI Task</button>
<ul>
{tasks.map((t) => (
<li key={t._id}>
Task ID: {t._id}, Status: {t.status}&nbsp;
<button onClick={() => handleStop(t._id)}>Stop</button>
<button onClick={() => handleDelete(t._id)}>Delete</button>
</li>
))}
</ul>
</div>
);
}
export default FormAi;

View File

@ -0,0 +1,52 @@
import React, { useState } from 'react';
import authService from '../services/auth.service';
import { useNavigate } from 'react-router-dom';
function FormLogin() {
const navigate = useNavigate();
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const handleLogin = async (e) => {
e.preventDefault();
try {
await authService.login(username, password);
alert('Login successful!');
navigate('/models');
} catch (err) {
alert('Login failed');
console.error(err);
}
};
return (
<div style={{ maxWidth: '400px', margin: '0 auto' }}>
<h2>Login</h2>
<form onSubmit={handleLogin}>
<div>
<label>Username:</label>
<input
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
required
/>
</div>
<div>
<label>Password:</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
</div>
<button type="submit">Login</button>
</form>
</div>
);
}
export default FormLogin;

View File

@ -0,0 +1,79 @@
import React, { useEffect, useState } from 'react';
import { getModels, createModel, deleteModel } from '../services/model.service';
function FormModels() {
const [models, setModels] = useState([]);
const [modelName, setModelName] = useState('');
const [file, setFile] = useState(null);
const fetchModels = async () => {
try {
const data = await getModels();
setModels(data.items || []);
} catch (error) {
console.error(error);
alert('Failed to load models');
}
};
useEffect(() => {
fetchModels();
}, []);
const handleCreateModel = async (e) => {
e.preventDefault();
if (!modelName || !file) {
alert('Please provide name and file');
return;
}
try {
await createModel(modelName, file);
setModelName('');
setFile(null);
alert('Model created successfully');
fetchModels();
} catch (err) {
console.error(err);
alert('Failed to create model');
}
};
const handleDelete = async (id) => {
if (!window.confirm('Are you sure?')) return;
try {
await deleteModel(id);
fetchModels();
} catch (error) {
console.error(error);
alert('Error deleting model');
}
};
return (
<div style={{ margin: '1rem' }}>
<h2>Models</h2>
<form onSubmit={handleCreateModel}>
<input
type="text"
placeholder="Model Name"
value={modelName}
onChange={(e) => setModelName(e.target.value)}
/>
<input type="file" onChange={(e) => setFile(e.target.files?.[0] || null)} />
<button type="submit">Create Model</button>
</form>
<ul>
{models.map((m) => (
<li key={m.id}>
{m.name} (ID: {m.id}){' '}
<button onClick={() => handleDelete(m.id)}>Delete</button>
</li>
))}
</ul>
</div>
);
}
export default FormModels;

View File

@ -0,0 +1,63 @@
import React, { useState } from 'react';
import authService from '../services/auth.service';
import { useNavigate } from 'react-router-dom';
function FormRegister() {
const navigate = useNavigate();
const [username, setUsername] = useState('');
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const handleRegister = async (e) => {
e.preventDefault();
try {
await authService.register(username, password, email);
alert('Registration successful!');
navigate('/login');
} catch (err) {
alert('Registration failed');
console.error(err);
}
};
return (
<div style={{ maxWidth: '400px', margin: '0 auto' }}>
<h2>Register</h2>
<form onSubmit={handleRegister}>
<div>
<label>Username:</label>
<input
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
required
/>
</div>
<div>
<label>Email:</label>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
</div>
<div>
<label>Password:</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
</div>
<button type="submit">Register</button>
</form>
</div>
);
}
export default FormRegister;

View File

@ -0,0 +1,47 @@
import React, { useEffect, useState } from 'react';
import api from '../services/api'; // or a dedicated function
function FormRenders() {
const [renders, setRenders] = useState([]);
const fetchRenders = async () => {
try {
// GET /renders? (Your backend might differ.)
// Must handle pagination if needed
const res = await api.get('/renders', { params: { page: 1, limit: 10 } });
setRenders(res.data.items || []);
} catch (error) {
console.error(error);
alert('Failed to load renders');
}
};
useEffect(() => {
fetchRenders();
}, []);
const handleDownload = (s3Key) => {
// If the backend returns a direct S3 link, just open it:
// window.open(downloadUrl, '_blank');
// Otherwise, if you only have s3Key, you might need to build the S3 URL or call a backend endpoint that redirects.
const s3Url = `https://YOUR_BUCKET.s3.amazonaws.com/${s3Key}`;
window.open(s3Url, '_blank');
};
return (
<div style={{ margin: '1rem' }}>
<h2>Renders</h2>
{renders.map((r) => (
<div key={r.id} style={{ marginBottom: '0.5rem' }}>
<strong>{r.name}</strong> (ID: {r.id}) &nbsp;
{r.s3Key && (
<button onClick={() => handleDownload(r.s3Key)}>Download ZIP</button>
)}
</div>
))}
</div>
);
}
export default FormRenders;

View File

@ -1,13 +0,0 @@
import React from 'react';
import LoginPage from './user/login.js';
import LandingPage from './landing.js';
const IndexPage = () => {
return (
<LandingPage />
)
}
export default IndexPage

View File

@ -4,19 +4,27 @@ import '../../styles/general.scss';
import LargeCubeComponent from '../../components/largeCube.js';
import UserLoginForm from '../../components/forms/user_auth/userLogin.js';
import FootComponent from '../../components/foot.js';
import NavBarComponent from '../../components/navbar.js';
const LoginPage = () => {
return (
<div className="login-container">
<header className="header">
<h1>XGPU</h1>
</header>
<>
<NavBarComponent />
<div className="login-container" style={{"display": "flex"}}>
<main className="main-content">
<LargeCubeComponent />
<UserLoginForm />
<div style={{"padding-left": "200px"}}>
<UserLoginForm />
</div>
</main>
<main className="main-content">
<div style={{"padding-right": "200px"}}>
<LargeCubeComponent />
</div>
</main>
</div>
<FootComponent />
</>
)
}

View File

@ -0,0 +1,23 @@
// src/services/api.js
import axios from 'axios';
import { getCookie } from './cookie';
const api = axios.create({
baseURL: 'http://0.0.0.0:9090',
withCredentials: true,
});
api.interceptors.request.use(
(config) => {
const token = getCookie('access_token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
export default api;

View File

@ -0,0 +1,35 @@
// src/services/auth.service.js
import api from './api';
import { setCookie, eraseCookie } from './cookie';
class AuthService {
async login(username, password) {
// The FastAPI /auth endpoint expects form data: username, password
const formData = new FormData();
formData.append('username', username);
formData.append('password', password);
const response = await api.post('/auth', formData);
const { access_token } = response.data;
// Save to cookie
setCookie('access_token', access_token, 1); // 1 day (adjust as needed)
return response.data;
}
async register(username, password, email) {
const payload = { username, password, email };
return api.post('/register/', payload);
}
logout() {
// Remove token cookie
eraseCookie('access_token');
}
// Optionally, you can provide a check to see if cookie is present
isLoggedIn() {
return !!document.cookie.includes('access_token=');
}
}
export default new AuthService();

View File

@ -0,0 +1,24 @@
export function setCookie(name, value, days = 1) {
const date = new Date();
date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000);
const expires = 'expires=' + date.toUTCString();
document.cookie = `${name}=${value};${expires};path=/;SameSite=Lax`;
}
export function getCookie(name) {
const nameEQ = name + '=';
const ca = document.cookie.split(';');
for (let i = 0; i < ca.length; i++) {
let c = ca[i];
while (c.charAt(0) === ' ') c = c.substring(1);
if (c.indexOf(nameEQ) === 0) {
return c.substring(nameEQ.length, c.length);
}
}
return null;
}
export function eraseCookie(name) {
document.cookie = name + '=; Max-Age=-99999999;path=/;';
}

View File

@ -0,0 +1,72 @@
// src/services/model.service.js
import api from './api';
/**
* Fetch a paginated list of models
*/
export async function getModels(page = 1, limit = 10) {
const res = await api.get('/models', { params: { page, limit } });
return res.data;
}
/**
* Create a new model:
* 1) Request a presigned URL from backend
* 2) PUT the file to S3
* 3) POST the model data with s3Key to backend
*/
export async function createModel(name, file) {
// get presigned URL from your backend
const presignRes = await api.post('/models/upload_url', { fileName: file.name });
const { url, key } = presignRes.data; // e.g. { url, key }
// upload file to S3
await fetch(url, {
method: 'PUT',
headers: { 'Content-Type': file.type },
body: file,
});
// call actual model creation
const createRes = await api.post('/models', {
name,
s3Key: key,
});
return createRes.data;
}
/**
* Update an existing model (similar approach)
*/
export async function updateModel(modelId, newName, newFile) {
let s3Key;
if (newFile) {
// 1) get presigned URL
const presignRes = await api.post('/models/upload_url', { fileName: newFile.name });
const { url, key } = presignRes.data;
// 2) PUT file to S3
await fetch(url, {
method: 'PUT',
headers: { 'Content-Type': newFile.type },
body: newFile,
});
s3Key = key;
}
// 3) call PUT /models/{id} with updated fields
const payload = {};
if (newName) payload.name = newName;
if (s3Key) payload.s3Key = s3Key;
const updateRes = await api.put(`/models/${modelId}`, payload);
return updateRes.data;
}
/**
* Delete model
*/
export async function deleteModel(modelId) {
const res = await api.delete(`/models/${modelId}`);
return res.data;
}

View File

@ -0,0 +1,82 @@
// src/services/task.service.js
import api from './api';
/**
* RENDER tasks
*/
// Return a list of tasks from the backend
export async function getRenderTasks(status, page = 1, limit = 10) {
const params = { page, limit };
if (status) params.status = status;
const res = await api.get('/renders/tasks', { params });
return res.data;
}
/**
* Create a render task (Photo or Video).
* The backend expects either `photo: {...}` or `video: {...}` in the request body
*/
export async function createRenderTaskPhoto(userId, modelId, renderName) {
const body = {
photo: { USER_ID: userId, MODEL_ID: modelId, RENDER_NAME: renderName },
};
const res = await api.post('/renders/tasks', body);
return res.data;
}
export async function createRenderTaskVideo(userId, modelId, renderName, frameStart, frameEnd) {
const body = {
video: {
USER_ID: userId,
MODEL_ID: modelId,
RENDER_NAME: renderName,
FRAME_START: frameStart,
FRAME_END: frameEnd,
},
};
const res = await api.post('/renders/tasks', body);
return res.data;
}
// Stop or resume a render task
export async function updateRenderTask(taskId, action) {
const res = await api.put(`/renders/tasks/${taskId}`, { action });
return res.data;
}
// Delete (stop) a render task
export async function deleteRenderTask(taskId) {
const res = await api.delete(`/renders/tasks/${taskId}`);
return res.data;
}
/**
* AI tasks
*/
export async function getAiTasks(status, page = 1, limit = 10) {
const params = { page, limit };
if (status) params.status = status;
const res = await api.get('/ai/tasks', { params });
return res.data;
}
export async function createAiTask(userId) {
// The backend expects { ai_model: { USER_ID: ... } }
const body = {
ai_model: { USER_ID: userId },
};
const res = await api.post('/ai/tasks', body);
return res.data;
}
export async function updateAiTask(taskId, action) {
const res = await api.put(`/ai/tasks/${taskId}`, { action });
return res.data;
}
export async function deleteAiTask(taskId) {
const res = await api.delete(`/ai/tasks/${taskId}`);
return res.data;
}

View File

@ -130,7 +130,9 @@ body, html {
}
.main-content {
margin-top: 0%;
padding-top: 15%;
padding-bottom: 12%;
width: 50%;
align-items: center;
justify-content: space-evenly;
}
@ -153,7 +155,6 @@ body, html {
// border: 2px solid $border-color;
border-radius: 10px;
margin-top: -400px;
margin-right: auto;
margin-left: auto;

View File

@ -1,9 +1,9 @@
$cube-size: 400px;
$cube-size: 350px;
$cube-color: rgba(0, 140, 0, 0.5);
$perspective: 10000px;
$perspective: 100000px;
$animation-duration: 20s;
@mixin cube-face {
@mixin large-cube-face {
position: absolute;
width: $cube-size - 15px;
height: $cube-size - 15px;
@ -11,7 +11,7 @@ $animation-duration: 20s;
will-change: transform;
}
@keyframes rotateCube {
@keyframes rotateLargeCube {
0% {
transform: rotateX(0) rotateY(0) rotateZ(0);
}
@ -20,24 +20,26 @@ $animation-duration: 20s;
}
}
.graphic-container {
perspective: $perspective;
perspective-origin: 50% 100px; // Adjusted for better 3D effect
z-index: 1; // Lower z-index than the login form to place it behind
.large-graphic-container {
width: 100%;
margin-top: 0%;
//perspective: $perspective;
//perspective-origin: 50% 100px; // Adjusted for better 3D effect
//z-index: 1; // Lower z-index than the login form to place it behind
}
.cube {
.large-cube {
width: $cube-size;
height: $cube-size;
position: relative;
//position: relative;
margin: auto; // Centers the cube within the graphic container
transform-style: preserve-3d;
transform-origin: center center; // Rotates around the center of the cube
animation: rotateCube $animation-duration infinite linear;
animation: rotateLargeCube $animation-duration infinite linear;
will-change: transform;
div {
@include cube-face;
@include large-cube-face;
}
.front { transform: translateZ($cube-size / 2); }