feat(forms & services): prepare by chatgpt changes in application + own adjustments
improve login form & add more simpler axios services + cookie usagefeat/x_gpu/chat_gpt_new_version
parent
fe05313ce2
commit
45a3415e52
File diff suppressed because it is too large
Load Diff
|
|
@ -15,11 +15,13 @@
|
||||||
"clean": "gatsby clean"
|
"clean": "gatsby clean"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"axios": "^1.7.9",
|
||||||
"gatsby": "^5.13.3",
|
"gatsby": "^5.13.3",
|
||||||
"gatsby-plugin-sass": "^6.13.1",
|
"gatsby-plugin-sass": "^3.2.0",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"react-dom": "^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"
|
"sass": "^1.32.7"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -13,6 +13,52 @@ const UserLoginForm = () => {
|
||||||
const usernameInput = React.createRef()
|
const usernameInput = React.createRef()
|
||||||
const passwordInput = 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 dispatch = useDispatch()
|
||||||
// const { info } = useSelector( userAuthSelector )
|
// const { info } = useSelector( userAuthSelector )
|
||||||
const info = "" // if redux is integrated - delete this line
|
const info = "" // if redux is integrated - delete this line
|
||||||
|
|
@ -27,17 +73,21 @@ const UserLoginForm = () => {
|
||||||
type: 'info',
|
type: 'info',
|
||||||
action: 'Create',
|
action: 'Create',
|
||||||
endpint: 'user/auth',
|
endpint: 'user/auth',
|
||||||
button_value: 'CONTINUE'
|
button_value: 'SIGN IN'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'text',
|
type: 'text',
|
||||||
name: 'EMAIL',
|
name: 'EMAIL',
|
||||||
ref: usernameInput
|
ref: usernameInput,
|
||||||
|
onChange: usernameValidation,
|
||||||
|
validationInfo: usernameValidationInfo
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'password',
|
type: 'password',
|
||||||
name: 'PASSWORD',
|
name: 'PASSWORD',
|
||||||
ref: passwordInput
|
ref: passwordInput,
|
||||||
|
onChange: passwordValidation,
|
||||||
|
validationInfo: passwordValidationInfo
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,8 @@ import '../styles/large.cube.scss';
|
||||||
|
|
||||||
const LargeCubeComponent = () => {
|
const LargeCubeComponent = () => {
|
||||||
return (
|
return (
|
||||||
<div className="graphic-container">
|
<div className="large-graphic-container">
|
||||||
<div className="cube">
|
<div className="large-cube">
|
||||||
<div className="front"></div>
|
<div className="front"></div>
|
||||||
<div className="back"></div>
|
<div className="back"></div>
|
||||||
<div className="right"></div>
|
<div className="right"></div>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
);
|
||||||
|
|
||||||
|
|
@ -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}
|
||||||
|
<button onClick={() => handleStop(t._id)}>Stop</button>
|
||||||
|
<button onClick={() => handleDelete(t._id)}>Delete</button>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FormAi;
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -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})
|
||||||
|
{r.s3Key && (
|
||||||
|
<button onClick={() => handleDownload(r.s3Key)}>Download ZIP</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FormRenders;
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -4,19 +4,27 @@ import '../../styles/general.scss';
|
||||||
|
|
||||||
import LargeCubeComponent from '../../components/largeCube.js';
|
import LargeCubeComponent from '../../components/largeCube.js';
|
||||||
import UserLoginForm from '../../components/forms/user_auth/userLogin.js';
|
import UserLoginForm from '../../components/forms/user_auth/userLogin.js';
|
||||||
|
import FootComponent from '../../components/foot.js';
|
||||||
|
import NavBarComponent from '../../components/navbar.js';
|
||||||
|
|
||||||
const LoginPage = () => {
|
const LoginPage = () => {
|
||||||
return (
|
return (
|
||||||
<div className="login-container">
|
<>
|
||||||
<header className="header">
|
<NavBarComponent />
|
||||||
<h1>XGPU</h1>
|
<div className="login-container" style={{"display": "flex"}}>
|
||||||
</header>
|
|
||||||
<main className="main-content">
|
<main className="main-content">
|
||||||
<LargeCubeComponent />
|
<div style={{"padding-left": "200px"}}>
|
||||||
<UserLoginForm />
|
<UserLoginForm />
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
<main className="main-content">
|
||||||
|
<div style={{"padding-right": "200px"}}>
|
||||||
|
<LargeCubeComponent />
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
<FootComponent />
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -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();
|
||||||
|
|
@ -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=/;';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -130,7 +130,9 @@ body, html {
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-content {
|
.main-content {
|
||||||
margin-top: 0%;
|
padding-top: 15%;
|
||||||
|
padding-bottom: 12%;
|
||||||
|
width: 50%;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-evenly;
|
justify-content: space-evenly;
|
||||||
}
|
}
|
||||||
|
|
@ -153,7 +155,6 @@ body, html {
|
||||||
// border: 2px solid $border-color;
|
// border: 2px solid $border-color;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
|
|
||||||
margin-top: -400px;
|
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
$cube-size: 400px;
|
$cube-size: 350px;
|
||||||
$cube-color: rgba(0, 140, 0, 0.5);
|
$cube-color: rgba(0, 140, 0, 0.5);
|
||||||
$perspective: 10000px;
|
$perspective: 100000px;
|
||||||
$animation-duration: 20s;
|
$animation-duration: 20s;
|
||||||
|
|
||||||
@mixin cube-face {
|
@mixin large-cube-face {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: $cube-size - 15px;
|
width: $cube-size - 15px;
|
||||||
height: $cube-size - 15px;
|
height: $cube-size - 15px;
|
||||||
|
|
@ -11,7 +11,7 @@ $animation-duration: 20s;
|
||||||
will-change: transform;
|
will-change: transform;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes rotateCube {
|
@keyframes rotateLargeCube {
|
||||||
0% {
|
0% {
|
||||||
transform: rotateX(0) rotateY(0) rotateZ(0);
|
transform: rotateX(0) rotateY(0) rotateZ(0);
|
||||||
}
|
}
|
||||||
|
|
@ -20,24 +20,26 @@ $animation-duration: 20s;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.graphic-container {
|
.large-graphic-container {
|
||||||
perspective: $perspective;
|
width: 100%;
|
||||||
perspective-origin: 50% 100px; // Adjusted for better 3D effect
|
margin-top: 0%;
|
||||||
z-index: 1; // Lower z-index than the login form to place it behind
|
//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;
|
width: $cube-size;
|
||||||
height: $cube-size;
|
height: $cube-size;
|
||||||
position: relative;
|
//position: relative;
|
||||||
margin: auto; // Centers the cube within the graphic container
|
margin: auto; // Centers the cube within the graphic container
|
||||||
transform-style: preserve-3d;
|
transform-style: preserve-3d;
|
||||||
transform-origin: center center; // Rotates around the center of the cube
|
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;
|
will-change: transform;
|
||||||
|
|
||||||
div {
|
div {
|
||||||
@include cube-face;
|
@include large-cube-face;
|
||||||
}
|
}
|
||||||
|
|
||||||
.front { transform: translateZ($cube-size / 2); }
|
.front { transform: translateZ($cube-size / 2); }
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue