Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion backend/src/entities/Group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export class Group extends BaseEntity {
@Field()
event_type: string;

@Column()
@Column({ default: 0 })
@Field()
piggy_bank: number;

Expand Down
45 changes: 42 additions & 3 deletions backend/src/resolvers/GroupResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ class CreateGroupInput {
@Field()
event_type!: string;

@Field()
piggy_bank!: number;
@Field({ nullable: true, defaultValue: 0 })
piggy_bank?: number;

@Field()
deadline!: Date;
Expand All @@ -40,6 +40,15 @@ class CreateGroupInput {
user_beneficiary?: string;
}

@InputType()
class AddFundsInput {
@Field()
groupId!: number;

@Field()
amount!: number;
}

@ObjectType()
export class MyGroupsResponse {
@Field(() => [Group])
Expand Down Expand Up @@ -121,7 +130,7 @@ export default class GroupResolver {
user_admin: userAdmin,
name: data.name,
event_type: data.event_type,
piggy_bank: data.piggy_bank,
piggy_bank: data.piggy_bank ?? 0,
deadline: data.deadline,
user_beneficiary: beneficiaryUser ?? undefined,
});
Expand Down Expand Up @@ -156,4 +165,34 @@ export default class GroupResolver {

return group;
}

@UseMiddleware(RoleMiddleware())
@Mutation(() => Group)
async addFundsToGroup(@Arg("data") data: AddFundsInput, @Ctx() ctx: ContextType) {
if (!ctx.user) throw new Error("Utilisateur non connecté");

// Vérifier que le groupe existe
const group = await Group.findOne({
where: { id: data.groupId },
relations: { groupMember: true },
});

if (!group) throw new Error("Groupe introuvable");

// Vérifier que l'utilisateur est membre du groupe
const isMember = await GroupMember.findOne({
where: { groupId: data.groupId, userId: ctx.user.id },
});

if (!isMember) throw new Error("Vous n'êtes pas membre de ce groupe");

// Vérifier que le montant est positif
if (data.amount <= 0) throw new Error("Le montant doit être positif");

// Ajouter les fonds à la cagnotte
group.piggy_bank += data.amount;
await group.save();

return group;
}
}
8 changes: 0 additions & 8 deletions frontend/src/components/Wishlist.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -151,14 +151,6 @@ export default function Wishlist() {
<div className="flex flex-col items-center justify-center text-white">
<Icon icon="gift" className="text-7xl opacity-80 mb-3" />
<p className="text-lg mb-8">Aucune idée pour l'instant.</p>
<Button
type="button"
icon="plus"
text="Ajouter une idée"
colour="green"
onClick={addModal.open}
className="px-4 py-2 rounded-xl"
/>
</div>
</div>
) : (
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/auth/AuthFormTemplate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export default function AuthFormTemplate({ title, children, onSubmit, footer }:
</Title>
<div className="flex flex-col justify-start w-full flex-shrink-0">
<form
className="flex flex-col items-center gap-3 justify-center w-full max-w-[600px] md:w-auto md:max-w-none"
className="flex flex-col items-center gap-4 justify-center w-full max-w-[600px] md:w-auto md:max-w-none"
onSubmit={onSubmit}
>
{children}
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/components/auth/auth.css
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@
align-items: center;
justify-content: center;
text-align: center;
font-size: 60px;
font-size: 75px;
line-height: 1;
font-weight: 400;
color: #fdfbf6;
}
Expand Down
56 changes: 29 additions & 27 deletions frontend/src/components/forms/CreateGroupForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { useCreateGroupMutation } from "../../graphql/generated/graphql-types";
import { GET_ALL_MY_GROUPS } from "../../graphql/operations/groupOperations";
import { groupCreationFormValidation } from "../../hooks/formValidationRules";
import { useSanitizedForm } from "../../hooks/useSanitizedForm";
import Button from "../utils/Button";
import Icon from "../utils/Icon";
import Input from "../utils/Input";
import InputWithToggle from "../utils/InputWithToggle";
Expand All @@ -15,9 +14,10 @@ import GroupLink from "./GroupLink";

type CreateGroupFormProps = {
onSuccess?: () => void;
onCancel?: () => void;
};

export default function CreateGroupForm({ onSuccess }: CreateGroupFormProps) {
export default function CreateGroupForm({ onSuccess, onCancel }: CreateGroupFormProps) {
const options = [
{
label: "Anniversaire",
Expand Down Expand Up @@ -121,14 +121,18 @@ export default function CreateGroupForm({ onSuccess }: CreateGroupFormProps) {
}

return (
<form className=" flex w-full h-full rounded-2xl" onSubmit={handleSubmit} autoComplete="off">
<div className="bg-green w-1/2 h-full flex flex-col justify-center pt-10 pb-5 rounded-tl-2xl rounded-bl-2xl">
<form
className="flex w-full h-full rounded-2xl max-md:flex-col max-md:bg-green max-md:rounded-none max-md:overflow-y-auto max-md:p-8 max-md:justify-center"
onSubmit={handleSubmit}
autoComplete="off"
>
<div className="bg-green w-1/2 h-full flex flex-col justify-center pt-10 pb-5 rounded-tl-2xl rounded-bl-2xl max-md:w-full max-md:rounded-none max-md:pt-0 max-md:pb-0">
{/* Form to create a new group */}
<Subtitle className="text-center text-2xl">Créer un groupe</Subtitle>
<div className="text-white text-8xl m-auto">
<Subtitle className="text-center text-2xl max-md:text-xl max-md:mb-10">Créer un groupe</Subtitle>
<div className="text-white text-8xl m-auto max-md:hidden">
<Icon icon="image" />
</div>
<div className="flex flex-col gap-4 px-20">
<div className="flex flex-col gap-4 px-20 max-md:px-0">
<Input
name="name"
type="text"
Expand All @@ -154,16 +158,6 @@ export default function CreateGroupForm({ onSuccess }: CreateGroupFormProps) {
theme="light"
/>

<Input
name="piggy_bank"
type="number"
value={String(formData.piggy_bank)}
onChange={handleChange}
placeholder={String(0)}
error={errors.piggy_bank}
icon="dollar"
/>

<InputWithToggle
checked={checked}
onCheckedChange={() => {
Expand All @@ -185,20 +179,28 @@ export default function CreateGroupForm({ onSuccess }: CreateGroupFormProps) {
error={errors.deadline}
/>
</div>
<Button
type="submit"
text="Créer le groupe"
className="text-center w-fit px-8 py-1 m-auto text-lg"
colour="dark"
rounded
>
Créer
</Button>
<div className="flex flex-col gap-6 max-md:w-full max-md:mt-8">
<button
type="submit"
className="bg-dark text-white font-inter-extra-bold rounded-lg py-2 px-8 w-fit m-auto text-lg shadow-md hover:brightness-110 max-md:w-full max-md:py-3 max-md:rounded-full max-md:text-base max-md:font-bold max-md:shadow-[0_2px_6px_rgba(32,9,4,0.2)] max-md:bg-white max-md:text-dark max-md:hover:bg-gray-100"
>
Créer le groupe
</button>
{onCancel && (
<button
type="button"
onClick={onCancel}
className="hidden max-md:block w-full py-3 rounded-full bg-dark text-white text-base font-bold shadow-[0_2px_6px_rgba(32,9,4,0.2)] hover:bg-[#463835]"
>
Annuler
</button>
)}
</div>
{error && <p className="text-orange font-inter text-sm pt-1 text-center">{error}</p>}
{errors.main && <p className="text-orange font-inter text-sm pt-1 text-center">{errors.main}</p>}
</div>

<div className="w-1/2 bg-white max-md:w-full flex flex-col max-md:rounded-none rounded-tr-2xl rounded-br-2xl">
<div className="w-1/2 bg-white h-full flex flex-col rounded-tr-2xl rounded-br-2xl max-md:hidden">
<div className="flex flex-col gap-4 px-20 m-auto">
{/* Adding users can go here */}
<div className="flex flex-row items-center w-full border border-blue">
Expand Down
105 changes: 105 additions & 0 deletions frontend/src/components/groups/AddFundsModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { useState } from "react";
import { useAddFundsToGroupMutation } from "../../graphql/generated/graphql-types";
import { GET_ALL_MY_GROUPS } from "../../graphql/operations/groupOperations";
import Button from "../utils/Button";
import Input from "../utils/Input";
import Modal from "../utils/Modal";

type AddFundsModalProps = {
isOpen: boolean;
onClose: () => void;
onSuccess?: () => void;
groupId: number;
currentAmount: number;
};

export default function AddFundsModal({
isOpen,
onClose,
onSuccess,
groupId,
currentAmount,
}: AddFundsModalProps) {
const [amount, setAmount] = useState<string>("");
const [error, setError] = useState<string>("");

const [addFunds, { loading }] = useAddFundsToGroupMutation({
refetchQueries: [{ query: GET_ALL_MY_GROUPS }],
awaitRefetchQueries: true,
});

const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError("");

const numAmount = Number(amount);
if (!amount || numAmount <= 0) {
setError("Veuillez entrer un montant valide");
return;
}

try {
await addFunds({
variables: {
data: {
groupId,
amount: numAmount,
},
},
});
setAmount("");
onSuccess?.();
onClose();
} catch (err) {
if (err instanceof Error) {
setError(err.message);
} else {
setError("Une erreur est survenue");
}
}
};

const handleClose = () => {
setAmount("");
setError("");
onClose();
};

return (
<Modal
isOpen={isOpen}
onClose={handleClose}
className="!w-[450px] !h-auto max-md:!h-auto max-md:!w-11/12 max-md:!rounded-2xl !bg-yellow"
>
<form onSubmit={handleSubmit} className="p-10 flex flex-col gap-8 bg-yellow rounded-2xl min-w-[350px]">
<h2 className="text-3xl font-inter-extra-bold text-white text-center mb-2">Ajouter des fonds</h2>

<div className="text-center">
<p className="text-white text-lg mb-1">Cagnotte actuelle</p>
<p className="font-bold text-white text-4xl">{currentAmount}€</p>
</div>

<div className="mt-2">
<Input
name="amount"
type="number"
value={amount}
onChange={(e) => setAmount(e.target.value)}
placeholder="Montant à ajouter (€)"
icon="piggyBank"
error={error}
/>
</div>

<div className="flex gap-4 justify-center mt-4">
<Button type="button" colour="orange" onClick={handleClose}>
Annuler
</Button>
<Button type="submit" colour="green">
{loading ? "..." : "Ajouter"}
</Button>
</div>
</form>
</Modal>
);
}
11 changes: 9 additions & 2 deletions frontend/src/components/groups/Groups.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ type GroupsProps = {
messages: Record<number, Message[]>;
getNbNewMessages: (groupId: number, messages: Message[]) => number;
updateLastVu: (groupId: number, date: Date | string, serveurSyconization?: boolean) => void;
onGroupClick?: (group: GetAllMyGroupsQuery["getAllMyGroups"]["groups"][number]) => void;
activeGroupId?: number;
};

export default function Groups({
Expand All @@ -25,6 +27,8 @@ export default function Groups({
loading,
error,
messages,
onGroupClick,
activeGroupId,
getNbNewMessages,
updateLastVu,
}: GroupsProps) {
Expand All @@ -37,7 +41,8 @@ export default function Groups({
<>
<Container
colour="blue"
title="Mes Groupes"
title="Mes groupes"
classNameTitle="text-[1.125rem]"
button={
<Button text={"Ajouter un groupe"} icon="plus" colour="green" onClick={createGroupModal.open} />
}
Expand All @@ -51,13 +56,15 @@ export default function Groups({
key={group.id}
id={Number(group.id)}
title={group.name}
active={activeGroupId === Number(group.id)}
onClick={() => {
setActiveGroup?.(group);
updateLastVu(Number(group.id), messages[Number(group.id)][0].createdAt);
onGroupClick?.(group);
}}
nbNewMessages={getNbNewMessages(Number(group.id), messages[Number(group.id)] || [])}
>
<p className="text-gray-600 text-sm sm:text-base truncate overflow-hidden text-ellipsis whitespace-nowrap">
<p className="text-gray-600 text-xs sm:text-sm leading-tight">
<span> Date limite: {formatDate(new Date(group.deadline))} </span> <br />
<span>
{group.groupMember?.length}{" "}
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/groups/Messaging/Message.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export default function Message({ regroupement, userId }: MessageProps) {
{regroupement.map((message, index) => {
return (
<div
className={`w-[320px] border-[1px] px-[16px] py-[10px] ${defClassRadius(regroupement.length, index + 1, isAutreMessage)} ${defClassColor(isAutreMessage)}`}
className={`w-[250px] md:w-[320px] border-[1px] px-[16px] py-[10px] ${defClassRadius(regroupement.length, index + 1, isAutreMessage)} ${defClassColor(isAutreMessage)}`}
key={message.id}
>
<p className="text-[16px] leading-[1.25]">{message.content}</p>
Expand All @@ -68,7 +68,7 @@ export default function Message({ regroupement, userId }: MessageProps) {
<img
src={getProfilePictureUrl(regroupement[0].user.image_url)}
alt="profile utilisateur"
className="w-[60px] h-[60px] rounded-full object-cover"
className="w-[40px] h-[40px] md:w-[60px] md:h-[60px] rounded-full object-cover"
/>
</div>
</div>
Expand Down
Loading