Skip to content

Commit

Permalink
Merge pull request #130 from daodaoedu/v2
Browse files Browse the repository at this point in the history
feat(group): filter out invalid data
  • Loading branch information
JohnsonMao authored Nov 16, 2024
2 parents af98b59 + 27149f7 commit c98462a
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 44 deletions.
1 change: 1 addition & 0 deletions components/Group/Form/Fields/AreaCheckbox.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ export default function AreaCheckbox({
control={physicalAreaControl}
/>
</Box>
{isPhysicalArea && !physicalAreaValue && <span className="ml-28 text-alert">請選擇地點</span>}
<div>
<FormControlLabel
control={<Checkbox onClick={() => handleCheckboxChange('線上')} />}
Expand Down
8 changes: 4 additions & 4 deletions components/Group/Form/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,13 +99,13 @@ export default function GroupForm({
label="揪團類型"
name="activityCategory"
handleValues={(action, value, activityCategory) => {
if (action === 'add' && value === 'Other') {
return ['Other'];
if (action === 'add' && value === '其他') {
return ['其他'];
}
if (action === 'remove' && !activityCategory.length) {
return ['Other'];
return ['其他'];
}
return activityCategory.filter((item) => item !== 'Other');
return activityCategory.filter((item) => item !== '其他');
}}
control={control}
value={values.activityCategory}
Expand Down
105 changes: 67 additions & 38 deletions components/Group/Form/useGroupForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import dayjs from 'dayjs';
import { useEffect, useRef, useState } from 'react';
import { useSelector } from 'react-redux';
import { ZodType, z } from 'zod';
import { useSnackbar } from '@/contexts/Snackbar';
import { CATEGORIES } from '@/constants/category';
import { AREAS } from '@/constants/areas';
import { EDUCATION_STEP } from '@/constants/member';
Expand Down Expand Up @@ -57,7 +58,9 @@ const rules = {
participator: z
.string()
.regex(/^(100|[1-9]?\d)$/, '請輸入整數,需大於 0,不可超過 100'),
area: z.array(z.string()).min(1, '請選擇地點'),
area: z
.array(z.enum(AREAS.concat({ label: '待討論' }).map(({ label }) => label)))
.min(1, '請選擇地點'),
time: z.string().max(50, '請勿輸入超過 50 字'),
partnerStyle: z
.string()
Expand All @@ -83,12 +86,19 @@ export default function useGroupForm(defaultValue) {
const [isDirty, setIsDirty] = useState(false);
const me = useSelector((state) => state.user);
const notLogin = !me?._id;
const [values, setValues] = useState({
const [values, setValues] = useState(() => ({
...INITIAL_VALUES,
...defaultValue,
...Object.fromEntries(
Object.entries(rules).map(([key, rule]) => [
key,
rule.safeParse(defaultValue[key])?.data || INITIAL_VALUES[key],
])
),
userId: me?._id,
});
}));
const [errors, setErrors] = useState({});
const { pushSnackbar } = useSnackbar();
const refs = useRef({});
const schema = z.object(rules);

Expand Down Expand Up @@ -119,15 +129,56 @@ export default function useGroupForm(defaultValue) {
onBlur,
};

const removePhoto = (url) => {
const pathArray = url.split('/');
fetch(`${BASE_URL}/image/${pathArray[pathArray.length - 1]}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${me.token}`,
},
});
};

const uploadPhoto = async (oldPhoto, newPhoto) => {
if (oldPhoto) {
removePhoto(oldPhoto);
}
if (newPhoto instanceof Blob) {
const formData = new FormData();

formData.append('file', newPhoto);

try {
const response = await fetch(`${BASE_URL}/image`, {
method: 'POST',
headers: {
Authorization: `Bearer ${me.token}`,
},
body: formData,
});
const data = await response.json();

return typeof data.url === 'string' ? data.url : '';
} catch {
return '';
}
} else {
return '';
}
};

const handleSubmit = (onValid) => async () => {
if (!schema.safeParse(values).success) {
const result = schema.safeParse(values);

if (!result.success) {
let isFocus = false;
const updatedErrors = Object.fromEntries(
Object.entries(rules).map(([key, rule]) => {
const errorMessage = rule.safeParse(values[key]).error?.issues?.[0]
?.message;

if (errorMessage && !isFocus) {
if (errorMessage && !isFocus && refs.current[key]) {
isFocus = true;
refs.current[key]?.focus();
}
Expand All @@ -136,47 +187,25 @@ export default function useGroupForm(defaultValue) {
}),
);
setErrors(updatedErrors);
if (!isFocus) {
pushSnackbar({
message: Object.values(updatedErrors)[0],
vertical: 'top',
horizontal: 'center',
type: 'error',
});
}
return;
}

if (values.originPhotoURL === values.photoURL) {
onValid(values);
onValid(result.data);
return;
}

if (values.originPhotoURL) {
const pathArray = values.originPhotoURL.split('/');
fetch(`${BASE_URL}/image/${pathArray[pathArray.length - 1]}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${me.token}`,
},
});
}

let photoURL = '';
const photoURL = await uploadPhoto(values.originPhotoURL, values.photoURL);

if (values.photoURL instanceof Blob) {
const formData = new FormData();

formData.append('file', values.photoURL);

try {
photoURL = await fetch(`${BASE_URL}/image`, {
method: 'POST',
headers: {
Authorization: `Bearer ${me.token}`,
},
body: formData,
})
.then((response) => response.json())
.then((data) => data.url);
} catch {
photoURL = '';
}
}
onValid({ ...values, photoURL });
onValid({ ...result.data, photoURL });
};

useEffect(() => {
Expand Down
6 changes: 4 additions & 2 deletions contexts/Snackbar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ function CloseButton({ onClick }) {
export default function SnackbarProvider({ children }) {
const [queue, setQueue] = useState([]);

const pushSnackbar = ({ message }) =>
const pushSnackbar = ({ message, type, vertical = 'bottom', horizontal = 'left' }) =>
new Promise((resolve) => {
setQueue((pre) => [
...pre,
{ id: Math.random(), open: true, message, resolve },
{ id: Math.random(), open: true, message, resolve, type, vertical, horizontal },
]);
});

Expand All @@ -49,7 +49,9 @@ export default function SnackbarProvider({ children }) {
{queue.map((data) => (
<MuiSnackbar
key={data.id}
anchorOrigin={{ vertical: data.vertical, horizontal: data.horizontal }}
open={data.open}
sx={{ '.MuiSnackbarContent-root': { backgroundColor: data.type === 'error' ? '#f33' : '#333' } }}
message={data.message}
onClose={closeSnackbar(data.id)}
action={<CloseButton onClick={closeSnackbar(data.id)} />}
Expand Down

0 comments on commit c98462a

Please sign in to comment.