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
86 changes: 69 additions & 17 deletions client/src/components/Admin/OpenTimeForm.jsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useState } from "react";
import { Add as AddIcon } from "@mui/icons-material";
import {
Button,
Expand Down Expand Up @@ -51,6 +52,17 @@ const OpenTimeForm = ({
{ label: "Last", value: -1 },
];

const [closeTimeErrorss, setCloseTimeErrorss] = useState({});

const getCloseTimeErrorsMessage = (index) => {
const errorType = closeTimeErrorss[index];
if (errorType === "minTime")
return "Closing time must be after opening time";
if (errorType === "equalTime")
return "Closing time must be different from opening time";
return "";
};

return (
<Card style={{ border: "1px solid lightgray", borderRadius: "4px" }}>
<CardContent>
Expand All @@ -60,7 +72,7 @@ const OpenTimeForm = ({
<div>
{values?.hours?.length > 0 &&
values?.hours.map((hour, index) => {
//weekOfMonth
//weekOfMonth`
const weekOfMonth = `hours[${index}].weekOfMonth`;
const touchedMonth = getIn(touched, weekOfMonth);
const errorMonth = getIn(errors, weekOfMonth);
Expand Down Expand Up @@ -159,13 +171,14 @@ const OpenTimeForm = ({
</MenuItem>
))}
</Select>
{touchedWeek && errorWeek ? (
{touchedWeek && errorWeek && (
<FormHelperText error>
Day is required
</FormHelperText>
) : null}
)}
</FormControl>
</Grid>

<Grid item xs={8} sm={3}>
<DesktopTimePicker
name={open}
Expand Down Expand Up @@ -225,31 +238,65 @@ const OpenTimeForm = ({
? dayjs(hour.close, "HH:mm:ss")
: null
}
onBlur={() => {
setFieldTouched(close, true, true);
}}
minTime={
hour.open
? dayjs(hour.open, "HH:mm:ss")
: undefined
}
onError={(newError) =>
setCloseTimeErrorss((prev) => ({
...prev,
[index]: newError,
}))
}
onBlur={() => setFieldTouched(close, true, true)}
onChange={(dt) => {
const openTime = hour.open
? dayjs(hour.open, "HH:mm:ss")
: null;
const closeTime =
dt && dt.isValid() ? dt : null;

if (openTime && closeTime) {
if (closeTime.isBefore(openTime)) {
setCloseTimeErrorss((prev) => ({
...prev,
[index]: "minTime",
}));
} else if (closeTime.isSame(openTime)) {
setCloseTimeErrorss((prev) => ({
...prev,
[index]: "equalTime",
}));
} else {
setCloseTimeErrorss((prev) => {
const copy = { ...prev };
delete copy[index];
return copy;
});
}
}

setFieldValue(
close,
dt && dt.isValid()
? dt.format("HH:mm:ss")
: ""
closeTime ? closeTime.format("HH:mm:ss") : ""
);
setFieldTouched(close, true, true);
}}
onClose={(e) => {
setFieldTouched(close, true, true);
}}
onClose={() => setFieldTouched(close, true, true)}
slotProps={{
textField: {
placeholder: "Enter closing time...",
id: "closeTime",
variant: "outlined",
helperText:
errorClose && touchedClose
? "Closing time is required"
: !isMobile && " ",
error: errorClose && touchedClose,
helperText: getCloseTimeErrorsMessage(index)
? getCloseTimeErrorsMessage(index)
: errorClose && touchedClose
? "Closing time is required"
: !isMobile && " ",
error:
!!closeTimeErrorss[index] ||
(errorClose && touchedClose),
},
}}
sx={{
Expand All @@ -272,6 +319,7 @@ const OpenTimeForm = ({
}}
/>
</Grid>

<Grid
item
xs={2}
Expand Down Expand Up @@ -320,6 +368,10 @@ const OpenTimeForm = ({
</div>
)}
</FieldArray>

{typeof errors.hours === "string" && (
<FormHelperText error>{errors.hours}</FormHelperText>
)}
</CardContent>
</Card>
);
Expand Down
51 changes: 45 additions & 6 deletions client/src/components/Admin/OrganizationEdit.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,47 @@ const validationSchema = Yup.object().shape({
zip: Yup.string().required("Zip code is required"),
latitude: Yup.number()
.required("Latitude is required")
.min(Number(DEFAULT_VIEWPORTS[TENANT_ID].bbox.split(",")[1]))
.max(Number(DEFAULT_VIEWPORTS[TENANT_ID].bbox.split(",")[3])),
.test(
"latitude-range",
`Latitude must be between ${Number(
DEFAULT_VIEWPORTS[TENANT_ID].bbox.split(",")[1]
)} and ${Number(DEFAULT_VIEWPORTS[TENANT_ID].bbox.split(",")[3])}`,
(value) => {
const minLat = Number(DEFAULT_VIEWPORTS[TENANT_ID].bbox.split(",")[1]);
const maxLat = Number(DEFAULT_VIEWPORTS[TENANT_ID].bbox.split(",")[3]);
return value >= minLat && value <= maxLat;
}
),
longitude: Yup.number()
.required("Longitude is required")
.min(Number(DEFAULT_VIEWPORTS[TENANT_ID].bbox.split(",")[0]))
.max(Number(DEFAULT_VIEWPORTS[TENANT_ID].bbox.split(",")[2])),
.test(
"longitude-range",
`Longitude must be between ${Number(
DEFAULT_VIEWPORTS[TENANT_ID].bbox.split(",")[0]
)} and ${Number(DEFAULT_VIEWPORTS[TENANT_ID].bbox.split(",")[2])}`,
(value) => {
const minLng = Number(DEFAULT_VIEWPORTS[TENANT_ID].bbox.split(",")[0]);
const maxLng = Number(DEFAULT_VIEWPORTS[TENANT_ID].bbox.split(",")[2]);
return value >= minLng && value <= maxLng;
}
),
email: Yup.string().email("Invalid email address format"),
hours: Yup.array().of(HourSchema),
hours: Yup.array()
.of(HourSchema)
.test("no-duplicate-hours", function (value) {
const seen = new Set();
for (const item of value) {
const key = `${item.weekOfMonth}-${item.dayOfWeek}-${item.open}-${item.close}`;
if (seen.has(key)) {
return this.createError({
path: "hours",
message: "Duplicate business hours are not allowed",
});
}
seen.add(key);
}
return true;
}),
instagram: Yup.string()
.matches(INSTAGRAM_REGEX, "Please enter a valid Instagram URL.")
.nullable()
Expand Down Expand Up @@ -391,6 +424,12 @@ const OrganizationEdit = (props) => {
} catch (error) {
if (isSubmitClicked) {
setSubmitClicked(false);
setToast({
message:
"Please fix the errors in the form before save progress.",
type: "error",
});

const errors = error.inner.reduce(
(errors, { path, message }) => ({
...errors,
Expand Down Expand Up @@ -431,6 +470,7 @@ const OrganizationEdit = (props) => {
navigate(nextUrl);
}
})

.catch((err) => {
setToast({
message:
Expand All @@ -440,7 +480,6 @@ const OrganizationEdit = (props) => {
setSubmitting(false);
});
} else {
console.log("new stakeholder data: ", values);
return stakeholderService
.post({ ...values, loginId: user.id })
.then((response) => {
Expand Down
41 changes: 28 additions & 13 deletions client/src/components/UI/Toast.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Snackbar } from "@mui/material";
import { Snackbar, Alert } from "@mui/material";
import { useToasterContext } from "../../contexts/toasterContext";
import { IconButton } from "./StandardButton";
import { success, error, secondary, linkText } from "../../theme/palette";

const Toast = () => {
const { toast, setToast } = useToasterContext();
Expand All @@ -12,6 +13,20 @@ const Toast = () => {

setToast(false);
};
const getToastColor = (type) => {
switch (type) {
case "warning":
return secondary;
case "info":
return linkText;
case "error":
return error;
case "success":
return success;
default:
return linkText;
}
};

return toast.message ? (
<Snackbar
Expand All @@ -22,18 +37,18 @@ const Toast = () => {
open={!!toast}
autoHideDuration={toast.duration || 4000}
onClose={handleSnackbarClose}
ContentProps={{
"aria-describedby": "message-id",
}}
message={<span id="message-id">{toast.message}</span>}
action={[
<IconButton
key={toast.message}
icon="close"
onClick={handleSnackbarClose}
/>,
]}
/>
>
<Alert
severity={toast.type || "info"}
onClose={handleSnackbarClose}
sx={{
backgroundColor: getToastColor(toast.type),
color: "white",
}}
>
{toast.message}
</Alert>
</Snackbar>
) : null;
};

Expand Down
10 changes: 7 additions & 3 deletions client/src/contexts/toasterContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ interface ToastProps {

export type ToastContextProps = {
toast: {
message: string;
message: string;
type?: "info" | "warning" | "success" | "error";
};
setToast: React.Dispatch<
React.SetStateAction<{
Expand All @@ -23,12 +24,15 @@ export type ToastContextProps = {
const initialState: ToastContextProps = {
toast: {
message: "",
type: "info",
},
setToast: () => {}
setToast: () => {},
};

export const ToasterContext = createContext<ToastContextProps>(initialState);
export const ToasterProvider = ({ children }: ToastProps) => {
const [toast, setToast] = useState({ message: "" });

const [toast, setToast] = useState<{ message: string; type?: "info" | "warning" | "success" | "error" }>({ message: "", type: "info" });

const value = useMemo(() => {
return {
Expand Down
Loading
Loading