This commit is contained in:
pro.boooooo 2024-04-03 03:46:05 +02:00
parent 7402661aaa
commit 873dd8c458
25 changed files with 404 additions and 129 deletions

View File

@ -17,6 +17,7 @@
},
"dependencies": {
"axios": "^1.6.7",
"clipboard-copy": "^4.0.1",
"react": "^18.2.0",
"react-apexcharts": "^1.4.1",
"react-chartjs-2": "^5.2.0",

View File

@ -37,36 +37,6 @@ export default defineConfig({
name: "chromium",
use: { ...devices["Desktop Chrome"] },
},
// {
// name: "firefox",
// use: { ...devices["Desktop Firefox"] },
// },
// {
// name: "webkit",
// use: { ...devices["Desktop Safari"] },
// },
/* Test against mobile viewports. */
// {
// name: 'Mobile Chrome',
// use: { ...devices['Pixel 5'] },
// },
// {
// name: 'Mobile Safari',
// use: { ...devices['iPhone 12'] },
// },
/* Test against branded browsers. */
// {
// name: 'Microsoft Edge',
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
// },
// {
// name: 'Google Chrome',
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
// },
],
//Run your local dev server before starting the tests /

7
pnpm-lock.yaml generated
View File

@ -8,6 +8,9 @@ dependencies:
axios:
specifier: ^1.6.7
version: 1.6.8
clipboard-copy:
specifier: ^4.0.1
version: 4.0.1
react:
specifier: ^18.2.0
version: 18.2.0
@ -1143,6 +1146,10 @@ packages:
string-width: 7.1.0
dev: true
/clipboard-copy@4.0.1:
resolution: {integrity: sha512-wOlqdqziE/NNTUJsfSgXmBMIrYmfd5V0HCGsR8uAKHcg+h9NENWINcfRjtWGU77wDHC8B8ijV4hMTGYbrKovng==}
dev: false
/color-convert@1.9.3:
resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
dependencies:

View File

@ -3,7 +3,6 @@ import axios from "axios";
export const isLoggedIn = async () => {
try {
const response = await axios.get("/authenticate");
return response.data;
} catch (error) {
return error.response.data;

View File

@ -1,6 +1,7 @@
import axios from "axios";
export const createItem = async (settings) => {
try {
console.log(settings);
const formData = new FormData();
@ -17,9 +18,14 @@ export const createItem = async (settings) => {
const response = await axios.post("/item", formData);
return response.data;
} catch (err) {
console.error(err);
return null;
}
};
export const updateItem = async (settings) => {
try {
console.log(settings);
const formData = new FormData();
@ -32,10 +38,19 @@ export const updateItem = async (settings) => {
const response = await axios.put(`/item/${settings._id}`, formData);
return response.data;
} catch (err) {
console.error(err);
return null;
}
};
export const deleteItem = async (id) => {
try {
console.log(id);
const response = await axios.delete(`/item/${id}`);
return response.data;
} catch (err) {
console.error(err);
return null;
}
};

View File

@ -1,10 +1,76 @@
import axios from "axios";
import { createItem } from "./items";
export const importRooms = async (data) => {
try {
const parsed = JSON.parse(data);
if (parsed.length === 0) {
return false;
}
for (let e of parsed) {
const room = await createRoom(e.name);
for (let f of e.items) {
const settings = { ...f, room: room._id };
await createItem(settings);
}
}
return true;
} catch (err) {
console.error(err);
return null;
}
};
export const exportRooms = async () => {
try {
const rooms = await getRooms();
let stock = [];
for (let room of rooms) {
const roomData = await getRoom(room._id);
delete roomData._id;
delete roomData.__v;
delete roomData.user;
delete roomData.updatedAt;
delete roomData.createdAt;
await roomData.items.forEach((f) => {
delete f.createdAt;
delete f.image;
delete f.invoice;
delete f.updatedAt;
delete f.room;
delete f.user;
delete f.__v;
delete f._id;
});
stock.push(roomData);
}
const final = JSON.stringify(stock);
return final;
} catch (err) {
console.error(err);
return null;
}
};
export const createRoom = async (name) => {
try {
const response = await axios.post("/room", {
name,
});
return response.data;
} catch (err) {
console.error(err);
}
};
export const getState = async () => {
@ -18,16 +84,31 @@ export const getState = async () => {
};
export const deleteRoom = async (id) => {
try {
const response = await axios.delete(`/room/${id}`);
return response.data;
} catch (err) {
console.error(err);
return null;
}
};
export const getRooms = async () => {
try {
const response = await axios.get(`/room`);
return response.data;
} catch (err) {
console.error(err);
return null;
}
};
export const getRoom = async (id) => {
try {
const response = await axios.get(`/room/${id}`);
return response.data;
} catch (err) {
console.error(err);
return null;
}
};

View File

@ -18,8 +18,9 @@ export const createUser = async (username, password, confirmation) => {
confirmation,
});
return response.data;
} catch (error) {
return error.response.data;
} catch (err) {
console.error(err);
return null;
}
};

View File

@ -3,6 +3,11 @@
* {
margin: 0;
padding: 0;
::selection {
background: $primary;
color: white;
}
}
body {
a {

View File

@ -1,9 +1,9 @@
import { MagnifyingGlass } from "react-loader-spinner";
import "./LoaderSpace.scss";
export default function LoaderSpace({ isVisible }) {
export default function LoaderSpace({ isVisible, isCenterLoader }) {
return (
<div className="loader-space-container">
<div className={`loader-space-container${isCenterLoader ? "-center" : ""}`}>
<MagnifyingGlass
visible={isVisible}
height="100"

View File

@ -1,4 +1,4 @@
.loader-space-container {
.loader-space-container-center {
position: absolute;
z-index: 9999;
position: fixed;
@ -6,3 +6,6 @@
left: 50%;
transform: translate(-50%, -50%);
}
.loader-space-container {
}

View File

@ -16,9 +16,9 @@ const initState = {
export const AuthenticationContext = React.createContext({
...initState,
login: () => {},
login: async () => {},
register: () => {},
logout: () => {},
logout: async () => {},
});
export const AuthenticationProvider = ({ children }) => {
@ -46,6 +46,7 @@ export const AuthenticationProvider = ({ children }) => {
const register = async (username, password, confirmation) => {
try {
const user = await registerApi(username, password, confirmation);
console.log(user);
setAuthState({ user });
redirect();
} catch (error) {

View File

@ -1,5 +1,5 @@
import NavBar from "../components/NavBar/NavBar";
import "./Layout.css";
import "./Layout.scss";
export default function Layout({ children }) {
return (

View File

@ -1,4 +1,5 @@
#layout-container {
display: flex;
flex-direction: column;
margin-bottom: 20px;
}

View File

@ -0,0 +1,38 @@
@import "../../assets/styles/vars.scss";
.container {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
width: 100%;
height: 100%;
gap: 20px;
margin-top: 30px;
.title {
color: $good_black;
}
.error {
color: red;
font-weight: bold;
}
.form {
display: flex;
flex-direction: column;
justify-content: space-around;
align-items: center;
border-radius: $my_border_rad;
border: 1px dashed $good_black;
gap: 10px;
padding: 10px;
.ipt {
width: 300px;
height: 35px;
text-align: center;
}
}
}

View File

@ -1,6 +1,8 @@
import React, { useState } from "react";
import { useAuth } from "../../hooks";
import { useEffect } from "react";
import "./authanticated.scss";
import "../../components/StylizedBtn/StylizedBtn.scss";
export const Login = () => {
const { login } = useAuth();
@ -18,32 +20,36 @@ export const Login = () => {
const response = await login(username, password);
if (response && !response.success) {
console.log("salut");
setError(response.error);
}
};
return (
<div>
<h1>Login page</h1>
{error && <p className="text-red-500">{error}</p>}
<div className="container">
<form className="form" onSubmit={onSubmit}>
<h3 className="title">Connexion</h3>
{error && <p>{error}</p>}
<form onSubmit={onSubmit}>
<input
type="text"
value={username}
placeholder="username"
placeholder="Pseudo"
className="ipt"
required
onChange={(e) => setUsername(e.target.value)}
/>
<input
type="password"
value={password}
placeholder="password"
placeholder="Mot de passe"
className="ipt"
required
onChange={(e) => setPassword(e.target.value)}
/>
<button type="submit">submit</button>
<button className="stylized-btn" type="submit">
Go
</button>
</form>
</div>
);

View File

@ -1,5 +1,5 @@
import React, { useState, useEffect } from "react";
import "../../components/StylizedBtn/StylizedBtn.scss";
import { useAuth } from "../../hooks";
export const Register = () => {
@ -8,49 +8,69 @@ export const Register = () => {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [confirmation, setConfirmation] = useState("");
const [error, setError] = useState();
const [error, setError] = useState(null);
useEffect(() => {
document.title = "Inscription";
}, []);
const onSubmit = async (e) => {
try {
e.preventDefault();
const response = await register(username, password, confirmation);
console.log(response);
if (!response) {
setError(
"Assurez vous que le mot de passe contient au moins 8 caracteres dont une majuscule, un symbole special et au minimum un chiffre.",
);
}
if (response && !response.success) {
setError(response.error);
setError(
"Assurez vous que le mot de passe contient au moins 8 caracteres dont une majuscule, un symbole special et au minimum un chiffre.",
);
}
} catch (err) {
console.error(err);
}
};
return (
<div>
<h1>Register page</h1>
{error && <p className="text-red-500">{error}</p>}
<form onSubmit={onSubmit}>
<div className="container">
<form className="form" onSubmit={onSubmit}>
<h3 className="title">Inscription</h3>
{error ? <div className="error">{error}</div> : null}
<input
type="text"
value={username}
placeholder="username"
placeholder="Pseudo"
className="ipt"
required
onChange={(e) => setUsername(e.target.value)}
/>
<input
type="password"
className="ipt"
value={password}
required
placeholder="password"
min={8}
placeholder="Mot de passe"
onChange={(e) => setPassword(e.target.value)}
/>
<input
type="password"
className="ipt"
min={8}
required
value={confirmation}
placeholder="confirmation"
placeholder="Confirmation du mot de passe"
onChange={(e) => setConfirmation(e.target.value)}
/>
<button type="submit">submit</button>
<button className="stylized-btn" type="submit">
Go
</button>
</form>
</div>
);

View File

@ -85,7 +85,7 @@ export const Home = () => {
</div>
)}
<LoaderSpace isVisible={isLoad} />
<LoaderSpace isVisible={isLoad} isCenterLoader={true} />
{dataset ? (
<div id="stats-container">

View File

@ -1,9 +1,17 @@
import { useAuth } from "../../hooks";
import { useState, useEffect } from "react";
import "./Profile.scss";
import { updateUser, deleteUser, logout } from "../../api";
import {
updateUser,
deleteUser,
logout,
exportRooms,
importRooms,
} from "../../api";
import StylizedBtn from "../../components/StylizedBtn/StylizedBtn";
import passwordCheck from "../../services/password";
import LoaderSpace from "./../../components/LoaderSpace/LoaderSpace";
import clipboardCopy from "clipboard-copy";
//Bilouuuuuuu94!@@
export default function Profile() {
@ -13,6 +21,10 @@ export default function Profile() {
const [oldPPassword, setOldPPassword] = useState("");
const [password, setPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
const [isIELoad, setIsIELoad] = useState(false);
const [exportDatas, setExportDatas] = useState(null);
const [importMsg, setImportMsg] = useState("");
const [isInAfterImport, setIsInAfterImport] = useState(false);
useEffect(() => {
document.title = `Profile`;
@ -40,6 +52,37 @@ export default function Profile() {
setOldPPassword(e.target.value);
};
const importData = () => {
const data = prompt("Veuillez coller le code que vous avez copier");
if (!data) return;
setExportDatas(null);
setIsInAfterImport(true);
setIsIELoad(true);
setImportMsg("Chargement ...");
importRooms(data).then((res) => {
if (!res) {
setImportMsg("Une erreur est survenue");
} else {
setImportMsg("Vos depenses ont ete transfere avec sucess.");
}
setIsIELoad(false);
console.log(res);
});
};
const exportData = () => {
setIsIELoad(true);
setIsInAfterImport(false);
exportRooms().then((res) => {
setExportDatas(res);
setIsIELoad(false);
clipboardCopy(`${res}`).then();
});
};
const handleSubmitPassword = () => {
if (!passwordCheck(password, confirmPassword)) {
alert(
@ -148,18 +191,36 @@ export default function Profile() {
<div className="profile-modifier-container">
<span onClick={handleHelpIE} className="profile-modifier-title ">
Fais voyager tes depenses.
Fais voyager tes depenses !
</span>
<LoaderSpace isCenterLoader={false} isVisible={isIELoad} />
{exportDatas ? (
<div id="exported-datas-container">
<div id="exported-datas-tips">
Vos données ont été copiées dans le presse-papier. Pour pouvoir
les importer, il suffira juste de cliquer sur le bouton ci-dessous{" "}
<b>"Importer"</b>, puis de coller les données dans le champ de
saisie qui s'ouvrira en haut de l'écran.
</div>
<div id="exported-datas">{exportDatas}</div>
</div>
) : null}
{isInAfterImport ? <div>{importMsg}</div> : null}
{!exportDatas ? (
<StylizedBtn
perso_style={{ width: "100px" }}
handle={null}
handle={exportData}
text="Exporter"
/>
) : null}
<StylizedBtn
perso_style={{ width: "100px" }}
handle={null}
handle={importData}
text="Importer"
/>
</div>

View File

@ -33,11 +33,31 @@
gap: 10px;
border-radius: $my_border_rad;
#exported-datas-container {
width: 80%;
max-height: 300px;
display: flex;
flex-direction: column;
gap: 20px;
#exported-datas-tips {
border: 1px solid $good_black;
padding: 5px;
}
#exported-datas {
width: 100%;
max-height: 200px;
overflow: auto;
}
}
@media (max-width: 770px) {
width: 80%;
}
.profile-modifier-title {
cursor: pointer;
color: $black_text;
font-weight: bold;
}

View File

@ -5,6 +5,7 @@ import "./Room.scss";
import AddBtn from "../../components/AddBtn/AddBtn";
import { createItem, deleteItem, updateItem } from "../../api/";
import StylizedBtn from "../../components/StylizedBtn/StylizedBtn";
import LoaderSpace from "../../components/LoaderSpace/LoaderSpace";
export default function Room() {
const { id } = useParams();
@ -12,12 +13,16 @@ export default function Room() {
const [isInForm, setInForm] = useState(false);
const [isInFormUpdate, setIsInFormUpdate] = useState(false);
const [formUpdateData, setFormUpdateData] = useState(null);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
setIsLoading(true);
getRoom(id).then((res) => {
console.log(res);
document.title = `Piece - ${res.name}`;
setData(res);
setIsLoading(false);
});
}, []);
@ -109,6 +114,10 @@ export default function Room() {
</div>
) : null}
{isLoading ? (
<LoaderSpace isCenterLoader={true} isVisible={isLoading} />
) : null}
{isInForm || isInFormUpdate ? <div id="blur"></div> : null}
{data.items && data.items.length > 0 ? (
@ -176,11 +185,7 @@ export default function Room() {
</div>
))}
</div>
) : (
<div>
<span>Cette piece ne contient pas d'objet.</span>
</div>
)}
) : null}
{isInForm && (
<div id="form-container">

View File

@ -106,6 +106,7 @@
display: flex;
gap: 5px;
width: 100%;
align-items: center;
.item-description {
color: $black;
@ -117,6 +118,7 @@
display: flex;
width: 100%;
gap: 5px;
align-items: center;
.item-price {
font-weight: bold;
@ -126,6 +128,7 @@
.item-buy-date-container {
display: flex;
width: 100%;
align-items: center;
gap: 5px;
.item-buy-date {

View File

@ -3,6 +3,7 @@ import { useState, useEffect } from "react";
import { createRoom, getRooms, deleteRoom } from "../../api";
import { Link } from "react-router-dom";
import LoaderSpace from "../../components/LoaderSpace/LoaderSpace";
import AddBtn from "./../../components/AddBtn/AddBtn";
export default function Rooms() {
const [rooms, setRooms] = useState(null);
@ -85,7 +86,7 @@ export default function Rooms() {
))}
</div>
) : (
<LoaderSpace isVisible={isLoad} />
<LoaderSpace isVisible={isLoad} isCenterLoader={true} />
)}
{isErr && !isLoad ? (
@ -94,9 +95,7 @@ export default function Rooms() {
<div id="rooms-add-container">
<div id="rooms-text-on">Creer une nouvelle piece</div>
<button id="add-rooms" onClick={onClickCreate}>
+
</button>
<AddBtn handle={onClickCreate} />
</div>
</div>
);

BIN
sujet.pdf Normal file

Binary file not shown.

View File

@ -1,26 +1,32 @@
// @ts-check
import { test, expect } from "@playwright/test";
import { waitFor } from "@testing-library/react";
test("Test de connexion", async ({ page }) => {
const account = "Bilouuuuuuu94!@@";
await page.goto("/login");
await page.waitForURL("/login");
await page
.locator(
"#layout-container > main > div > form > input[type=text]:nth-child(1)",
)
.fill("Bilouuuuuuu94!@@");
.fill(account);
await page
.locator(
"#layout-container > main > div > form > input[type=password]:nth-child(2)",
)
.fill("Bilouuuuuuu94!@@");
.fill(account);
await page.locator("#layout-container > main > div > form > button").click({
button: "left",
});
await page.waitForURL("/");
const title = await page.title();
console.log(title);
expect(await page.title()).toBe("Accueil");
expect(await page.locator("#title").innerText()).toBe(`Bonjour ${account} !`);
test.setTimeout(10000);
await page.waitForURL("/");
});

View File

@ -0,0 +1,33 @@
// @ts-check
import { test, expect } from "@playwright/test";
test("Test de connexion", async ({ page }) => {
const account = "Eheheh9400$$!@@";
await page.goto("/register");
await page.waitForURL("/register");
await page
.locator(
"#layout-container > main > div > form > input[type=text]:nth-child(1)",
)
.fill(account);
await page
.locator(
"#layout-container > main > div > form > input[type=password]:nth-child(2)",
)
.fill(account);
await page
.locator(
"#layout-container > main > div > form > input[type=password]:nth-child(3)",
)
.fill(account);
await page
.locator("#layout-container > main > div > form > button")
.click({ button: "left" });
await page.waitForURL("/");
expect(await page.title()).toBe("Accueil");
expect(await page.locator("#title").innerText()).toBe(`Bonjour ${account} !`);
});