Skip to content

Commit 2091d2c

Browse files
Merge pull request #146 from NexGenStudioDev/dev
feat: Enhance AddMember feature with form validation and state manage…
2 parents d22ee3d + 09db1a3 commit 2091d2c

16 files changed

Lines changed: 240 additions & 58 deletions

src/App.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ body {
147147
--cd-surface-2: #f1f5f9;
148148
--cd-surface-3: #eef2f7;
149149
--cd-border: #d8e1ee;
150-
--cd-border-subtle: #e9eef6;
150+
--cd-border-subtle: rgb(165, 166, 167);
151151
--cd-text: #1e293b;
152152
--cd-text-2: #53657d;
153153
--cd-text-muted: #8392a8;

src/Component/ui/DropDown.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { useTheme } from "@/theme";
55
type DropDownProps = {
66
options: string[];
77
label?: string;
8+
error?: string;
89
onSelect: (option: string) => void;
910
className?: string;
1011
value?: string;
@@ -20,6 +21,7 @@ const DropDown: React.FC<DropDownProps> = ({
2021
value,
2122
placeholder = "Select an option",
2223
disabled = false,
24+
error,
2325
}) => {
2426
const { theme } = useTheme();
2527
const [open, setOpen] = useState(false);
@@ -69,7 +71,7 @@ const DropDown: React.FC<DropDownProps> = ({
6971
className="w-full rounded-lg px-3 py-2 text-left flex justify-between items-center text-sm transition-all duration-150 disabled:cursor-not-allowed disabled:opacity-60"
7072
style={{
7173
backgroundColor: theme.bg.surface,
72-
border: `1px solid ${theme.border.default}`,
74+
border: `1px solid ${error ? "var(--cd-danger)" : "var(--cd-border)"}`,
7375
color: selected ? theme.text.primary : theme.text.muted,
7476
}}
7577
>
@@ -80,12 +82,12 @@ const DropDown: React.FC<DropDownProps> = ({
8082
/>
8183
</button>
8284

83-
{open && options.length > 0 && (
85+
{!error && open && options.length > 0 && (
8486
<div
8587
className="absolute mt-1 w-full rounded-lg z-20 overflow-hidden max-h-60 overflow-y-auto"
8688
style={{
8789
backgroundColor: theme.bg.surface,
88-
border: `1px solid ${theme.border.default}`,
90+
border: `1px solid ${error ? "var(--cd-danger)" : "var(--cd-border)"}`,
8991
boxShadow: `0 8px 24px ${theme.shadow.md}`,
9092
}}
9193
role="listbox"
@@ -115,6 +117,12 @@ const DropDown: React.FC<DropDownProps> = ({
115117
))}
116118
</div>
117119
)}
120+
121+
{error && (
122+
<p className="text-xs mt-1 text-[var(--cd-danger)]">
123+
{error}
124+
</p>
125+
)}
118126
</div>
119127
);
120128
};

src/Component/ui/Input.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ type InputType = "text" | "email" | "password" | "number" | "url" | "tel" | "tim
66
type InputProps = {
77
label?: string;
88
name: string;
9+
910
placeholder?: string;
1011
value?: string | number;
1112
type?: InputType;

src/Component/ui/Url.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React from "react";
22

33
interface UrlProps {
44
protocol?: string;
5-
domain: string;
5+
domain: string | undefined;
66
themeMode?: "light" | "dark";
77
className?: string;
88
style?: React.CSSProperties;

src/features/AddMember/v1/Component/AddMemberHeader.tsx

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,12 @@ import { Link } from "react-router";
33
import Button from "../../../../Component/ui/Button";
44
import { memo } from "react";
55

6-
const AddMemberHeader = () => {
6+
type AddMemberHeaderProps = {
7+
onCreate: () => void;
8+
onDiscard: () => void;
9+
};
10+
11+
const AddMemberHeader = ({ onCreate, onDiscard }: AddMemberHeaderProps) => {
712
return (
813
<div
914
className="py-[3vh] w-full border-b flex text-xl font-bold justify-between"
@@ -20,12 +25,8 @@ const AddMemberHeader = () => {
2025
</Link>
2126

2227
<div className="w-[40%] h-full mr-[3vw] flex justify-end gap-3">
23-
<Button
24-
text="Discard Draft"
25-
variant="secondary"
26-
onClick={() => alert("Discard Draft clicked")}
27-
/>
28-
<Button text="Create Member" onClick={() => alert("Create Member clicked")} />
28+
<Button text="Discard Draft" variant="secondary" onClick={onDiscard} />
29+
<Button text="Create Member" onClick={onCreate} />
2930
</div>
3031
</div>
3132
);

src/features/AddMember/v1/Component/Administrative_MetaData.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const Administrative_MetaData = () => {
1414
Administrative Metadata
1515
</p>
1616
<MemberShip_Status />
17-
<AccessLevel />
17+
{/* <AccessLevel /> */}
1818
</div>
1919
);
2020
};

src/features/AddMember/v1/Component/Community_Involment.tsx

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,24 @@ import { GrGroup } from "react-icons/gr";
33
import InterestBox from "./InterestBox";
44
import AREA_OF_INTEREST from "../Constant/Interest.constant";
55
import { TextArea } from "../../../../Component/ui/TextArea";
6+
import { useFormContext } from "react-hook-form";
7+
import type { MemberFormValues } from "../Validator/AddMember.Validator";
68

79
const Community_Involvement = () => {
8-
const [internalNotes, setInternalNotes] = useState("");
10+
const { watch, setValue , formState} = useFormContext<MemberFormValues>();
11+
12+
const {errors} = formState
13+
14+
const [internalNotes, setInternalNotes] = useState(watch("internalNotes") ?? "");
15+
const areaOfInterest = watch("areaOfInterest") ?? [];
16+
17+
const toggleInterest = (interest: string, isChecked: boolean) => {
18+
const nextInterests = isChecked
19+
? [...areaOfInterest, interest]
20+
: areaOfInterest.filter((value) => value !== interest);
21+
22+
setValue("areaOfInterest", nextInterests, { shouldDirty: true });
23+
};
924

1025
return (
1126
<div
@@ -30,7 +45,13 @@ const Community_Involvement = () => {
3045

3146
<div className="flex flex-wrap gap-4 mt-3">
3247
{AREA_OF_INTEREST.map((interest, index) => (
33-
<InterestBox key={index} label={interest} isChecked={false} onClick={() => {}} />
48+
<InterestBox
49+
key={index}
50+
label={interest}
51+
52+
isChecked={areaOfInterest.includes(interest)}
53+
onClick={(clicked) => toggleInterest(interest, clicked)}
54+
/>
3455
))}
3556
</div>
3657

@@ -39,7 +60,11 @@ const Community_Involvement = () => {
3960
name="internalNotes"
4061
placeholder="Enter internal notes"
4162
value={internalNotes}
42-
onChange={(_, value) => setInternalNotes(value)}
63+
error={errors.internalNotes?.message}
64+
onChange={(_, value) => {
65+
setInternalNotes(value);
66+
setValue("internalNotes", value, { shouldDirty: true });
67+
}}
4368
className="mt-[3vh]"
4469
rows={5}
4570
/>

src/features/AddMember/v1/Component/InterestBox.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ const InterestBox = (props: InterestBoxProps) => {
1515
className={`InterestBox flex p-3 font-bold ${
1616
Clicked
1717
? "bg-blue-100 border-blue-500 text-blue-600"
18-
: "bg-gray-100 border-gray-300 text-black"
18+
: "bg-[cd-surface] border-gray-300 text-[--cd-text)"
1919
} border rounded-lg cursor-pointer`}
2020
onClick={() => {
2121
props.onClick(!Clicked);
@@ -24,7 +24,7 @@ const InterestBox = (props: InterestBoxProps) => {
2424
>
2525
<div
2626
className={`rounded-md border-2 w-6 h-6 flex items-center justify-center ${
27-
Clicked ? "border-blue-500 bg-blue-500" : "border-gray-300"
27+
Clicked ? "border-blue-500 bg-blue-500" : "border-[var(cd-border-subtle)] bg-transparent"
2828
}`}
2929
>
3030
{Clicked && <TiTick className="text-white text-lg" />}

src/features/AddMember/v1/Component/MemberShip_Status.tsx

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { useState } from "react";
22

3+
import { useFormContext } from "react-hook-form";
4+
import type { MemberFormValues } from "../Validator/AddMember.Validator";
5+
36
type MembershipStatus = "Active" | "Inactive" | "Pending" | "Suspended" | "On Boarding";
47

58
const MemberShip_Status = () => {
6-
const [membershipStatus, setMembershipStatus] = useState<MembershipStatus>("Active");
7-
89
const statusColorMap: Record<MembershipStatus, string> = {
910
Active: "bg-green-500",
1011
Inactive: "bg-gray-400",
@@ -13,10 +14,12 @@ const MemberShip_Status = () => {
1314
"On Boarding": "bg-blue-400",
1415
};
1516

17+
const { watch, setValue , formState} = useFormContext<MemberFormValues>();
18+
const {errors} = formState
19+
const membershipStatus = (watch("membershipStatus") ?? "On Boarding") as MembershipStatus;
20+
1621
return (
1722
<div className="MemberShip_Status flex flex-col gap-2 mt-4 text-lg">
18-
<p className="text-md font-semibold">Membership Status</p>
19-
2023
{/* Active */}
2124
<div className="flex items-center gap-4">
2225
<input
@@ -25,7 +28,7 @@ const MemberShip_Status = () => {
2528
id="Active"
2629
value="Active"
2730
checked={membershipStatus === "Active"}
28-
onChange={() => setMembershipStatus("Active")}
31+
onChange={() => setValue("membershipStatus", "Active", { shouldDirty: true })}
2932
/>
3033

3134
<span
@@ -34,15 +37,20 @@ const MemberShip_Status = () => {
3437
<label htmlFor="Active">Active</label>
3538
</div>
3639

40+
{errors.membershipStatus && (
41+
<p className="text-red-500 text-sm">{errors.membershipStatus.message}</p>
42+
)}
43+
3744
{/* Inactive */}
3845
<div className="flex items-center gap-4">
3946
<input
4047
type="radio"
4148
id="Inactive"
4249
name="membershipStatus"
50+
4351
value="Inactive"
4452
checked={membershipStatus === "Inactive"}
45-
onChange={() => setMembershipStatus("Inactive")}
53+
onChange={() => setValue("membershipStatus", "Inactive", { shouldDirty: true })}
4654
/>
4755

4856
<span
@@ -60,7 +68,7 @@ const MemberShip_Status = () => {
6068
value="Pending"
6169
id="Pending"
6270
checked={membershipStatus === "Pending"}
63-
onChange={() => setMembershipStatus("Pending")}
71+
onChange={() => setValue("membershipStatus", "Pending", { shouldDirty: true })}
6472
/>
6573

6674
<span
@@ -77,7 +85,7 @@ const MemberShip_Status = () => {
7785
id="OnBoarding"
7886
value="On Boarding"
7987
checked={membershipStatus === "On Boarding"}
80-
onChange={() => setMembershipStatus("On Boarding")}
88+
onChange={() => setValue("membershipStatus", "On Boarding", { shouldDirty: true })}
8189
/>
8290
<span
8391
className={`w-4 h-4 rounded-full ${statusColorMap["On Boarding"]} border border-gray-300`}
@@ -93,7 +101,7 @@ const MemberShip_Status = () => {
93101
id="Suspended"
94102
value="Suspended"
95103
checked={membershipStatus === "Suspended"}
96-
onChange={() => setMembershipStatus("Suspended")}
104+
onChange={() => setValue("membershipStatus", "Suspended", { shouldDirty: true })}
97105
/>
98106

99107
<span

src/features/AddMember/v1/Component/SkillChip.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ type SkillChipProps = {
44

55
const SkillChip = (props: SkillChipProps) => {
66
return (
7-
<div className="bg-blue-100 text-blue-800 px-3 py-1 rounded-full text-sm">{props.skill}</div>
7+
<div className="px-3 py-1 rounded-full text-sm">{props.skill}</div>
88
);
99
};
1010

0 commit comments

Comments
 (0)