feat: update things
This commit is contained in:
parent
de26f9f69d
commit
f61d0a111a
100
package-lock.json
generated
100
package-lock.json
generated
@ -9,6 +9,7 @@
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@heroicons/react": "^2.1.5",
|
||||
"axios": "^1.7.7",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-router-dom": "^6.26.1"
|
||||
@ -1582,6 +1583,12 @@
|
||||
"dev": true,
|
||||
"license": "Python-2.0"
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/autoprefixer": {
|
||||
"version": "10.4.20",
|
||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz",
|
||||
@ -1620,6 +1627,17 @@
|
||||
"postcss": "^8.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.7.7",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz",
|
||||
"integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
@ -1813,6 +1831,18 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/commander": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
|
||||
@ -1931,6 +1961,15 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/didyoumean": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
|
||||
@ -2337,6 +2376,26 @@
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.9",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
|
||||
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"debug": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/foreground-child": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz",
|
||||
@ -2354,6 +2413,20 @@
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz",
|
||||
"integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/fraction.js": {
|
||||
"version": "4.3.7",
|
||||
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
|
||||
@ -2795,6 +2868,27 @@
|
||||
"node": ">=8.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
@ -3237,6 +3331,12 @@
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/punycode": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||
|
@ -11,6 +11,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@heroicons/react": "^2.1.5",
|
||||
"axios": "^1.7.7",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-router-dom": "^6.26.1"
|
||||
|
10
src/App.tsx
10
src/App.tsx
@ -5,6 +5,10 @@ import Navigation from "./component/Navigation/Navigation";
|
||||
import Dashboard from "./pages/Dashboard";
|
||||
import Practicals from "./pages/Practicals";
|
||||
import Instances from "./pages/Instances";
|
||||
import Practical from "./pages/Practical";
|
||||
import LoginPage from "./pages/Login";
|
||||
import PageTest from "./pages/PageTest";
|
||||
import CreateTp from "./pages/admin/CreateTp";
|
||||
|
||||
function App() {
|
||||
const [showNotif, setShowNotif] = useState(false);
|
||||
@ -14,9 +18,11 @@ function App() {
|
||||
<Routes>
|
||||
<Route path="/" element={<Dashboard />} />
|
||||
<Route path="/tps" element={<Practicals />} />
|
||||
<Route path="/tps/:id" element={<h1>id</h1>} />
|
||||
<Route path="/tps/:id" element={<Practical />} />
|
||||
<Route path="/instances" element={<Instances />} />
|
||||
<Route path="/profile" element={<h1>Profile</h1>} />
|
||||
<Route path="/profile" element={<PageTest />} />
|
||||
<Route path="/login" element={<LoginPage />} />
|
||||
<Route path="/admin/tps" element={<CreateTp />} />
|
||||
</Routes>
|
||||
</Navigation>
|
||||
|
||||
|
@ -15,7 +15,7 @@ export default function Card({ title, cards }: CardProps) {
|
||||
))}
|
||||
</div>
|
||||
<div className="flex justify-end mt-auto pt-6">
|
||||
<button className="btn btn-primary">View more</button>
|
||||
<button className="btn btn-primary primary">View more</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -42,7 +42,7 @@ const Navigation: React.FC<NavigationProps> = ({
|
||||
}, [theme]);
|
||||
|
||||
return (
|
||||
<div className="app">
|
||||
<div className="app h-screen">
|
||||
<div className="drawer">
|
||||
<input
|
||||
id="my-drawer"
|
||||
@ -50,7 +50,7 @@ const Navigation: React.FC<NavigationProps> = ({
|
||||
className="drawer-toggle"
|
||||
checked={isDrawerOpen}
|
||||
/>
|
||||
<div className="drawer-content">
|
||||
<div className="drawer-content h-full">
|
||||
<div className="p-6 flex justify-between">
|
||||
<div className="btn btn-ghost" onClick={toggleDrawer}>
|
||||
<Bars3Icon className="size-6" />
|
||||
@ -59,7 +59,6 @@ const Navigation: React.FC<NavigationProps> = ({
|
||||
<input type="checkbox" onChange={handleToggle} />
|
||||
|
||||
<SunIcon className="size-6 swap-off" />
|
||||
|
||||
<MoonIcon className="size-6 swap-on" />
|
||||
</label>
|
||||
</div>
|
||||
@ -100,7 +99,7 @@ const Navigation: React.FC<NavigationProps> = ({
|
||||
</li>
|
||||
<div className="divider" onClick={toggleDrawer}></div>
|
||||
<li>
|
||||
<Link to="logout" onClick={toggleDrawer}>
|
||||
<Link to="login" onClick={toggleDrawer}>
|
||||
Log Out
|
||||
</Link>
|
||||
</li>
|
||||
|
@ -4,11 +4,9 @@ import { Link } from "react-router-dom";
|
||||
const Sidebar: React.FC = () => {
|
||||
return (
|
||||
<div className="drawer drawer-mobile">
|
||||
{/* Drawer Toggle Button (for mobile) */}
|
||||
<input id="sidebar-toggle" type="checkbox" className="drawer-toggle" />
|
||||
|
||||
<div className="drawer-content">
|
||||
{/* Main content area */}
|
||||
<label
|
||||
htmlFor="sidebar-toggle"
|
||||
className="btn btn-primary drawer-button lg:hidden"
|
||||
|
@ -1,16 +1,50 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import axios from "axios";
|
||||
import Card from "../component/Card/Card";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
function Instances() {
|
||||
return (
|
||||
<div className="flex flex-col items-center p-4 gap-5">
|
||||
<h1 className="text-4xl font-bold mb-8">Instances</h1>
|
||||
const [instances, setInstances] = useState([]);
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-16 w-[85%] h-full">
|
||||
<Card title="Actives" cards={["Websocket Chat", "VM personnelle"]} />
|
||||
<Card
|
||||
title="Terminated"
|
||||
cards={["Bot Discord", "Bienvenue a la Banquise"]}
|
||||
/>
|
||||
useEffect(() => {
|
||||
axios.get("/api/instances").then((res) => {
|
||||
setInstances(res.data);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="container mx-auto py-10">
|
||||
<h1 className="text-3xl font-bold mb-6 text-center">Your instances</h1>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{instances.map((instance) => (
|
||||
<div
|
||||
key={instance.id}
|
||||
className="card card-compact bg-base-200 shadow-lg p-4"
|
||||
>
|
||||
<div className="card-body">
|
||||
<h2 className="card-title">{instance.name}</h2>
|
||||
{instance.tp && (
|
||||
<p className="text-lg">Linked to: {instance.tp.name}</p>
|
||||
)}
|
||||
<div className="card-actions justify-end">
|
||||
<Link
|
||||
to={`/instances/${instance.id}`}
|
||||
className="btn btn-primary"
|
||||
>
|
||||
Learn More
|
||||
</Link>
|
||||
{instance.tp && (
|
||||
<Link
|
||||
to={`/tps/${instance.tp.id}`}
|
||||
className="btn btn-primary"
|
||||
>
|
||||
See TP
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
87
src/pages/Login.tsx
Normal file
87
src/pages/Login.tsx
Normal file
@ -0,0 +1,87 @@
|
||||
import axios from "axios";
|
||||
import React, { useState } from "react";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
|
||||
const LoginPage: React.FC = () => {
|
||||
const [username, setUsername] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const { from } = location.state || { from: { pathname: "/" } }; // Default to home if no state
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("j_username", username);
|
||||
formData.append("j_password", password);
|
||||
axios
|
||||
.post("/j_security_check", formData, {
|
||||
withCredentials: true,
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status === 200) {
|
||||
navigate(from);
|
||||
}
|
||||
})
|
||||
.catch((response) => {
|
||||
if (response.status === 401) {
|
||||
setError("wrong_user_pwd");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex mt-20 justify-center items-center">
|
||||
<div className="card w-96 bg-base-100 shadow-xl">
|
||||
<div className="card-body">
|
||||
<h2 className="card-title">{"login"}</h2>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="form-control">
|
||||
<label className="label">
|
||||
<span className="label-text">{"username"}</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Username"
|
||||
name="j_username"
|
||||
className={`input input-bordered ${error ? "input-error" : ""}`}
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-control mt-4">
|
||||
<label className="label">
|
||||
<span className="label-text">{"password"}</span>
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
placeholder="Password"
|
||||
name="j_password"
|
||||
className={`input input-bordered ${error ? "input-error" : ""}`}
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
{error && (
|
||||
<>
|
||||
<p className="pt-4 text-red-500">{error}</p>
|
||||
</>
|
||||
)}
|
||||
<div className="form-control mt-4">
|
||||
<button type="submit" className="btn btn-primary">
|
||||
{"login"}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LoginPage;
|
90
src/pages/PageTest.tsx
Normal file
90
src/pages/PageTest.tsx
Normal file
@ -0,0 +1,90 @@
|
||||
import axios from "axios";
|
||||
import { useState } from "react";
|
||||
|
||||
function PageTest() {
|
||||
const [users, setUsers] = useState([
|
||||
{
|
||||
id: 1,
|
||||
name: "Alice Smith",
|
||||
email: "alice@example.com",
|
||||
role: "Admin",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "Bob Johnson",
|
||||
email: "bob@example.com",
|
||||
role: "User",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: "Charlie Brown",
|
||||
email: "charlie@example.com",
|
||||
role: "User",
|
||||
},
|
||||
// Add more user data here as needed
|
||||
]);
|
||||
|
||||
const handleEditUser = (userId) => {
|
||||
// Logic for editing the user (e.g., opening a modal)
|
||||
alert(`Edit user with ID: ${userId}`);
|
||||
};
|
||||
|
||||
const handleDeleteUser = (userId) => {
|
||||
// Logic for deleting the user
|
||||
const confirmDelete = window.confirm(
|
||||
"Are you sure you want to delete this user?",
|
||||
);
|
||||
if (confirmDelete) {
|
||||
setUsers(users.filter((user) => user.id !== userId));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="container mx-auto p-6">
|
||||
<div className="bg-white shadow-lg rounded-lg p-6">
|
||||
<h1 className="text-3xl font-bold text-blue-600 mb-6">Manage Users</h1>
|
||||
|
||||
{/* Users Table */}
|
||||
<div className="overflow-x-auto">
|
||||
<table className="table w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="p-4">#</th>
|
||||
<th className="p-4">Name</th>
|
||||
<th className="p-4">Email</th>
|
||||
<th className="p-4">Role</th>
|
||||
<th className="p-4">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{users.map((user, index) => (
|
||||
<tr key={user.id} className="hover:bg-gray-100">
|
||||
<td className="p-4">{index + 1}</td>
|
||||
<td className="p-4">{user.name}</td>
|
||||
<td className="p-4">{user.email}</td>
|
||||
<td className="p-4">{user.role}</td>
|
||||
<td className="p-4">
|
||||
<button
|
||||
className="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-400 mr-2"
|
||||
onClick={() => handleEditUser(user.id)}
|
||||
>
|
||||
Edit
|
||||
</button>
|
||||
<button
|
||||
className="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-400"
|
||||
onClick={() => handleDeleteUser(user.id)}
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default PageTest;
|
98
src/pages/Practical.tsx
Normal file
98
src/pages/Practical.tsx
Normal file
@ -0,0 +1,98 @@
|
||||
import { ArrowDownTrayIcon } from "@heroicons/react/24/outline";
|
||||
import axios from "axios";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
|
||||
function Practical() {
|
||||
const { id } = useParams();
|
||||
const [tp, setTp] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
console.log("akljsbcascb");
|
||||
axios.get(`/api/tps/${id}`).then((res) => {
|
||||
setTp(res.data);
|
||||
});
|
||||
}, [id]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{tp && (
|
||||
<>
|
||||
<h1 className="text-3xl font-bold mb-2 ms-10">{tp.name}</h1>
|
||||
<div className="px-6">
|
||||
<div className="flex flex-col lg:flex-row">
|
||||
<div className="lg:w-2/3 p-4">
|
||||
<div className="w-full h-[80vh] bg-gray-200 rounded-lg overflow-hidden mb-4">
|
||||
<iframe
|
||||
src={tp.pdfLink}
|
||||
className="w-full h-full"
|
||||
title={`${tp.name} PDF`}
|
||||
></iframe>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="lg:w-1/3 px-4">
|
||||
<div className="bg-base-200 p-6 rounded-lg shadow-md mt-4">
|
||||
<h2 className="text-2xl font-semibold text-blue-600 mb-2">
|
||||
Information
|
||||
</h2>
|
||||
<ul className="space-y-4">
|
||||
<li>{tp.description}</li>
|
||||
<li className="flex justify-between">
|
||||
<span>Duration:</span>
|
||||
<span className="font-medium">2 hours</span>
|
||||
</li>
|
||||
<li className="flex justify-between">
|
||||
<span>Tools Used:</span>
|
||||
<span className="font-medium">Python, SSH, Vim</span>
|
||||
</li>
|
||||
<li className="flex justify-between">
|
||||
<span>Difficulty:</span>
|
||||
<span className="font-medium">Beginner</span>
|
||||
</li>
|
||||
<li className="flex justify-between">
|
||||
<span>Date:</span>
|
||||
<span className="font-medium">Oct 23, 2024</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="bg-base-200 p-6 rounded-lg shadow-md mt-6">
|
||||
<h2 className="text-2xl font-semibold text-blue-600 mb-2">
|
||||
Resources
|
||||
</h2>
|
||||
<ul className="space-y-4">
|
||||
<li className="flex justify-between">
|
||||
<span>SSH:</span>
|
||||
<span className="font-medium">{tp.ssh}</span>
|
||||
</li>
|
||||
<li className="flex justify-between">
|
||||
<span>Password:</span>
|
||||
<span className="font-medium">{tp.pwd}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<a
|
||||
href={tp.pdfLink}
|
||||
target="_blank"
|
||||
className="card w-full bg-base-200 shadow-md hover:shadow-lg transition-shadow rounded-lg mt-6"
|
||||
>
|
||||
<div className="card-body flex-row justify-between items-center">
|
||||
<div>
|
||||
<div className="card-title text-lg font-bold">
|
||||
Subject
|
||||
</div>
|
||||
<p className="text-base-content">Download</p>
|
||||
</div>
|
||||
<ArrowDownTrayIcon className="size-6" />
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Practical;
|
@ -1,13 +1,37 @@
|
||||
import Card from "../component/Card/Card";
|
||||
import { useEffect, useState } from "react";
|
||||
import axios from "axios";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
function Practicals() {
|
||||
return (
|
||||
<div className="flex flex-col items-center p-4 gap-5">
|
||||
<h1 className="text-4xl font-bold mb-8">TPs</h1>
|
||||
const [tps, setTps] = useState([]);
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-16 w-[85%] h-full">
|
||||
<Card title="En cours" cards={["Websocket Chat"]} />
|
||||
<Card title="Fini" cards={["Bot Discord", "Bienvenue a la Banquise"]} />
|
||||
useEffect(() => {
|
||||
axios.get("/api/tps").then((res) => {
|
||||
setTps(res.data);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="container mx-auto py-10">
|
||||
<h1 className="text-3xl font-bold mb-6 text-center">Your TPs</h1>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{tps.map((practical) => (
|
||||
<div
|
||||
key={practical.id}
|
||||
className="card card-compact bg-base-200 shadow-lg p-4"
|
||||
>
|
||||
<div className="card-body">
|
||||
<h2 className="card-title">{practical.name}</h2>
|
||||
<p>{practical.description}</p>
|
||||
<p className="text-sm text-gray-500">Respo: {practical.respo}</p>
|
||||
<div className="card-actions justify-end">
|
||||
<Link to={`/tps/${practical.id}`} className="btn btn-primary">
|
||||
Learn More
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
159
src/pages/admin/CreateTp.tsx
Normal file
159
src/pages/admin/CreateTp.tsx
Normal file
@ -0,0 +1,159 @@
|
||||
import axios from "axios";
|
||||
import { useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
function CreateTp() {
|
||||
const [practical, setPractical] = useState({
|
||||
title: "",
|
||||
description: "",
|
||||
duration: "",
|
||||
tools: "",
|
||||
difficulty: "Intermediate",
|
||||
date: "",
|
||||
pdf: "",
|
||||
});
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleInputChange = (e) => {
|
||||
const { name, value } = e.target;
|
||||
setPractical({ ...practical, [name]: value });
|
||||
};
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
console.log("Practical created :", practical);
|
||||
axios.post("/api/tps", practical).then((res) => {
|
||||
if (res.status === 200) {
|
||||
navigate(`/tps/${res.data.id}`);
|
||||
}
|
||||
});
|
||||
};
|
||||
return (
|
||||
<div className="container mx-auto p-6">
|
||||
<div className="shadow-lg rounded-lg p-6">
|
||||
<h1 className="text-3xl font-bold text-blue-600 mb-6">
|
||||
Create Practical
|
||||
</h1>
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
<div>
|
||||
<label className="block text-gray-700 font-semibold mb-2">
|
||||
Title:
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="title"
|
||||
value={practical.title}
|
||||
onChange={handleInputChange}
|
||||
className="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
placeholder="Enter practical title"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-gray-700 font-semibold mb-2">
|
||||
Description:
|
||||
</label>
|
||||
<textarea
|
||||
name="description"
|
||||
value={practical.description}
|
||||
onChange={handleInputChange}
|
||||
className="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
placeholder="Enter practical description"
|
||||
rows={4}
|
||||
required
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-gray-700 font-semibold mb-2">
|
||||
PDF Link:
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="pdf"
|
||||
value={practical.pdf}
|
||||
onChange={handleInputChange}
|
||||
className="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
placeholder="Enter PDF link"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-gray-700 font-semibold mb-2">
|
||||
Duration (hours):
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
name="duration"
|
||||
value={practical.duration}
|
||||
onChange={handleInputChange}
|
||||
className="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
placeholder="Enter duration (in hours)"
|
||||
min="1"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-gray-700 font-semibold mb-2">
|
||||
Tools Used:
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="tools"
|
||||
value={practical.tools}
|
||||
onChange={handleInputChange}
|
||||
className="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
placeholder="Enter tools used"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-gray-700 font-semibold mb-2">
|
||||
Difficulty:
|
||||
</label>
|
||||
<select
|
||||
name="difficulty"
|
||||
value={practical.difficulty}
|
||||
onChange={handleInputChange}
|
||||
className="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
>
|
||||
<option value="Beginner">Beginner</option>
|
||||
<option value="Intermediate">Intermediate</option>
|
||||
<option value="Advanced">Advanced</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-gray-700 font-semibold mb-2">
|
||||
Date:
|
||||
</label>
|
||||
<input
|
||||
type="date"
|
||||
name="date"
|
||||
value={practical.date}
|
||||
onChange={handleInputChange}
|
||||
className="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="text-right">
|
||||
<button
|
||||
type="submit"
|
||||
className="bg-blue-600 text-white px-6 py-2 rounded-lg hover:bg-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
>
|
||||
Create Practical
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default CreateTp;
|
@ -4,10 +4,27 @@ import daisyui from "daisyui";
|
||||
export default {
|
||||
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
|
||||
theme: {
|
||||
extend: {},
|
||||
extend: {
|
||||
colors: {},
|
||||
},
|
||||
colors: {},
|
||||
},
|
||||
plugins: [daisyui],
|
||||
daisyui: {
|
||||
themes: ["light", "dark"],
|
||||
// themes: ["light", "dark"],
|
||||
themes: [
|
||||
{
|
||||
light: {
|
||||
...require("daisyui/src/theming/themes")["light"],
|
||||
primary: "#99d2fd",
|
||||
test: "#76beee",
|
||||
},
|
||||
dark: {
|
||||
...require("daisyui/src/theming/themes")["dark"],
|
||||
primary: "#34a6fc",
|
||||
test: "#1f5078",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user