diff --git a/package.json b/package.json index ac4b120..9ee48e7 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/playwright.config.js b/playwright.config.js index 95de71c..5352cd4 100644 --- a/playwright.config.js +++ b/playwright.config.js @@ -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 / diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 899500c..5155c00 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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: diff --git a/src/api/authentication.js b/src/api/authentication.js index ed86046..77cc1ae 100644 --- a/src/api/authentication.js +++ b/src/api/authentication.js @@ -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; diff --git a/src/api/items.js b/src/api/items.js index 242ba61..de8a7bc 100644 --- a/src/api/items.js +++ b/src/api/items.js @@ -1,41 +1,56 @@ import axios from "axios"; export const createItem = async (settings) => { - console.log(settings); + try { + console.log(settings); - const formData = new FormData(); - formData.append("room", settings.room); - formData.append("brand", settings.brand); - formData.append("model", settings.model); - formData.append("price", settings.price); - formData.append("purchaseDate", settings.purchaseDate); - formData.append("link", settings.link); - formData.append("description", settings.description); - formData.append("image", settings.image); - formData.append("invoice", settings.invoice); + const formData = new FormData(); + formData.append("room", settings.room); + formData.append("brand", settings.brand); + formData.append("model", settings.model); + formData.append("price", settings.price); + formData.append("purchaseDate", settings.purchaseDate); + formData.append("link", settings.link); + formData.append("description", settings.description); + formData.append("image", settings.image); + formData.append("invoice", settings.invoice); - const response = await axios.post("/item", formData); + const response = await axios.post("/item", formData); - return response.data; + return response.data; + } catch (err) { + console.error(err); + return null; + } }; export const updateItem = async (settings) => { - console.log(settings); + try { + console.log(settings); - const formData = new FormData(); - formData.append("brand", settings.brand); - formData.append("model", settings.model); - formData.append("price", settings.price); - formData.append("description", settings.description); - formData.append("purchaseDate", settings.purchaseDate); - formData.append("link", settings.link); + const formData = new FormData(); + formData.append("brand", settings.brand); + formData.append("model", settings.model); + formData.append("price", settings.price); + formData.append("description", settings.description); + formData.append("purchaseDate", settings.purchaseDate); + formData.append("link", settings.link); - const response = await axios.put(`/item/${settings._id}`, formData); - return response.data; + const response = await axios.put(`/item/${settings._id}`, formData); + return response.data; + } catch (err) { + console.error(err); + return null; + } }; export const deleteItem = async (id) => { - console.log(id); - const response = await axios.delete(`/item/${id}`); - return response.data; + try { + console.log(id); + const response = await axios.delete(`/item/${id}`); + return response.data; + } catch (err) { + console.error(err); + return null; + } }; diff --git a/src/api/rooms.js b/src/api/rooms.js index 950d0b1..159962e 100644 --- a/src/api/rooms.js +++ b/src/api/rooms.js @@ -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) => { - const response = await axios.post("/room", { - name, - }); - return response.data; + 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) => { - const response = await axios.delete(`/room/${id}`); - return response.data; + try { + const response = await axios.delete(`/room/${id}`); + return response.data; + } catch (err) { + console.error(err); + return null; + } }; export const getRooms = async () => { - const response = await axios.get(`/room`); - return response.data; + try { + const response = await axios.get(`/room`); + return response.data; + } catch (err) { + console.error(err); + return null; + } }; export const getRoom = async (id) => { - const response = await axios.get(`/room/${id}`); - return response.data; + try { + const response = await axios.get(`/room/${id}`); + return response.data; + } catch (err) { + console.error(err); + return null; + } }; diff --git a/src/api/user.js b/src/api/user.js index e0a4c62..210b173 100644 --- a/src/api/user.js +++ b/src/api/user.js @@ -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; } }; diff --git a/src/assets/styles/index.scss b/src/assets/styles/index.scss index 92c2b81..bf14591 100644 --- a/src/assets/styles/index.scss +++ b/src/assets/styles/index.scss @@ -3,6 +3,11 @@ * { margin: 0; padding: 0; + + ::selection { + background: $primary; + color: white; + } } body { a { diff --git a/src/components/LoaderSpace/LoaderSpace.jsx b/src/components/LoaderSpace/LoaderSpace.jsx index fea09bd..38e7630 100644 --- a/src/components/LoaderSpace/LoaderSpace.jsx +++ b/src/components/LoaderSpace/LoaderSpace.jsx @@ -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 ( -
+
{}, + 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) { diff --git a/src/layout/Layout.jsx b/src/layout/Layout.jsx index 1176394..9ac6f12 100644 --- a/src/layout/Layout.jsx +++ b/src/layout/Layout.jsx @@ -1,11 +1,11 @@ import NavBar from "../components/NavBar/NavBar"; -import "./Layout.css"; +import "./Layout.scss"; export default function Layout({ children }) { - return ( -
- -
{children}
-
- ); + return ( +
+ +
{children}
+
+ ); } diff --git a/src/layout/Layout.css b/src/layout/Layout.scss similarity index 73% rename from src/layout/Layout.css rename to src/layout/Layout.scss index b15e85e..5f950c4 100644 --- a/src/layout/Layout.css +++ b/src/layout/Layout.scss @@ -1,4 +1,5 @@ #layout-container { display: flex; flex-direction: column; + margin-bottom: 20px; } diff --git a/src/pages/authenticated/authanticated.scss b/src/pages/authenticated/authanticated.scss new file mode 100644 index 0000000..964abfa --- /dev/null +++ b/src/pages/authenticated/authanticated.scss @@ -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; + } + } +} diff --git a/src/pages/authenticated/login.jsx b/src/pages/authenticated/login.jsx index b740514..ce929b3 100644 --- a/src/pages/authenticated/login.jsx +++ b/src/pages/authenticated/login.jsx @@ -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 ( -
-

Login page

- {error &&

{error}

} +
+
+

Connexion

+ {error &&

{error}

} - setUsername(e.target.value)} /> setPassword(e.target.value)} /> - + +
); diff --git a/src/pages/authenticated/register.jsx b/src/pages/authenticated/register.jsx index 76cbe9b..3424ef3 100644 --- a/src/pages/authenticated/register.jsx +++ b/src/pages/authenticated/register.jsx @@ -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) => { - e.preventDefault(); - const response = await register(username, password, confirmation); - console.log(response); + try { + e.preventDefault(); + const response = await register(username, password, confirmation); - if (response && !response.success) { - setError(response.error); + 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( + "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 ( -
-

Register page

- {error &&

{error}

} -
+
+ +

Inscription

+ + {error ?
{error}
: null} + setUsername(e.target.value)} /> setPassword(e.target.value)} /> setConfirmation(e.target.value)} /> - +
); diff --git a/src/pages/home/home.jsx b/src/pages/home/home.jsx index 8c43d18..2eaa13b 100644 --- a/src/pages/home/home.jsx +++ b/src/pages/home/home.jsx @@ -85,7 +85,7 @@ export const Home = () => {
)} - + {dataset ? (
diff --git a/src/pages/profile/Profile.jsx b/src/pages/profile/Profile.jsx index 54cb460..5224d8c 100644 --- a/src/pages/profile/Profile.jsx +++ b/src/pages/profile/Profile.jsx @@ -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() {
- Fais voyager tes depenses. + Fais voyager tes depenses ! - + + + {exportDatas ? ( +
+
+ 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{" "} + "Importer", puis de coller les données dans le champ de + saisie qui s'ouvrira en haut de l'écran. +
+
{exportDatas}
+
+ ) : null} + + {isInAfterImport ?
{importMsg}
: null} + + {!exportDatas ? ( + + ) : null}
diff --git a/src/pages/profile/Profile.scss b/src/pages/profile/Profile.scss index 2d265e6..ccf62f8 100644 --- a/src/pages/profile/Profile.scss +++ b/src/pages/profile/Profile.scss @@ -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; } diff --git a/src/pages/room/Room.jsx b/src/pages/room/Room.jsx index b619b2a..f7d8ed5 100644 --- a/src/pages/room/Room.jsx +++ b/src/pages/room/Room.jsx @@ -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() {
) : null} + {isLoading ? ( + + ) : null} + {isInForm || isInFormUpdate ?
: null} {data.items && data.items.length > 0 ? ( @@ -176,11 +185,7 @@ export default function Room() {
))}
- ) : ( -
- Cette piece ne contient pas d'objet. -
- )} + ) : null} {isInForm && (
diff --git a/src/pages/room/Room.scss b/src/pages/room/Room.scss index 52a7f0c..17b9c89 100644 --- a/src/pages/room/Room.scss +++ b/src/pages/room/Room.scss @@ -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 { diff --git a/src/pages/rooms/Rooms.jsx b/src/pages/rooms/Rooms.jsx index 4612b6b..4896cfa 100644 --- a/src/pages/rooms/Rooms.jsx +++ b/src/pages/rooms/Rooms.jsx @@ -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() { ))}
) : ( - + )} {isErr && !isLoad ? ( @@ -94,9 +95,7 @@ export default function Rooms() {
Creer une nouvelle piece
- +
); diff --git a/sujet.pdf b/sujet.pdf new file mode 100644 index 0000000..680b60d Binary files /dev/null and b/sujet.pdf differ diff --git a/tests/authenticated/login.test.js b/tests/authenticated/login.test.js index 8e79c0d..73f832b 100644 --- a/tests/authenticated/login.test.js +++ b/tests/authenticated/login.test.js @@ -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("/"); }); diff --git a/tests/authenticated/register.test.js b/tests/authenticated/register.test.js new file mode 100644 index 0000000..d13ff91 --- /dev/null +++ b/tests/authenticated/register.test.js @@ -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} !`); +});