Skip to content

Commit 728ec84

Browse files
e-waitwilkhoo
andauthored
Add image upload to camp creation (#224)
* Add image upload to camp creation * aspect ratio fixes * Add error handling so that when a user tries to click next, errors are shown (if any). Co-authored-by: twilkhoo <tejaswilkhoo@hotmail.com>
1 parent 7296320 commit 728ec84

File tree

5 files changed

+78
-47
lines changed

5 files changed

+78
-47
lines changed

frontend/src/APIClients/CampsAPIClient.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ const getCampById = async (id: string): Promise<CampResponse> => {
3636

3737
const createNewCamp = async (
3838
camp: CreateUpdateCampRequest,
39+
fileURL?: string,
3940
): Promise<CreateUpdateCampResponse> => {
4041
// Check if atleast the first step and 1 session is scheduled
4142
if (!isMinCampDetailsFilled(camp)) {
@@ -47,6 +48,11 @@ const createNewCamp = async (
4748
const formData = new FormData();
4849
formData.append("data", JSON.stringify(camp));
4950

51+
if (fileURL) {
52+
const file = await fetch(fileURL).then((res) => res.blob());
53+
formData.append("file", file);
54+
}
55+
5056
const { data } = await baseAPIClient.post("/camp", formData, {
5157
headers: { Authorization: getBearerToken() },
5258
});
@@ -56,6 +62,7 @@ const createNewCamp = async (
5662
const editCampById = async (
5763
id: string,
5864
camp: CreateUpdateCampRequest,
65+
fileURL?: string,
5966
): Promise<CreateUpdateCampResponse> => {
6067
// Check if atleast the first step and 1 session is scheduled
6168
if (!isMinCampDetailsFilled(camp)) {
@@ -66,6 +73,12 @@ const editCampById = async (
6673

6774
const formData = new FormData();
6875
formData.append("data", JSON.stringify({ ...camp }));
76+
77+
if (fileURL) {
78+
const file = await fetch(fileURL).then((res) => res.blob());
79+
formData.append("file", file);
80+
}
81+
6982
const { data } = await baseAPIClient.patch(`/camp/${id}`, formData, {
7083
headers: {
7184
Authorization: getBearerToken(),

frontend/src/components/pages/CampCreation/CampCreationFooter.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export type CampCreationFooterProps = {
1111
isPublishedCamp: boolean,
1212
isNewCamp: boolean,
1313
) => Promise<void>;
14+
setShowCreationErrors: React.Dispatch<React.SetStateAction<boolean>>;
1415
};
1516

1617
const CampCreationFooter = ({
@@ -19,6 +20,7 @@ const CampCreationFooter = ({
1920
handleStepNavigation,
2021
createUpdateCamp,
2122
isEditingCamp,
23+
setShowCreationErrors,
2224
}: CampCreationFooterProps): React.ReactElement => {
2325
const [isAwaitingReq, setIsAwaitingReq] = React.useState(false);
2426

@@ -30,6 +32,10 @@ const CampCreationFooter = ({
3032
};
3133

3234
const onNextStep = async () => {
35+
if (!isCurrentStepCompleted) {
36+
setShowCreationErrors(true);
37+
return;
38+
}
3339
handleStepNavigation(1);
3440

3541
if (currentStep === CampCreationPages.RegistrationFormPage) {
@@ -79,7 +85,12 @@ const CampCreationFooter = ({
7985
height="48px"
8086
variant="primary"
8187
onClick={onNextStep}
82-
disabled={!isCurrentStepCompleted}
88+
background={
89+
isCurrentStepCompleted
90+
? "primary.green.100"
91+
: "primary.green_disabled.100"
92+
}
93+
_hover={{ cursor: isCurrentStepCompleted ? "pointer" : "not-allowed" }}
8394
isLoading={
8495
isAwaitingReq &&
8596
currentStep === CampCreationPages.RegistrationFormPage

frontend/src/components/pages/CampCreation/CampDetails/index.tsx

Lines changed: 41 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import {
1111
Checkbox,
1212
Button,
1313
Image,
14+
AspectRatio,
15+
VStack,
1416
} from "@chakra-ui/react";
1517
import IconImage from "../../../../assets/icon_image.svg";
1618
import { MAX_CAMP_DESC_LENGTH } from "../../../../constants/CampManagementConstants";
@@ -56,6 +58,7 @@ type CampCreationDetailsProps = {
5658
handleProvince: (event: React.ChangeEvent<HTMLSelectElement>) => void;
5759
handlePostalCode: (event: React.ChangeEvent<HTMLInputElement>) => void;
5860
setCampImageURL: React.Dispatch<React.SetStateAction<string>>;
61+
showErrors: boolean;
5962
};
6063

6164
const CampCreationDetails = ({
@@ -95,8 +98,8 @@ const CampCreationDetails = ({
9598
handleProvince,
9699
handlePostalCode,
97100
setCampImageURL,
101+
showErrors,
98102
}: CampCreationDetailsProps): React.ReactElement => {
99-
const [showErrors, setShowErrors] = useState<boolean>(false);
100103
const [showImageError, setShowImageError] = useState<boolean>(false);
101104

102105
const errorText = (input: boolean | string | number, message: string) => {
@@ -149,10 +152,8 @@ const CampCreationDetails = ({
149152
};
150153

151154
return (
152-
<Box paddingLeft="200px">
153-
<Text textStyle="displayXLarge" marginTop="56px">
154-
Camp Details
155-
</Text>
155+
<Box paddingLeft="200px" my="56px">
156+
<Text textStyle="displayXLarge">Camp Details</Text>
156157
<Text textStyle="displayLarge" marginTop="32px">
157158
Overview
158159
</Text>
@@ -552,44 +553,43 @@ const CampCreationDetails = ({
552553
style={{ display: "none" }}
553554
accept="image/*"
554555
/>
555-
<Box
556-
marginTop="32px"
557-
bg="background.grey.200"
558-
width="528px"
559-
height="325px"
560-
border="3px"
561-
borderStyle="dashed"
562-
borderColor="gray.200"
563-
onDragOver={handleOnDragOver}
564-
onDrop={handleOnDrop}
565-
_hover={{
566-
borderColor: "gray.400",
567-
}}
568-
onClick={handleCampImageClick}
569-
cursor="pointer"
570-
>
571-
{!campImageURL ? (
572-
<>
556+
<AspectRatio marginTop="32px" width="528px" ratio={16 / 9}>
557+
<Box
558+
bg="background.grey.200"
559+
border="3px"
560+
borderStyle="dashed"
561+
borderColor="gray.200"
562+
onDragOver={handleOnDragOver}
563+
onDrop={handleOnDrop}
564+
_hover={{
565+
borderColor: "gray.400",
566+
}}
567+
onClick={handleCampImageClick}
568+
cursor="pointer"
569+
>
570+
{!campImageURL ? (
571+
<VStack spacing={4} justify="center">
572+
<Image src={IconImage} alt="File upload icon" width="150px" />
573+
<Text
574+
textStyle="buttonSemiBold"
575+
textAlign="center"
576+
marginTop="30px"
577+
>
578+
Click or drag and drop to add an image
579+
<br />
580+
Max File Size: 5 MB{" "}
581+
</Text>
582+
</VStack>
583+
) : (
573584
<Image
574-
margin="35px auto 0 auto"
575-
src={IconImage}
576-
alt="File upload icon"
577-
width="190px"
585+
src={campImageURL}
586+
alt="Selected camp image"
587+
objectFit="scale-down"
578588
/>
579-
<Text
580-
textStyle="buttonSemiBold"
581-
textAlign="center"
582-
marginTop="30px"
583-
>
584-
Click or drag and drop to add an image
585-
<br />
586-
Max File Size: 5 MB{" "}
587-
</Text>
588-
</>
589-
) : (
590-
<Image width="528px" height="319px" src={campImageURL} alt="camp" />
591-
)}
592-
</Box>
589+
)}
590+
</Box>
591+
</AspectRatio>
592+
593593
<Text
594594
textStyle="caption"
595595
color="red"
@@ -615,10 +615,6 @@ const CampCreationDetails = ({
615615
>
616616
Replace Image
617617
</Button>
618-
619-
<Box marginTop="20px">
620-
<Button onClick={() => setShowErrors(true)}>Dummy Submit</Button>
621-
</Box>
622618
</Box>
623619
);
624620
};

frontend/src/components/pages/CampCreation/index.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ const CampCreationPage = (): React.ReactElement => {
5151

5252
const [visitedRegistrationPage, setVisitedRegistrationPage] = useState(false);
5353

54+
const [showCreationErrors, setShowCreationErrors] = useState<boolean>(false);
55+
5456
// Variables to determine whether or not all required fields have been filled out.
5557
// NOTE: This will depend on what type of state a page requires, i.e. determining
5658
// if a checkbox is checked is different than determining if an input field is filled.
@@ -295,11 +297,15 @@ const CampCreationPage = (): React.ReactElement => {
295297
let campResponse: CreateUpdateCampResponse;
296298

297299
if (isNewCamp) {
298-
campResponse = await CampsAPIClient.createNewCamp(campFields);
300+
campResponse = await CampsAPIClient.createNewCamp(
301+
campFields,
302+
campImageURL,
303+
);
299304
} else {
300305
campResponse = await CampsAPIClient.editCampById(
301306
editCampId,
302307
campFields,
308+
campImageURL,
303309
);
304310
}
305311

@@ -416,6 +422,7 @@ const CampCreationPage = (): React.ReactElement => {
416422
setPostalCode(event.target.value)
417423
}
418424
setCampImageURL={setCampImageURL}
425+
showErrors={showCreationErrors}
419426
/>
420427
</React.Fragment>
421428
);
@@ -471,6 +478,7 @@ const CampCreationPage = (): React.ReactElement => {
471478
handleStepNavigation={handleStepNavigation}
472479
createUpdateCamp={createUpdateCampHelper}
473480
isEditingCamp={editCampId !== undefined}
481+
setShowCreationErrors={setShowCreationErrors}
474482
/>
475483
</VStack>
476484
);

frontend/src/theme/colors.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ const colors = {
55
200: "#3A7035",
66
600: "#146600",
77
},
8+
green_disabled: {
9+
100: "#B5CFB3",
10+
},
811
},
912
secondary: {
1013
critical: {

0 commit comments

Comments
 (0)