From c3952ed737523ec5ed15d31d04c582ce3f163891 Mon Sep 17 00:00:00 2001 From: Florian Schaessens Date: Sat, 8 Feb 2025 16:32:46 +0100 Subject: [PATCH 01/15] feature Get all dogs by owner id --- .../src/components/_atoms/Button/Button.scss | 17 ++++- .../src/components/_atoms/Button/Button.tsx | 7 +- .../components/_molecules/Card/IdCard.scss | 4 +- .../src/components/_molecules/Card/IdCard.tsx | 13 +++- client/src/graphQL/queries/dog.ts | 13 ++++ client/src/layouts/Dashboard/DashSideBar.tsx | 4 +- client/src/main.tsx | 3 +- .../pages/Owner/Dogs/DogList/MyDogList.scss | 69 +++++++++++++++++++ .../pages/Owner/Dogs/DogList/MyDogList.tsx | 49 +++++++++++++ 9 files changed, 169 insertions(+), 10 deletions(-) create mode 100644 client/src/graphQL/queries/dog.ts create mode 100644 client/src/pages/Owner/Dogs/DogList/MyDogList.scss create mode 100644 client/src/pages/Owner/Dogs/DogList/MyDogList.tsx diff --git a/client/src/components/_atoms/Button/Button.scss b/client/src/components/_atoms/Button/Button.scss index dac8c6e..5735e0e 100644 --- a/client/src/components/_atoms/Button/Button.scss +++ b/client/src/components/_atoms/Button/Button.scss @@ -6,7 +6,8 @@ $btn-types: ( "light", "invite", "role-select-left", - "role-select-right" + "role-select-right", + "thin-btn-light" ); .button { @@ -84,4 +85,18 @@ $btn-types: ( background-color: $orange-400; } } + + &.thin-btn-light { + $background-color: $orange-400; + background-color: $orange-400; + padding: 0.8rem; + padding-inline: min(5rem, 10vw); + border-radius: 5px; + box-shadow: (0 3px 5px 5px #ad998533) ; + + &:hover { + background-color: lighten($background-color, 5%); + color: $font-light; + } + } } \ No newline at end of file diff --git a/client/src/components/_atoms/Button/Button.tsx b/client/src/components/_atoms/Button/Button.tsx index f53b276..d8596c8 100644 --- a/client/src/components/_atoms/Button/Button.tsx +++ b/client/src/components/_atoms/Button/Button.tsx @@ -7,7 +7,8 @@ type ButtonStyles = | "invite" | "button" | "role-select-left" - | "role-select-right"; + | "role-select-right" + | "thin-btn-light"; /** To add a new type: * 1. Add the type in ButtonStyles up above * 2. Add the type in Button.scss (list $btn-types) @@ -42,7 +43,9 @@ export default function Button({ ? "btn-role-select-left" : style === "role-select-right" ? "btn-role-select-right" - : "btn-light"; + : style === "thin-btn-light" + ? "thin-btn-light" + : "btn-light"; if (href) { return ( diff --git a/client/src/components/_molecules/Card/IdCard.scss b/client/src/components/_molecules/Card/IdCard.scss index 86b74ac..97161d4 100644 --- a/client/src/components/_molecules/Card/IdCard.scss +++ b/client/src/components/_molecules/Card/IdCard.scss @@ -14,7 +14,7 @@ margin: 0.2rem; &[data-dog] { - background-color: #1B8095 + background-color: $blue-100; } &__image { @@ -70,7 +70,7 @@ } &--link { - grid-area: 1 / 3 / 2 / 4; + grid-area: 1 / 2 / 2 / 4; align-self: end; justify-self: end; padding: min(2vw, 2rem) 1vw; diff --git a/client/src/components/_molecules/Card/IdCard.tsx b/client/src/components/_molecules/Card/IdCard.tsx index 002310e..e786288 100644 --- a/client/src/components/_molecules/Card/IdCard.tsx +++ b/client/src/components/_molecules/Card/IdCard.tsx @@ -1,6 +1,7 @@ import "./IdCard.scss"; import type { Dog } from "@/types/Dog"; import type { Owner } from "@/types/User"; +import { useUser } from "@/hooks/useUser"; type IdCardProps = { type: "dog" | "owner"; @@ -11,22 +12,28 @@ export default function IdCard({ type, data }: IdCardProps) { const isDog = type === "dog"; const dogData = data as Dog; const ownerData = data as Owner; + const { role } = useUser(); const getCardInfo = () => { if (isDog) { return { - image: dogData.picture, + image: + `${import.meta.env.VITE_API_URL}${dogData.picture}` || + `${import.meta.env.VITE_API_URL}/upload/images/defaultdog.jpg`, imageAlt: `${dogData.name} le chien`, title: dogData.name, subtitle: dogData.breed, age: `${dogData.getAge} ans`, info: "informations complémentaires", - link: `/dog/${dogData.id}`, + link: + role === "owner" + ? `/owner/my-dogs/profile/${dogData.id}` + : `/dog/${dogData.id}`, }; } return { - image: ownerData.avatar, + image: `${import.meta.env.VITE_API_URL}${ownerData.avatar}`, imageAlt: `Avatar de ${ownerData.firstname} ${ownerData.lastname}`, title: `${ownerData.firstname} ${ownerData.lastname}`, subtitle: ownerData.email, diff --git a/client/src/graphQL/queries/dog.ts b/client/src/graphQL/queries/dog.ts new file mode 100644 index 0000000..e0fd817 --- /dev/null +++ b/client/src/graphQL/queries/dog.ts @@ -0,0 +1,13 @@ +import { gql } from "@apollo/client"; + +export const GET_ALL_DOGS_BY_OWNER_ID = gql` +query GetAllDogsByOwnerId($ownerId: Float!) { + getAllDogsByOwnerId(ownerId: $ownerId) { + breed + getAge + id + name + picture + } + } +`; diff --git a/client/src/layouts/Dashboard/DashSideBar.tsx b/client/src/layouts/Dashboard/DashSideBar.tsx index 5d84f9a..1099623 100644 --- a/client/src/layouts/Dashboard/DashSideBar.tsx +++ b/client/src/layouts/Dashboard/DashSideBar.tsx @@ -8,12 +8,14 @@ import { Paw } from "@/assets/icons/paw"; import { Exit } from "@/assets/icons/exit"; import { useAuth } from "@/hooks/useAuth"; +import { useUser } from "@/hooks/useUser"; import { useState } from "react"; const DashSideBar = () => { const location = useLocation(); const { logout } = useAuth(); - const [userRole] = useState<"trainer" | "owner">("trainer"); + const { role } = useUser(); + const [userRole] = useState<"trainer" | "owner">(role || "trainer"); const isActive = (path: string) => location.pathname.includes(path) diff --git a/client/src/main.tsx b/client/src/main.tsx index 52e30db..388a669 100644 --- a/client/src/main.tsx +++ b/client/src/main.tsx @@ -38,6 +38,7 @@ import TestFileUpload from "./components/TestFileUpload.tsx"; // FIXME: delete import TestME from "./components/TestME.tsx"; +import MyDogList from "./pages/Owner/Dogs/DogList/MyDogList.tsx"; const router = createBrowserRouter([ { @@ -115,7 +116,7 @@ const router = createBrowserRouter([ children: [ { index: true, - element:

my-dogs List

, + element: , }, { path: "new", diff --git a/client/src/pages/Owner/Dogs/DogList/MyDogList.scss b/client/src/pages/Owner/Dogs/DogList/MyDogList.scss new file mode 100644 index 0000000..3c929b3 --- /dev/null +++ b/client/src/pages/Owner/Dogs/DogList/MyDogList.scss @@ -0,0 +1,69 @@ +@import "@style"; + + +.myDogList { + display: flex; + flex-direction: column; + align-items: flex-start; + max-width: max-content; + min-height: calc(100vh - $dashHeaderHeight - $dashInlinePadding); + gap: 2rem; + + @media (width < $mobile) { + min-height: calc(100vh - $dashHeaderHeight - $dashHeaderHeight); + } + + &::after { + content: ''; + width: 30%; + aspect-ratio: 1; + position: fixed; + z-index: -1; + bottom: 0; + right: 2rem; + background: bottom right url(../../../../assets/illustrations/chien-high-five-proprietaire-canape.png) no-repeat; + background-size: 30vw; + + @media (width < $tablet) { + display: none; + } + } + + &__title { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + font-weight: $extraBold; + font-size: clamp(1rem , 3vw , $m); + + &--count { + font-weight: $semiBold; + font-size: clamp(1rem , 2vw , $s); + background-color: $beige-200; + width: calc(clamp(1rem , 2vw , $s)* 2 ); + aspect-ratio: 1; + border-radius: 50%; + display: flex; + justify-content: center; + align-items: center; + } + } + + &__list { + display: flex; + flex-direction: column; + gap: 1rem; + margin-bottom: 2rem; + + } + + &__button { + margin: 0 auto 0 auto; + + @media (width < $mobile) { + margin: auto auto 0 auto; + } + } + +} \ No newline at end of file diff --git a/client/src/pages/Owner/Dogs/DogList/MyDogList.tsx b/client/src/pages/Owner/Dogs/DogList/MyDogList.tsx new file mode 100644 index 0000000..f699078 --- /dev/null +++ b/client/src/pages/Owner/Dogs/DogList/MyDogList.tsx @@ -0,0 +1,49 @@ +import "./MyDogList.scss"; +import Button from "@/components/_atoms/Button/Button"; +import IdCard from "@/components/_molecules/Card/IdCard"; +import { GET_ALL_DOGS_BY_OWNER_ID } from "@/graphQL/queries/dog"; + +import { useQuery } from "@apollo/client"; +import { useUser } from "@/hooks/useUser"; + +import type { Dog } from "@/types/Dog"; + +function MyDogList() { + const { user } = useUser(); + const { data, loading, error } = useQuery(GET_ALL_DOGS_BY_OWNER_ID, { + variables: { + ownerId: user?.id ? Number(user.id) : null, + }, + skip: !user?.id, + }); + + const dogs = data?.getAllDogsByOwnerId || []; + + if (loading) { + return

loading...

; + } + if (error) { + return

{error.message}

; + } + + return ( +
+
+

Mes chiens

+ {dogs.length} +
+
+ {dogs.map((dog: Dog) => ( + + ))} +
+ + + +
+ ); +} + +export default MyDogList; From d711542857bdd3c13f6a99dd4872844c547184df Mon Sep 17 00:00:00 2001 From: Florian Schaessens Date: Sat, 8 Feb 2025 17:20:32 +0100 Subject: [PATCH 02/15] update Form molecule --- .../src/components/_molecules/Form/Form.scss | 52 ++++++++++++++----- .../src/components/_molecules/Form/Form.tsx | 6 ++- .../src/pages/Owner/Dogs/DogForm/DogForm.scss | 0 .../src/pages/Owner/Dogs/DogForm/DogForm.tsx | 0 4 files changed, 45 insertions(+), 13 deletions(-) create mode 100644 client/src/pages/Owner/Dogs/DogForm/DogForm.scss create mode 100644 client/src/pages/Owner/Dogs/DogForm/DogForm.tsx diff --git a/client/src/components/_molecules/Form/Form.scss b/client/src/components/_molecules/Form/Form.scss index 0d7da23..b4b2060 100644 --- a/client/src/components/_molecules/Form/Form.scss +++ b/client/src/components/_molecules/Form/Form.scss @@ -1,5 +1,16 @@ @import "@style"; +$form-style: ( + 'dark-blue': ( + bg: $blue-400, + text: $font-light + ), + 'beige': ( + bg: $beige-200, + text: $font-dark + ) +); + .form { display: flex; flex-direction: column; @@ -11,29 +22,46 @@ width: fit-content; margin: auto; - h2 { - color: $font-light; + @each $style, $colors in $form-style { + &--#{$style} { + background-color: map-get($colors, bg); + color: map-get($colors, text); + + h2 { + color: map-get($colors, text); + } + + .form__otherFields { + color: map-get($colors, text); + } + + .introductiveText { + color: map-get($colors, text); + } + } + } + + h2 { font-weight: $semiBold; font-size: $xl; margin: 0; padding: 0; - } - - .form__buttons { + } + + .form__buttons { display: flex; flex-direction: row; gap: 0.5rem; justify-content: flex-end; - } - - .form__otherFields { - color: $font-light; + } + + .form__otherFields { & * { - margin: 0; - padding: 0; + margin: 0; + padding: 0; } + } } -} .introductiveText { color: $font-light; diff --git a/client/src/components/_molecules/Form/Form.tsx b/client/src/components/_molecules/Form/Form.tsx index 10192fa..7964a96 100644 --- a/client/src/components/_molecules/Form/Form.tsx +++ b/client/src/components/_molecules/Form/Form.tsx @@ -3,12 +3,16 @@ import "./Form.scss"; import Button from "@/components/_atoms/Button/Button"; import TextInput from "@/components/_atoms/Inputs/TextInput/TextInput"; +type FormStyles = "dark-blue" | "beige"; + export default function Form({ + style = "dark-blue", className, title, children, onSubmit, }: { + style?: FormStyles; className?: string; title: string; children: React.ReactNode; @@ -40,7 +44,7 @@ export default function Form({ // -- End children filtering return ( -
+

{title}

{introductiveText}
{inputs}
diff --git a/client/src/pages/Owner/Dogs/DogForm/DogForm.scss b/client/src/pages/Owner/Dogs/DogForm/DogForm.scss new file mode 100644 index 0000000..e69de29 diff --git a/client/src/pages/Owner/Dogs/DogForm/DogForm.tsx b/client/src/pages/Owner/Dogs/DogForm/DogForm.tsx new file mode 100644 index 0000000..e69de29 From 4f3c9e141d96e75cd19548954a847814ab3491b5 Mon Sep 17 00:00:00 2001 From: Florian Schaessens Date: Sun, 9 Feb 2025 20:55:51 +0100 Subject: [PATCH 03/15] add dogForm conmponent --- client/src/assets/icons/upload.svg | 4 ++ .../_atoms/Inputs/TextInput/TextInput.scss | 6 ++ .../_atoms/Inputs/TextInput/TextInput.tsx | 36 +++++++++- .../src/components/_molecules/Card/IdCard.tsx | 2 +- client/src/graphQL/queries/dog.ts | 1 + client/src/main.tsx | 3 +- .../src/pages/Owner/Dogs/DogForm/DogForm.scss | 41 +++++++++++ .../src/pages/Owner/Dogs/DogForm/DogForm.tsx | 69 +++++++++++++++++++ .../pages/Owner/Dogs/DogList/MyDogList.scss | 23 +------ client/src/styles/_mixins.scss | 26 +++++++ client/src/types/Dog.d.ts | 1 + server/src/entities/Dog.ts | 8 +++ server/src/resolvers/DogResolver.ts | 15 +++- 13 files changed, 208 insertions(+), 27 deletions(-) create mode 100644 client/src/assets/icons/upload.svg diff --git a/client/src/assets/icons/upload.svg b/client/src/assets/icons/upload.svg new file mode 100644 index 0000000..f86892b --- /dev/null +++ b/client/src/assets/icons/upload.svg @@ -0,0 +1,4 @@ + + + + diff --git a/client/src/components/_atoms/Inputs/TextInput/TextInput.scss b/client/src/components/_atoms/Inputs/TextInput/TextInput.scss index fd551fe..6d6bf35 100644 --- a/client/src/components/_atoms/Inputs/TextInput/TextInput.scss +++ b/client/src/components/_atoms/Inputs/TextInput/TextInput.scss @@ -39,6 +39,12 @@ } &__light { + + label { + font-weight: $bold; + color: $blue-400; + } + input, textarea { border: 2px solid $beige-300; diff --git a/client/src/components/_atoms/Inputs/TextInput/TextInput.tsx b/client/src/components/_atoms/Inputs/TextInput/TextInput.tsx index cbc956b..7f3beb5 100644 --- a/client/src/components/_atoms/Inputs/TextInput/TextInput.tsx +++ b/client/src/components/_atoms/Inputs/TextInput/TextInput.tsx @@ -14,14 +14,19 @@ type TextInputTypes = | "SIRET" | "company_name" | "telephone" - | "description"; + | "description" + | "name" + | "age" + | "breed" + | "info"; interface TextInputProps { type?: TextInputTypes; required?: boolean; passwordRef?: React.RefObject; isLogin?: boolean; - inputType?: "input" | "textarea"; + inputType?: "input" | "textarea" | "number"; + min?: number; style?: "dark" | "light"; label?: string; placeholder?: string; @@ -76,6 +81,22 @@ const TEXT_INPUT_CONFIG: Record< mappedLabel: "Description", mappedPlaceholder: "Entrez votre description", }, + name: { + mappedLabel: "Nom de mon chien", + mappedPlaceholder: "Entrez le nom de votre chien", + }, + age: { + mappedLabel: "Âge de mon chien", + mappedPlaceholder: "Entrez l’âge de votre chien", + }, + breed: { + mappedLabel: "Race de mon chien", + mappedPlaceholder: "Entrez la race de votre chien", + }, + info: { + mappedLabel: "Informations complémentaires", + mappedPlaceholder: "Entrez un commentaire sur votre chien", + }, }; // forwardRef allows us to use useRef in the component calling this one @@ -155,7 +176,16 @@ const TextInput = React.forwardRef< } - type={isPasswordField ? (showPassword ? "text" : "password") : type} + type={ + isPasswordField + ? showPassword + ? "text" + : "password" + : inputType === "number" + ? "number" + : "text" + } + min={type === "age" ? "0" : undefined} placeholder={mappedPlaceholder} required={required} onBlur={validateInput} diff --git a/client/src/components/_molecules/Card/IdCard.tsx b/client/src/components/_molecules/Card/IdCard.tsx index e786288..9054e39 100644 --- a/client/src/components/_molecules/Card/IdCard.tsx +++ b/client/src/components/_molecules/Card/IdCard.tsx @@ -24,7 +24,7 @@ export default function IdCard({ type, data }: IdCardProps) { title: dogData.name, subtitle: dogData.breed, age: `${dogData.getAge} ans`, - info: "informations complémentaires", + info: dogData.info, link: role === "owner" ? `/owner/my-dogs/profile/${dogData.id}` diff --git a/client/src/graphQL/queries/dog.ts b/client/src/graphQL/queries/dog.ts index e0fd817..f59f4be 100644 --- a/client/src/graphQL/queries/dog.ts +++ b/client/src/graphQL/queries/dog.ts @@ -8,6 +8,7 @@ query GetAllDogsByOwnerId($ownerId: Float!) { id name picture + info } } `; diff --git a/client/src/main.tsx b/client/src/main.tsx index 38a59a1..887f016 100644 --- a/client/src/main.tsx +++ b/client/src/main.tsx @@ -41,6 +41,7 @@ import TestFileUpload from "./components/TestFileUpload.tsx"; // FIXME: delete import TestME from "./components/TestME.tsx"; import MyDogList from "./pages/Owner/Dogs/DogList/MyDogList.tsx"; +import DogForm from "./pages/Owner/Dogs/DogForm/DogForm.tsx"; const router = createBrowserRouter([ { @@ -122,7 +123,7 @@ const router = createBrowserRouter([ }, { path: "new", - element:

my-dogs/new

, + element: , }, { path: "profile/:id", diff --git a/client/src/pages/Owner/Dogs/DogForm/DogForm.scss b/client/src/pages/Owner/Dogs/DogForm/DogForm.scss index e69de29..cf365ad 100644 --- a/client/src/pages/Owner/Dogs/DogForm/DogForm.scss +++ b/client/src/pages/Owner/Dogs/DogForm/DogForm.scss @@ -0,0 +1,41 @@ +@import "@style"; + + +.dogForm { + @include dogBackground; + + &__form { + @include dashContentArea(-2rem); + margin:2rem 1rem; + max-width: max-content; + display: flex; + flex-direction: column; + + &__title { + display: flex; + align-items: center; + + &--upload { + min-width: clamp(64px , 10%, 96px); + aspect-ratio: 1; + background: url(../../../../assets/icons/upload.svg)center no-repeat, $blue-400; + background-size: 30%; + border-radius: 50%; + cursor: pointer; + } + + &--intro { + margin-left: 2rem; + } + } + + } + + &__button { + margin: 2rem auto 0 auto; + + @media (width < $mobile) { + margin: auto auto 0 auto; + } + } +} \ No newline at end of file diff --git a/client/src/pages/Owner/Dogs/DogForm/DogForm.tsx b/client/src/pages/Owner/Dogs/DogForm/DogForm.tsx index e69de29..f73590b 100644 --- a/client/src/pages/Owner/Dogs/DogForm/DogForm.tsx +++ b/client/src/pages/Owner/Dogs/DogForm/DogForm.tsx @@ -0,0 +1,69 @@ +import "./DogForm.scss"; +import { useRef, useEffect } from "react"; +import TextInput from "@/components/_atoms/Inputs/TextInput/TextInput"; +import Button from "@/components/_atoms/Button/Button"; +import type { Dog } from "@/types/Dog"; + +interface dogFormProps { + mode: "create" | "update"; + initialData?: Dog | null; +} + +export default function DogForm({ + mode = "create", + initialData = null, +}: dogFormProps) { + const pictureRef = useRef(null); + const nameRef = useRef(null); + const breedRef = useRef(null); + const ageRef = useRef(null); + const infoRef = useRef(null); + + useEffect(() => { + if (initialData) { + if (nameRef.current) nameRef.current.value = initialData.name || ""; + if (breedRef.current) breedRef.current.value = initialData.breed || ""; + if (ageRef.current) + ageRef.current.value = (initialData.getAge || "0").toString(); + if (infoRef.current) infoRef.current.value = initialData.info || ""; + if (pictureRef.current) + pictureRef.current.value = initialData.picture || ""; + } + }, [initialData]); + + const formTitle = + mode === "create" ? "Ajout d'un chien" : "Profil de mon chien"; + const formSubtitle = + mode === "create" + ? "Renseignez les informations de votre nouveau compagnon" + : "Vous pouvez ici changer toutes les informations liées à votre compagnon"; + const buttonText = + mode === "create" + ? "Valider l'ajout de mon nouveau chien" + : "Sauvegarder les modifications"; + + return ( +
+ +
+
+
{/* Upload */}
+
+

{formTitle}

+

{formSubtitle}

+
+
+ +
+ + + + + + + +
+ ); +} diff --git a/client/src/pages/Owner/Dogs/DogList/MyDogList.scss b/client/src/pages/Owner/Dogs/DogList/MyDogList.scss index 3c929b3..24a1552 100644 --- a/client/src/pages/Owner/Dogs/DogList/MyDogList.scss +++ b/client/src/pages/Owner/Dogs/DogList/MyDogList.scss @@ -2,33 +2,14 @@ .myDogList { + @include dashContentArea; + @include dogBackground; display: flex; flex-direction: column; align-items: flex-start; max-width: max-content; - min-height: calc(100vh - $dashHeaderHeight - $dashInlinePadding); gap: 2rem; - @media (width < $mobile) { - min-height: calc(100vh - $dashHeaderHeight - $dashHeaderHeight); - } - - &::after { - content: ''; - width: 30%; - aspect-ratio: 1; - position: fixed; - z-index: -1; - bottom: 0; - right: 2rem; - background: bottom right url(../../../../assets/illustrations/chien-high-five-proprietaire-canape.png) no-repeat; - background-size: 30vw; - - @media (width < $tablet) { - display: none; - } - } - &__title { display: flex; align-items: center; diff --git a/client/src/styles/_mixins.scss b/client/src/styles/_mixins.scss index 9211f2d..1632f8a 100644 --- a/client/src/styles/_mixins.scss +++ b/client/src/styles/_mixins.scss @@ -5,6 +5,14 @@ padding: 1.2vh 1.5vw; } +@mixin dashContentArea($offset : 0) { + min-height: calc(100vh - $dashHeaderHeight - $dashInlinePadding + #{$offset}); + + @media (width < $mobile) { + min-height: calc(100vh - $dashHeaderHeight - $dashHeaderHeight + #{$offset}); + } +} + @mixin title { color: $blue-400; font-weight: $semiBold; @@ -22,6 +30,24 @@ text-align: center; } +@mixin dogBackground { + &::after { + content: ''; + width: 30%; + aspect-ratio: 1; + position: fixed; + z-index: -1; + bottom: 0; + right: 2rem; + background: bottom right url(../../../../assets/illustrations/chien-high-five-proprietaire-canape.png) no-repeat; + background-size: 30vw; + + @media (width < $tablet) { + display: none; + } + } +} + .hidden__ { &mobile { @media screen and (max-width: $mobile) { diff --git a/client/src/types/Dog.d.ts b/client/src/types/Dog.d.ts index 7edd8fe..9f68cd5 100644 --- a/client/src/types/Dog.d.ts +++ b/client/src/types/Dog.d.ts @@ -4,5 +4,6 @@ export interface Dog { birthDate: Date; breed: string; picture: string; + info: string; getAge?: number; } diff --git a/server/src/entities/Dog.ts b/server/src/entities/Dog.ts index bcc4802..23fe604 100644 --- a/server/src/entities/Dog.ts +++ b/server/src/entities/Dog.ts @@ -42,6 +42,12 @@ export class Dog { @Field({ nullable: true }) picture?: string; + @Column({ + nullable: true, + }) + @Field({ nullable: true }) + info?: string; + @ManyToOne( () => Owner, (owner) => owner.dogs, @@ -74,11 +80,13 @@ export class Dog { birthDate = new Date(), breed = "", picture = "/upload/images/defaultdog.jpg", + info = "Informations complémentaires", ) { this.owner = owner; this.name = name; this.birthDate = birthDate; this.breed = breed; this.picture = picture; + this.info = info; } } diff --git a/server/src/resolvers/DogResolver.ts b/server/src/resolvers/DogResolver.ts index fb91312..cdefb03 100644 --- a/server/src/resolvers/DogResolver.ts +++ b/server/src/resolvers/DogResolver.ts @@ -34,6 +34,7 @@ export class DogResolver { @Arg("name", { nullable: true }) name?: string, @Arg("birthDate", { nullable: true }) birthDate?: Date, @Arg("breed", { nullable: true }) breed?: string, + @Arg("info", { nullable: true }) info?: string, @Arg("picture", () => GraphQLUpload, { nullable: true }) picture?: Promise, ): Promise { @@ -49,7 +50,7 @@ export class DogResolver { picturePath = await fileUploader.addProfilePicture(picture); } - const dog = new Dog(owner, name, birthDate, breed, picturePath); + const dog = new Dog(owner, name, birthDate, breed, picturePath, info); return await dogRepository.save(dog); } @@ -60,6 +61,9 @@ export class DogResolver { @Arg("name", { nullable: true }) name?: string, @Arg("birthDate", { nullable: true }) birthDate?: Date, @Arg("breed", { nullable: true }) breed?: string, + @Arg("info", { nullable: true }) info?: string, + @Arg("picture", () => GraphQLUpload, { nullable: true }) + picture?: Promise, ): Promise { const dog = await dogRepository.findOne({ where: { @@ -73,9 +77,18 @@ export class DogResolver { throw new Error(`Dog with ID ${dogId} not found`); } + let picturePath = "/upload/images/defaultdog.jpg"; + if (name) dog.name = name; if (birthDate) dog.birthDate = birthDate; if (breed) dog.breed = breed; + if (info) dog.info = info; + + if (picture) { + const fileUploader = new FileUploadResolver(); + picturePath = await fileUploader.addProfilePicture(picture); + dog.picture = picturePath; + } return await dogRepository.save(dog); } From 4f9adf4ed584ed314d4db407a9003916be60078e Mon Sep 17 00:00:00 2001 From: Florian Schaessens Date: Sun, 9 Feb 2025 22:25:58 +0100 Subject: [PATCH 04/15] end Create Dog owner --- client/src/components/TestFileUpload.tsx | 10 +-- .../_atoms/Inputs/TextInput/TextInput.tsx | 16 ++-- .../components/_molecules/Card/IdCard.scss | 6 +- .../src/components/_molecules/Card/IdCard.tsx | 2 +- client/src/graphQL/mutations/dogs.ts | 23 ++++++ client/src/hooks/useFileUpload.ts | 17 +++++ .../src/pages/Owner/Dogs/DogForm/DogForm.scss | 13 ++++ .../src/pages/Owner/Dogs/DogForm/DogForm.tsx | 76 ++++++++++++++++--- 8 files changed, 135 insertions(+), 28 deletions(-) create mode 100644 client/src/hooks/useFileUpload.ts diff --git a/client/src/components/TestFileUpload.tsx b/client/src/components/TestFileUpload.tsx index f60b5f6..aec9519 100644 --- a/client/src/components/TestFileUpload.tsx +++ b/client/src/components/TestFileUpload.tsx @@ -1,16 +1,10 @@ -import { useState } from "react"; import { useMutation } from "@apollo/client"; import { DOG_PROFIL_PICTURE } from "@/graphQL/mutations/dogs"; +import { useFileUpload } from "@/hooks/useFileUpload"; function TestFileUpload() { const [uploadDogPicture] = useMutation(DOG_PROFIL_PICTURE); - const [selectedFile, setSelectedFile] = useState(null); - - const handleChange = (event: React.ChangeEvent) => { - if (event.target.files?.[0]) { - setSelectedFile(event.target.files[0]); - } - }; + const { selectedFile, handleChange } = useFileUpload(); const handleSend = async () => { if (!selectedFile) { diff --git a/client/src/components/_atoms/Inputs/TextInput/TextInput.tsx b/client/src/components/_atoms/Inputs/TextInput/TextInput.tsx index 7f3beb5..5502312 100644 --- a/client/src/components/_atoms/Inputs/TextInput/TextInput.tsx +++ b/client/src/components/_atoms/Inputs/TextInput/TextInput.tsx @@ -16,7 +16,7 @@ type TextInputTypes = | "telephone" | "description" | "name" - | "age" + | "birthDate" | "breed" | "info"; @@ -25,8 +25,7 @@ interface TextInputProps { required?: boolean; passwordRef?: React.RefObject; isLogin?: boolean; - inputType?: "input" | "textarea" | "number"; - min?: number; + inputType?: "input" | "textarea" | "date"; style?: "dark" | "light"; label?: string; placeholder?: string; @@ -85,9 +84,9 @@ const TEXT_INPUT_CONFIG: Record< mappedLabel: "Nom de mon chien", mappedPlaceholder: "Entrez le nom de votre chien", }, - age: { - mappedLabel: "Âge de mon chien", - mappedPlaceholder: "Entrez l’âge de votre chien", + birthDate: { + mappedLabel: "Date de naissance de mon chien", + mappedPlaceholder: "Sélectionnez la date de naissance", }, breed: { mappedLabel: "Race de mon chien", @@ -181,11 +180,10 @@ const TextInput = React.forwardRef< ? showPassword ? "text" : "password" - : inputType === "number" - ? "number" + : inputType === "date" + ? "date" : "text" } - min={type === "age" ? "0" : undefined} placeholder={mappedPlaceholder} required={required} onBlur={validateInput} diff --git a/client/src/components/_molecules/Card/IdCard.scss b/client/src/components/_molecules/Card/IdCard.scss index 97161d4..3793762 100644 --- a/client/src/components/_molecules/Card/IdCard.scss +++ b/client/src/components/_molecules/Card/IdCard.scss @@ -19,7 +19,10 @@ &__image { grid-area: 1 / 1 / 2 / 2; - object-fit: cover; + object-fit: contain; + max-width: 250px; + width: 100%; + aspect-ratio: 1; &--round { @include avatar; @@ -30,6 +33,7 @@ grid-area: 1 / 2 / 2 / 4; padding: min(1rem, 1vw); color: $blue-500; + text-overflow: ellipsis; h3 { margin: 0.5rem 0; diff --git a/client/src/components/_molecules/Card/IdCard.tsx b/client/src/components/_molecules/Card/IdCard.tsx index 9054e39..e5b9fa5 100644 --- a/client/src/components/_molecules/Card/IdCard.tsx +++ b/client/src/components/_molecules/Card/IdCard.tsx @@ -49,7 +49,7 @@ export default function IdCard({ type, data }: IdCardProps) { {imageAlt}
diff --git a/client/src/graphQL/mutations/dogs.ts b/client/src/graphQL/mutations/dogs.ts index f027300..05ccde6 100644 --- a/client/src/graphQL/mutations/dogs.ts +++ b/client/src/graphQL/mutations/dogs.ts @@ -5,3 +5,26 @@ export const DOG_PROFIL_PICTURE = gql` uploadDogProfilePicture(file: $file, dogId: $dogId) } `; + +export const CREATE_DOG = gql` +mutation CreateDog($ownerId: Float!, $picture: Upload, $info: String, $breed: String, $birthDate: DateTimeISO, $name: String) { + createDog(ownerId: $ownerId, picture: $picture, info: $info, breed: $breed, birthDate: $birthDate, name: $name) { + name + picture + getAge + breed + info + } +} +`; +export const UPDATE_DOG = gql` +mutation UpdateDog($ownerId: Float!, $dogId: Float!, $name: String, $birthDate: DateTimeISO, $breed: String, $info: String, $picture: Upload) { + updateDog(ownerId: $ownerId, dogId: $dogId, name: $name, birthDate: $birthDate, breed: $breed, info: $info, picture: $picture) { + breed + getAge + info + name + picture + } +} +`; diff --git a/client/src/hooks/useFileUpload.ts b/client/src/hooks/useFileUpload.ts new file mode 100644 index 0000000..7a5731f --- /dev/null +++ b/client/src/hooks/useFileUpload.ts @@ -0,0 +1,17 @@ +import { useState } from "react"; + +export const useFileUpload = () => { + const [selectedFile, setSelectedFile] = useState(null); + + const handleChange = (event: React.ChangeEvent) => { + if (event.target.files?.[0]) { + setSelectedFile(event.target.files[0]); + } + }; + + return { + selectedFile, + setSelectedFile, + handleChange, + }; +}; diff --git a/client/src/pages/Owner/Dogs/DogForm/DogForm.scss b/client/src/pages/Owner/Dogs/DogForm/DogForm.scss index cf365ad..1e0c28f 100644 --- a/client/src/pages/Owner/Dogs/DogForm/DogForm.scss +++ b/client/src/pages/Owner/Dogs/DogForm/DogForm.scss @@ -22,6 +22,19 @@ background-size: 30%; border-radius: 50%; cursor: pointer; + + &:focus { + outline: none; + border-radius: 50%; + } + + &:hover { + filter: brightness(1.5); + } + + input { + display: none; + } } &--intro { diff --git a/client/src/pages/Owner/Dogs/DogForm/DogForm.tsx b/client/src/pages/Owner/Dogs/DogForm/DogForm.tsx index f73590b..72159a2 100644 --- a/client/src/pages/Owner/Dogs/DogForm/DogForm.tsx +++ b/client/src/pages/Owner/Dogs/DogForm/DogForm.tsx @@ -3,8 +3,12 @@ import { useRef, useEffect } from "react"; import TextInput from "@/components/_atoms/Inputs/TextInput/TextInput"; import Button from "@/components/_atoms/Button/Button"; import type { Dog } from "@/types/Dog"; +import { useFileUpload } from "@/hooks/useFileUpload"; +import { useMutation } from "@apollo/client"; +import { useUser } from "@/hooks/useUser"; +import { CREATE_DOG, UPDATE_DOG } from "@/graphQL/mutations/dogs"; -interface dogFormProps { +interface DogFormProps { mode: "create" | "update"; initialData?: Dog | null; } @@ -12,25 +16,29 @@ interface dogFormProps { export default function DogForm({ mode = "create", initialData = null, -}: dogFormProps) { +}: DogFormProps) { const pictureRef = useRef(null); const nameRef = useRef(null); const breedRef = useRef(null); - const ageRef = useRef(null); + const birthDateRef = useRef(null); const infoRef = useRef(null); + const { handleChange, selectedFile } = useFileUpload(); + const { user } = useUser(); useEffect(() => { if (initialData) { if (nameRef.current) nameRef.current.value = initialData.name || ""; if (breedRef.current) breedRef.current.value = initialData.breed || ""; - if (ageRef.current) - ageRef.current.value = (initialData.getAge || "0").toString(); + if (birthDateRef.current) + birthDateRef.current.value = (initialData.getAge || "0").toString(); if (infoRef.current) infoRef.current.value = initialData.info || ""; if (pictureRef.current) pictureRef.current.value = initialData.picture || ""; } }, [initialData]); + const query = mode === "create" ? CREATE_DOG : UPDATE_DOG; + const [selectedQuery] = useMutation(query); const formTitle = mode === "create" ? "Ajout d'un chien" : "Profil de mon chien"; const formSubtitle = @@ -42,12 +50,62 @@ export default function DogForm({ ? "Valider l'ajout de mon nouveau chien" : "Sauvegarder les modifications"; + const handleSubmit = async (event: React.FormEvent) => { + event.preventDefault(); + + try { + const birthDate = birthDateRef.current?.value + ? new Date(birthDateRef.current.value).toISOString() + : undefined; + + const variables = { + ownerId: Number(user?.id), + name: nameRef.current?.value, + breed: breedRef.current?.value, + birthDate, + info: infoRef.current?.value, + picture: selectedFile, + ...(mode === "update" && { dogId: initialData?.id }), + }; + + const { data } = await selectedQuery({ + variables, + }); + + console.info(data); + + window.location.href = "/owner/my-dogs"; + } catch (error) { + console.error("Error saving dog:", error); + } + }; + + const handleKeyPress = (event: React.KeyboardEvent) => { + if (event.key === "Enter" || event.key === " ") { + event.preventDefault(); + pictureRef.current?.click(); + } + }; + return (
-
+
-
{/* Upload */}
+
pictureRef.current?.click()} + onKeyDown={handleKeyPress} + role="button" + tabIndex={0} + > + +

{formTitle}

{formSubtitle}

@@ -56,10 +114,10 @@ export default function DogForm({
- + - From b8aa41ccb644889a82abd107864676abdb5370d2 Mon Sep 17 00:00:00 2001 From: Florian Schaessens Date: Mon, 10 Feb 2025 08:13:16 +0100 Subject: [PATCH 05/15] End Update Dog --- .../components/_molecules/Card/IdCard.scss | 2 +- client/src/graphQL/queries/dog.ts | 13 +++++ client/src/hooks/useAuth.ts | 6 +++ client/src/main.tsx | 11 ++-- .../pages/Owner/Dogs/CreateDog/CreateDog.tsx | 5 ++ .../src/pages/Owner/Dogs/DogForm/DogForm.tsx | 53 +++++++++++++------ .../pages/Owner/Dogs/DogList/MyDogList.tsx | 17 ++++-- .../pages/Owner/Dogs/UpdateDog/UpdateDog.tsx | 20 +++++++ 8 files changed, 101 insertions(+), 26 deletions(-) create mode 100644 client/src/pages/Owner/Dogs/CreateDog/CreateDog.tsx create mode 100644 client/src/pages/Owner/Dogs/UpdateDog/UpdateDog.tsx diff --git a/client/src/components/_molecules/Card/IdCard.scss b/client/src/components/_molecules/Card/IdCard.scss index 3793762..3073709 100644 --- a/client/src/components/_molecules/Card/IdCard.scss +++ b/client/src/components/_molecules/Card/IdCard.scss @@ -81,7 +81,7 @@ color: $blue-600; &:hover { - color: $blue-100; + color: $beige-100; } } diff --git a/client/src/graphQL/queries/dog.ts b/client/src/graphQL/queries/dog.ts index f59f4be..dfb140b 100644 --- a/client/src/graphQL/queries/dog.ts +++ b/client/src/graphQL/queries/dog.ts @@ -12,3 +12,16 @@ query GetAllDogsByOwnerId($ownerId: Float!) { } } `; + +export const GET_DOG_BY_ID = gql` +query GetDogById($getDogByIdId: Float!) { + getDogById(id: $getDogByIdId) { + breed + birthDate + id + info + name + picture + } +} +`; diff --git a/client/src/hooks/useAuth.ts b/client/src/hooks/useAuth.ts index 130b6ed..a47ebf9 100644 --- a/client/src/hooks/useAuth.ts +++ b/client/src/hooks/useAuth.ts @@ -1,5 +1,7 @@ import { useMutation } from "@apollo/client"; import { useNavigate } from "react-router-dom"; +import { useContext } from "react"; +import { AuthContext } from "@/context/AuthContext"; import { jwtDecode } from "jwt-decode"; import { LOGIN } from "@/graphQL/mutations/user"; @@ -16,6 +18,7 @@ interface LoginResponse { export const useAuth = () => { const [loginMutation, { data, loading }] = useMutation(LOGIN); const navigate = useNavigate(); + const { refetch } = useContext(AuthContext); const login = async ( email: string, @@ -36,6 +39,9 @@ export const useAuth = () => { localStorage.setItem("authToken", token); const decoded = jwtDecode(token); + + refetch(); + navigate(`/${decoded.role.toLowerCase()}`); return { diff --git a/client/src/main.tsx b/client/src/main.tsx index 887f016..b5e2dd5 100644 --- a/client/src/main.tsx +++ b/client/src/main.tsx @@ -21,6 +21,7 @@ import WelcomePageLayout from "@/layouts/WelcomePage/WelcomePageLayout.tsx"; // Components import Contact from "@/pages/WelcomePage/Contact.tsx"; +import CreateDog from "./pages/Owner/Dogs/CreateDog/CreateDog.tsx"; import CustomerId from "./pages/Trainer/Customers/CustomerId/CustomerId.tsx"; import CustomerList from "./pages/Trainer/Customers/CustomerList/CustomerList.tsx"; import DesignSystem from "@/pages/DesignSystem/DesignSystem.tsx"; @@ -30,18 +31,18 @@ import EventList from "./pages/Event/EventList/EventList.tsx"; import Homepage from "@/pages/Homepage/Homepage.tsx"; import Login from "@/pages/Login/Login.tsx"; import NewPassword from "./pages/Login/NewPassword.tsx"; +import MyDogList from "./pages/Owner/Dogs/DogList/MyDogList.tsx"; import Planning from "./pages/Planning/Planning.tsx"; import Profile from "@/pages/Profile/Profile.tsx"; import Registration from "./pages/Registration/Registration.tsx"; import ResetLink from "./pages/Login/ResetLink.tsx"; import ResetPassword from "./pages/Login/ResetPassword.tsx"; import Services from "@/pages/WelcomePage/Services.tsx"; -import TestFileUpload from "./components/TestFileUpload.tsx"; +import UpdateDog from "./pages/Owner/Dogs/UpdateDog/UpdateDog.tsx"; // FIXME: delete import TestME from "./components/TestME.tsx"; -import MyDogList from "./pages/Owner/Dogs/DogList/MyDogList.tsx"; -import DogForm from "./pages/Owner/Dogs/DogForm/DogForm.tsx"; +import TestFileUpload from "./components/TestFileUpload.tsx"; const router = createBrowserRouter([ { @@ -123,11 +124,11 @@ const router = createBrowserRouter([ }, { path: "new", - element: , + element: , }, { path: "profile/:id", - element:

my-dogs/profile/:id

, + element: , }, ], }, diff --git a/client/src/pages/Owner/Dogs/CreateDog/CreateDog.tsx b/client/src/pages/Owner/Dogs/CreateDog/CreateDog.tsx new file mode 100644 index 0000000..2226fbf --- /dev/null +++ b/client/src/pages/Owner/Dogs/CreateDog/CreateDog.tsx @@ -0,0 +1,5 @@ +import DogForm from "../DogForm/DogForm"; + +export default function CreateDog() { + return ; +} diff --git a/client/src/pages/Owner/Dogs/DogForm/DogForm.tsx b/client/src/pages/Owner/Dogs/DogForm/DogForm.tsx index 72159a2..d647656 100644 --- a/client/src/pages/Owner/Dogs/DogForm/DogForm.tsx +++ b/client/src/pages/Owner/Dogs/DogForm/DogForm.tsx @@ -6,6 +6,7 @@ import type { Dog } from "@/types/Dog"; import { useFileUpload } from "@/hooks/useFileUpload"; import { useMutation } from "@apollo/client"; import { useUser } from "@/hooks/useUser"; +import { useNavigate } from "react-router-dom"; import { CREATE_DOG, UPDATE_DOG } from "@/graphQL/mutations/dogs"; interface DogFormProps { @@ -21,20 +22,37 @@ export default function DogForm({ const nameRef = useRef(null); const breedRef = useRef(null); const birthDateRef = useRef(null); - const infoRef = useRef(null); + const infoRef = useRef(null); const { handleChange, selectedFile } = useFileUpload(); const { user } = useUser(); + const navigate = useNavigate(); useEffect(() => { - if (initialData) { - if (nameRef.current) nameRef.current.value = initialData.name || ""; - if (breedRef.current) breedRef.current.value = initialData.breed || ""; - if (birthDateRef.current) - birthDateRef.current.value = (initialData.getAge || "0").toString(); - if (infoRef.current) infoRef.current.value = initialData.info || ""; - if (pictureRef.current) - pictureRef.current.value = initialData.picture || ""; + let isComponentMounted = true; + + if (initialData && isComponentMounted) { + if (nameRef.current instanceof HTMLInputElement) { + nameRef.current.value = initialData.name || ""; + } + if (breedRef.current instanceof HTMLInputElement) { + breedRef.current.value = initialData.breed || ""; + } + if (birthDateRef.current instanceof HTMLInputElement) { + birthDateRef.current.value = initialData.birthDate + ? new Date(initialData.birthDate).toISOString().split("T")[0] + : ""; + } + if (infoRef.current instanceof HTMLTextAreaElement) { + infoRef.current.value = initialData.info || ""; + } + if (pictureRef.current instanceof HTMLInputElement) { + pictureRef.current.value = ""; + } } + + return () => { + isComponentMounted = false; + }; }, [initialData]); const query = mode === "create" ? CREATE_DOG : UPDATE_DOG; @@ -54,9 +72,9 @@ export default function DogForm({ event.preventDefault(); try { - const birthDate = birthDateRef.current?.value - ? new Date(birthDateRef.current.value).toISOString() - : undefined; + const birthDate = birthDateRef.current?.value + ? new Date(birthDateRef.current.value).toISOString() + : undefined; const variables = { ownerId: Number(user?.id), @@ -65,16 +83,19 @@ export default function DogForm({ birthDate, info: infoRef.current?.value, picture: selectedFile, - ...(mode === "update" && { dogId: initialData?.id }), + ...(mode === "update" && { dogId: Number(initialData?.id) }), }; - const { data } = await selectedQuery({ + await selectedQuery({ variables, }); - console.info(data); + const message = + mode === "create" + ? "Votre chien a été ajouté avec succès !" + : "Les modifications ont été enregistrées avec succès !"; - window.location.href = "/owner/my-dogs"; + navigate("/owner/my-dogs", { state: { message: `${message}` } }); } catch (error) { console.error("Error saving dog:", error); } diff --git a/client/src/pages/Owner/Dogs/DogList/MyDogList.tsx b/client/src/pages/Owner/Dogs/DogList/MyDogList.tsx index f699078..48e90a9 100644 --- a/client/src/pages/Owner/Dogs/DogList/MyDogList.tsx +++ b/client/src/pages/Owner/Dogs/DogList/MyDogList.tsx @@ -5,12 +5,24 @@ import { GET_ALL_DOGS_BY_OWNER_ID } from "@/graphQL/queries/dog"; import { useQuery } from "@apollo/client"; import { useUser } from "@/hooks/useUser"; +import { useLocation, useNavigate } from "react-router-dom"; +import { useEffect } from "react"; import type { Dog } from "@/types/Dog"; function MyDogList() { + const location = useLocation(); + const navigate = useNavigate(); + + useEffect(() => { + if (location.state?.message) { + alert(location.state.message); + navigate(location.pathname, { replace: true }); + } + }, [location, navigate]); + const { user } = useUser(); - const { data, loading, error } = useQuery(GET_ALL_DOGS_BY_OWNER_ID, { + const { data, loading } = useQuery(GET_ALL_DOGS_BY_OWNER_ID, { variables: { ownerId: user?.id ? Number(user.id) : null, }, @@ -22,9 +34,6 @@ function MyDogList() { if (loading) { return

loading...

; } - if (error) { - return

{error.message}

; - } return (
diff --git a/client/src/pages/Owner/Dogs/UpdateDog/UpdateDog.tsx b/client/src/pages/Owner/Dogs/UpdateDog/UpdateDog.tsx new file mode 100644 index 0000000..c2292b5 --- /dev/null +++ b/client/src/pages/Owner/Dogs/UpdateDog/UpdateDog.tsx @@ -0,0 +1,20 @@ +import DogForm from "../DogForm/DogForm"; +import { useParams } from "react-router-dom"; +import { useQuery } from "@apollo/client"; +import { GET_DOG_BY_ID } from "@/graphQL/queries/dog"; + +export default function UpdateDog() { + const { id } = useParams(); + + const { data, loading } = useQuery(GET_DOG_BY_ID, { + variables: { + getDogByIdId: Number(id), + }, + }); + + if (loading) { + return

loading...

; + } + + return ; +} From 93a5e7f6f54eb56416e61aed57908693be740e12 Mon Sep 17 00:00:00 2001 From: Florian Schaessens Date: Sat, 15 Feb 2025 10:53:56 +0100 Subject: [PATCH 06/15] fix css for google chrome --- client/src/components/_molecules/Card/IdCard.scss | 2 ++ client/src/components/_molecules/Card/IdCard.tsx | 14 ++++++++++---- client/src/pages/DesignSystem/DesignSystem.tsx | 1 + 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/client/src/components/_molecules/Card/IdCard.scss b/client/src/components/_molecules/Card/IdCard.scss index 3073709..279a8c7 100644 --- a/client/src/components/_molecules/Card/IdCard.scss +++ b/client/src/components/_molecules/Card/IdCard.scss @@ -26,6 +26,8 @@ &--round { @include avatar; + max-width: 250px; + width: 100%; } } diff --git a/client/src/components/_molecules/Card/IdCard.tsx b/client/src/components/_molecules/Card/IdCard.tsx index e5b9fa5..956f638 100644 --- a/client/src/components/_molecules/Card/IdCard.tsx +++ b/client/src/components/_molecules/Card/IdCard.tsx @@ -14,12 +14,18 @@ export default function IdCard({ type, data }: IdCardProps) { const ownerData = data as Owner; const { role } = useUser(); + const getImageUrl = (path: string) => { + if (path?.startsWith("http")) { + return path; + } + return `${import.meta.env.VITE_API_URL || ""}${path}`; + }; + const getCardInfo = () => { if (isDog) { + const dogImage = dogData.picture || "/upload/images/defaultdog.jpg"; return { - image: - `${import.meta.env.VITE_API_URL}${dogData.picture}` || - `${import.meta.env.VITE_API_URL}/upload/images/defaultdog.jpg`, + image: getImageUrl(dogImage), imageAlt: `${dogData.name} le chien`, title: dogData.name, subtitle: dogData.breed, @@ -33,7 +39,7 @@ export default function IdCard({ type, data }: IdCardProps) { } return { - image: `${import.meta.env.VITE_API_URL}${ownerData.avatar}`, + image: getImageUrl(ownerData.avatar), imageAlt: `Avatar de ${ownerData.firstname} ${ownerData.lastname}`, title: `${ownerData.firstname} ${ownerData.lastname}`, subtitle: ownerData.email, diff --git a/client/src/pages/DesignSystem/DesignSystem.tsx b/client/src/pages/DesignSystem/DesignSystem.tsx index 195a6f1..e38a3aa 100644 --- a/client/src/pages/DesignSystem/DesignSystem.tsx +++ b/client/src/pages/DesignSystem/DesignSystem.tsx @@ -14,6 +14,7 @@ export default function DesignSystem() { getAge: 3, breed: "Caniche", picture: "https://placehold.co/400x400", + info: "aime les saucisses", }; const owner = { From 50ba7166ab68f73f9a07b23d2f54704f6ac937fa Mon Sep 17 00:00:00 2001 From: Florian Schaessens Date: Wed, 19 Feb 2025 13:58:23 +0100 Subject: [PATCH 07/15] update thin button atom --- .../src/components/_atoms/Button/Button.scss | 209 +++++++++--------- .../src/components/_atoms/Button/Button.tsx | 56 +++-- .../src/pages/Owner/Dogs/DogForm/DogForm.scss | 13 +- .../src/pages/Owner/Dogs/DogForm/DogForm.tsx | 19 +- .../pages/Owner/Dogs/DogList/MyDogList.scss | 18 +- .../pages/Owner/Dogs/DogList/MyDogList.tsx | 5 +- 6 files changed, 176 insertions(+), 144 deletions(-) diff --git a/client/src/components/_atoms/Button/Button.scss b/client/src/components/_atoms/Button/Button.scss index 5735e0e..ee6768c 100644 --- a/client/src/components/_atoms/Button/Button.scss +++ b/client/src/components/_atoms/Button/Button.scss @@ -1,102 +1,107 @@ -@import "@style"; - -$btn-types: ( - "submit", - "dark", - "light", - "invite", - "role-select-left", - "role-select-right", - "thin-btn-light" -); - -.button { - all: inherit; - min-height: $btn-height; - color: $primary-light; - font-weight: $semiBold; - border-radius: $atoms-radius; - padding: 1.25rem; - padding-inline: 3.5rem; - margin: 0.2rem; - font-size: $s; - background-color: $blue-200; - text-align: center; - cursor: pointer; - border: none; - display: inline-block; - - @each $btn-type in $btn-types { - &.btn-#{$btn-type} { - color: $font-light; - } - } - - &.btn-submit { - $background-color: $blue-200; - background-color: $background-color; - - &:hover { - background-color: lighten($background-color, 10%); - } - } - - &.btn-dark { - $background-color: $blue-500; - background-color: $background-color; - - &:hover { - background-color: lighten($background-color, 10%); - } - } - - &.btn-light { - $background-color: $blue-200; - background-color: $background-color; - - &:hover { - background-color: lighten($background-color, 10%); - } - } - - &.btn-invite { - $background-color: $blue-500; - background-color: $background-color; - - &:hover { - background-color: lighten($background-color, 10%); - } - } - - &.btn-role-select-left { - $background-color: $green-400; - background-color: $background-color; - - &:hover { - background-color: $green-300; - } - } - - &.btn-role-select-right { - $background-color: $orange-500; - background-color: $background-color; - - &:hover { - background-color: $orange-400; - } - } - - &.thin-btn-light { - $background-color: $orange-400; - background-color: $orange-400; - padding: 0.8rem; - padding-inline: min(5rem, 10vw); - border-radius: 5px; - box-shadow: (0 3px 5px 5px #ad998533) ; - - &:hover { - background-color: lighten($background-color, 5%); - color: $font-light; - } - } -} \ No newline at end of file +@import "@style"; + +$btn-types: ( + "submit", + "dark", + "light", + "invite", + "role-select-left", + "role-select-right", + "thin-btn-light" +); + +$colors: ( + "orange": $orange-400, + "blue": $blue-100, + "green": $green-400, +); + +.button { + all: inherit; + min-height: $btn-height; + color: $primary-light; + font-weight: $semiBold; + border-radius: $atoms-radius; + padding: 1.25rem; + padding-inline: 3.5rem; + margin: 0.2rem; + font-size: $s; + background-color: $blue-200; + text-align: center; + cursor: pointer; + border: none; + display: inline-block; + + @each $btn-type in $btn-types { + &.btn-#{$btn-type} { + color: $font-light; + } + } + + &.btn-submit { + $background-color: $blue-200; + background-color: $background-color; + &:hover { + background-color: lighten($background-color, 10%); + } + } + + &.btn-dark { + $background-color: $blue-500; + background-color: $background-color; + &:hover { + background-color: lighten($background-color, 10%); + } + } + + &.btn-light { + $background-color: $blue-200; + background-color: $background-color; + &:hover { + background-color: lighten($background-color, 10%); + } + } + + &.btn-invite { + $background-color: $blue-500; + background-color: $background-color; + &:hover { + background-color: lighten($background-color, 10%); + } + } + + &.btn-role-select-left { + $background-color: $green-400; + background-color: $background-color; + &:hover { + background-color: $green-300; + } + } + + &.btn-role-select-right { + $background-color: $orange-500; + background-color: $background-color; + + &:hover { + background-color: $orange-400; + } + } + + &.thin-btn-light { + padding: 0.8rem; + padding-inline: min(5rem, 10vw); + border-radius: 5px; + box-shadow: 0 3px 5px 5px rgba($beige-500, 0.2); + + @each $color, $base-color in $colors { + &.thin-btn-#{$color} { + background-color: $base-color; + + &:hover { + background-color: lighten($base-color, 5%); + color: $font-light; + } + } + } + } +} diff --git a/client/src/components/_atoms/Button/Button.tsx b/client/src/components/_atoms/Button/Button.tsx index f8b58fc..54650de 100644 --- a/client/src/components/_atoms/Button/Button.tsx +++ b/client/src/components/_atoms/Button/Button.tsx @@ -1,6 +1,8 @@ import { useNavigate } from "react-router-dom"; import "./Button.scss"; +type ThinButtonColor = "orange" | "blue" | "green"; + type ButtonStyles = | "submit" | "btn-dark" @@ -10,7 +12,25 @@ type ButtonStyles = | "button" | "role-select-left" | "role-select-right" - | "thin-btn-light"; + | { type: "thin-btn-light"; color: ThinButtonColor }; + +interface BaseButtonProps { + type?: "submit" | "button" | "reset"; + children?: string; + href?: string; + className?: string; + onClick?: () => void; +} + +interface RegularButtonProps extends BaseButtonProps { + style: Exclude; +} + +interface ThinButtonProps extends BaseButtonProps { + style: { type: "thin-btn-light"; color: ThinButtonColor }; +} + +type ButtonProps = RegularButtonProps | ThinButtonProps; /** To add a new type: * 1. Add the type in ButtonStyles up above * 2. Add the type in Button.scss (list $btn-types) @@ -25,30 +45,22 @@ export default function Button({ href, className, onClick, -}: { - style: ButtonStyles; - type?: "submit" | "button" | "reset" | undefined; - children?: string; - href?: string; - className?: string; - onClick?: () => void; -}) { +}: ButtonProps) { const buttonType = style === "submit" ? "submit" : "button"; const buttonClassName = - style === "submit" - ? "btn-submit" - : style === "btn-dark" - ? "btn-dark" - : style === "invite" || style === "event" - ? "btn-invite" - : style === "role-select-left" - ? "btn-role-select-left" - : style === "role-select-right" - ? "btn-role-select-right" - : style === "thin-btn-light" - ? "thin-btn-light" + typeof style === "object" + ? `thin-btn-light thin-btn-${style.color}` + : style === "submit" + ? "btn-submit" + : style === "btn-dark" + ? "btn-dark" + : style === "invite" || style === "event" + ? "btn-invite" + : style === "role-select-left" + ? "btn-role-select-left" + : style === "role-select-right" + ? "btn-role-select-right" : "btn-light"; - const navigate = useNavigate(); if (href) { diff --git a/client/src/pages/Owner/Dogs/DogForm/DogForm.scss b/client/src/pages/Owner/Dogs/DogForm/DogForm.scss index 1e0c28f..f86fb0b 100644 --- a/client/src/pages/Owner/Dogs/DogForm/DogForm.scss +++ b/client/src/pages/Owner/Dogs/DogForm/DogForm.scss @@ -6,10 +6,14 @@ &__form { @include dashContentArea(-2rem); - margin:2rem 1rem; - max-width: max-content; - display: flex; - flex-direction: column; + + & { + margin:2rem 1rem; + max-width: max-content; + display: flex; + flex-direction: column; + } + &__title { display: flex; @@ -21,6 +25,7 @@ background: url(../../../../assets/icons/upload.svg)center no-repeat, $blue-400; background-size: 30%; border-radius: 50%; + border: none; cursor: pointer; &:focus { diff --git a/client/src/pages/Owner/Dogs/DogForm/DogForm.tsx b/client/src/pages/Owner/Dogs/DogForm/DogForm.tsx index d647656..1297853 100644 --- a/client/src/pages/Owner/Dogs/DogForm/DogForm.tsx +++ b/client/src/pages/Owner/Dogs/DogForm/DogForm.tsx @@ -92,10 +92,11 @@ export default function DogForm({ const message = mode === "create" - ? "Votre chien a été ajouté avec succès !" - : "Les modifications ont été enregistrées avec succès !"; + ? `Votre chien ${variables.name} a été ajouté avec succès !` + : `Les modificationsd de ${variables.name} ont été enregistrées avec succès !`; - navigate("/owner/my-dogs", { state: { message: `${message}` } }); + sessionStorage.setItem("dogAlert", message); + navigate("/owner/my-dogs"); } catch (error) { console.error("Error saving dog:", error); } @@ -113,12 +114,11 @@ export default function DogForm({
-
pictureRef.current?.click()} onKeyDown={handleKeyPress} - role="button" - tabIndex={0} + type="button" > -
+

{formTitle}

{formSubtitle}

@@ -138,7 +138,10 @@ export default function DogForm({ - diff --git a/client/src/pages/Owner/Dogs/DogList/MyDogList.scss b/client/src/pages/Owner/Dogs/DogList/MyDogList.scss index 24a1552..ec32684 100644 --- a/client/src/pages/Owner/Dogs/DogList/MyDogList.scss +++ b/client/src/pages/Owner/Dogs/DogList/MyDogList.scss @@ -2,13 +2,17 @@ .myDogList { - @include dashContentArea; - @include dogBackground; - display: flex; - flex-direction: column; - align-items: flex-start; - max-width: max-content; - gap: 2rem; + @include dashContentArea(-2rem); + & { + @include dogBackground; + & { + display: flex; + flex-direction: column; + align-items: flex-start; + max-width: max-content; + gap: 2rem; + } + } &__title { display: flex; diff --git a/client/src/pages/Owner/Dogs/DogList/MyDogList.tsx b/client/src/pages/Owner/Dogs/DogList/MyDogList.tsx index 48e90a9..1b8d9cd 100644 --- a/client/src/pages/Owner/Dogs/DogList/MyDogList.tsx +++ b/client/src/pages/Owner/Dogs/DogList/MyDogList.tsx @@ -47,7 +47,10 @@ function MyDogList() { ))} - From 85463272672bb204af5833322e38cc78c034d784 Mon Sep 17 00:00:00 2001 From: Florian Schaessens Date: Wed, 19 Feb 2025 16:45:16 +0100 Subject: [PATCH 08/15] add lenght to picture in dog entity --- server/src/entities/Dog.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/src/entities/Dog.ts b/server/src/entities/Dog.ts index 23fe604..ee63fb6 100644 --- a/server/src/entities/Dog.ts +++ b/server/src/entities/Dog.ts @@ -37,6 +37,8 @@ export class Dog { @Column({ nullable: true, + type: "varchar", + length: 255, default: "/upload/images/defaultdog.jpg", }) @Field({ nullable: true }) From 934ce2c0a95f8989e0e123fed1a45fa92713c28d Mon Sep 17 00:00:00 2001 From: Florian Schaessens Date: Fri, 21 Feb 2025 17:29:31 +0100 Subject: [PATCH 09/15] update funtion name --- client/src/hooks/useFileUpload.ts | 4 ++-- client/src/pages/Owner/Dogs/DogForm/DogForm.scss | 2 +- client/src/pages/Owner/Dogs/DogForm/DogForm.tsx | 4 ++-- server/src/entities/Dog.ts | 2 ++ 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/client/src/hooks/useFileUpload.ts b/client/src/hooks/useFileUpload.ts index 7a5731f..40cf59c 100644 --- a/client/src/hooks/useFileUpload.ts +++ b/client/src/hooks/useFileUpload.ts @@ -3,7 +3,7 @@ import { useState } from "react"; export const useFileUpload = () => { const [selectedFile, setSelectedFile] = useState(null); - const handleChange = (event: React.ChangeEvent) => { + const handleFileChange = (event: React.ChangeEvent) => { if (event.target.files?.[0]) { setSelectedFile(event.target.files[0]); } @@ -12,6 +12,6 @@ export const useFileUpload = () => { return { selectedFile, setSelectedFile, - handleChange, + handleFileChange, }; }; diff --git a/client/src/pages/Owner/Dogs/DogForm/DogForm.scss b/client/src/pages/Owner/Dogs/DogForm/DogForm.scss index f86fb0b..4c22083 100644 --- a/client/src/pages/Owner/Dogs/DogForm/DogForm.scss +++ b/client/src/pages/Owner/Dogs/DogForm/DogForm.scss @@ -22,7 +22,7 @@ &--upload { min-width: clamp(64px , 10%, 96px); aspect-ratio: 1; - background: url(../../../../assets/icons/upload.svg)center no-repeat, $blue-400; + background: url(../../../../assets/icons/upload.svg) center no-repeat, $blue-400; background-size: 30%; border-radius: 50%; border: none; diff --git a/client/src/pages/Owner/Dogs/DogForm/DogForm.tsx b/client/src/pages/Owner/Dogs/DogForm/DogForm.tsx index 1297853..57e66ad 100644 --- a/client/src/pages/Owner/Dogs/DogForm/DogForm.tsx +++ b/client/src/pages/Owner/Dogs/DogForm/DogForm.tsx @@ -23,7 +23,7 @@ export default function DogForm({ const breedRef = useRef(null); const birthDateRef = useRef(null); const infoRef = useRef(null); - const { handleChange, selectedFile } = useFileUpload(); + const { handleFileChange, selectedFile } = useFileUpload(); const { user } = useUser(); const navigate = useNavigate(); @@ -122,7 +122,7 @@ export default function DogForm({ > diff --git a/server/src/entities/Dog.ts b/server/src/entities/Dog.ts index ee63fb6..628bad3 100644 --- a/server/src/entities/Dog.ts +++ b/server/src/entities/Dog.ts @@ -46,6 +46,8 @@ export class Dog { @Column({ nullable: true, + type: "varchar", + length: 255, }) @Field({ nullable: true }) info?: string; From a49fe76f8f6bff0cc8ea309f4bb7191b46e6b5de Mon Sep 17 00:00:00 2001 From: Florian Schaessens Date: Thu, 27 Feb 2025 20:57:40 +0100 Subject: [PATCH 10/15] resize icard image --- client/src/components/_molecules/Card/IdCard.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/components/_molecules/Card/IdCard.scss b/client/src/components/_molecules/Card/IdCard.scss index 279a8c7..cad5bb4 100644 --- a/client/src/components/_molecules/Card/IdCard.scss +++ b/client/src/components/_molecules/Card/IdCard.scss @@ -19,7 +19,7 @@ &__image { grid-area: 1 / 1 / 2 / 2; - object-fit: contain; + object-fit: cover center; max-width: 250px; width: 100%; aspect-ratio: 1; From 6ede6fad60e011cd1e7394fae09da3b0ec35a88b Mon Sep 17 00:00:00 2001 From: Florian Schaessens Date: Fri, 28 Feb 2025 10:45:35 +0100 Subject: [PATCH 11/15] fix async login --- client/src/hooks/useAuth.ts | 32 +++++++++++++++++--- client/src/layouts/Dashboard/DashSideBar.tsx | 4 +-- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/client/src/hooks/useAuth.ts b/client/src/hooks/useAuth.ts index a47ebf9..37b7c39 100644 --- a/client/src/hooks/useAuth.ts +++ b/client/src/hooks/useAuth.ts @@ -18,7 +18,7 @@ interface LoginResponse { export const useAuth = () => { const [loginMutation, { data, loading }] = useMutation(LOGIN); const navigate = useNavigate(); - const { refetch } = useContext(AuthContext); + const { refetch, user, isLoading } = useContext(AuthContext); const login = async ( email: string, @@ -44,9 +44,33 @@ export const useAuth = () => { navigate(`/${decoded.role.toLowerCase()}`); - return { - success: true, - }; + return new Promise((resolve) => { + const checkUserData = () => { + if (user && !isLoading) { + navigate(`/${decoded.role.toLowerCase()}`); + resolve({ + success: true, + }); + return true; + } + return false; + }; + + if (checkUserData()) return; + + const interval = setInterval(() => { + if (checkUserData()) { + clearInterval(interval); + } + }, 100); + + setTimeout(() => { + clearInterval(interval); + resolve({ + success: false, + }); + }, 5000); + }); } catch (error) { return { success: false, diff --git a/client/src/layouts/Dashboard/DashSideBar.tsx b/client/src/layouts/Dashboard/DashSideBar.tsx index 1099623..ea51ed9 100644 --- a/client/src/layouts/Dashboard/DashSideBar.tsx +++ b/client/src/layouts/Dashboard/DashSideBar.tsx @@ -9,13 +9,13 @@ import { Exit } from "@/assets/icons/exit"; import { useAuth } from "@/hooks/useAuth"; import { useUser } from "@/hooks/useUser"; -import { useState } from "react"; const DashSideBar = () => { const location = useLocation(); const { logout } = useAuth(); const { role } = useUser(); - const [userRole] = useState<"trainer" | "owner">(role || "trainer"); + + const userRole = role || "trainer"; const isActive = (path: string) => location.pathname.includes(path) From 152e5b9022dcce175bf1a1b4c8a6c7c19b4fa8f2 Mon Sep 17 00:00:00 2001 From: Florian Schaessens Date: Fri, 28 Feb 2025 13:42:16 +0100 Subject: [PATCH 12/15] fix textarea dimension --- client/src/pages/Owner/Dogs/DogForm/DogForm.scss | 6 ++++++ client/src/pages/Owner/Dogs/DogForm/DogForm.tsx | 7 ++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/client/src/pages/Owner/Dogs/DogForm/DogForm.scss b/client/src/pages/Owner/Dogs/DogForm/DogForm.scss index 4c22083..7646a4f 100644 --- a/client/src/pages/Owner/Dogs/DogForm/DogForm.scss +++ b/client/src/pages/Owner/Dogs/DogForm/DogForm.scss @@ -47,6 +47,12 @@ } } + textarea { + resize: vertical; + min-height: 120px; + width: 100%; + } + } &__button { diff --git a/client/src/pages/Owner/Dogs/DogForm/DogForm.tsx b/client/src/pages/Owner/Dogs/DogForm/DogForm.tsx index 57e66ad..de4bee4 100644 --- a/client/src/pages/Owner/Dogs/DogForm/DogForm.tsx +++ b/client/src/pages/Owner/Dogs/DogForm/DogForm.tsx @@ -136,7 +136,12 @@ export default function DogForm({
- +