Skip to content

Commit

Permalink
Merge pull request #196 from ruby10127130/feature/marathon/stepperBar
Browse files Browse the repository at this point in the history
Feature/marathon/stepper bar
  • Loading branch information
JohnsonMao authored Dec 29, 2024
2 parents 54889b1 + f5bde33 commit c8cc7b5
Show file tree
Hide file tree
Showing 17 changed files with 272 additions and 104 deletions.
141 changes: 99 additions & 42 deletions components/Marathon/SignUp/MarathonForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ export default function MarathonForm({
const reduxDispatch = useDispatch();
const [hasLoaded, setHasLoaded] = useState(false);
const [errors, setErrors] = useState({});
const [hasErrors, setHasErrors] = useState(false);
const marathonState = useSelector((state) => { return state.marathon; });
const localStorgeStored = window.localStorage.getItem('newMarathon');
const editingMarathon = localStorgeStored ? JSON.parse(localStorgeStored) : null;
Expand All @@ -87,20 +86,6 @@ export default function MarathonForm({
};
const [newMarathon, setNewMarathon] = useReducer(marathonFormReducer, initialState());

const onNextStep = () => {
if (hasErrors) {
toast.error('請修正錯誤');
} else {
reduxDispatch(updateNewMarathon(newMarathon));
setCurrentStep(currentStep + 1);
}
};

const onPrevStep = () => {
reduxDispatch(updateNewMarathon(newMarathon));
setCurrentStep(currentStep - 1);
};

const validators = {
required: (value) => {
return value.trim().length > 0;
Expand All @@ -110,50 +95,74 @@ export default function MarathonForm({
return names.length === milestonesLength;
}
};
const validateRules = {

const marathonDataMap = {
title: {
dispatchType: 'UPDATE_FIELD',
dispatchKey: 'title',
validate: validators.required,
message: '請填寫表格',
},
description: {
dispatchType: 'UPDATE_FIELD',
dispatchKey: 'description',
validate: validators.required,
message: '請填寫計畫敘述',
},
motivationDescription: {
dispatchType: 'UPDATE_MOTIVATION_FIELD',
dispatchKey: 'description',
validate: validators.required,
message: '請填寫學習動機',
},
outcomesDescription: {
dispatchType: 'UPDATE_OUTCOMES_FIELD',
dispatchKey: 'description',
validate: validators.required,
message: '請填寫學習成果',
},
goals: {
dispatchType: 'UPDATE_FIELD',
dispatchKey: 'goals',
validate: validators.required,
message: '請填寫學習目標',
},
content: {
dispatchType: 'UPDATE_FIELD',
dispatchKey: 'content',
validate: validators.required,
message: '請填寫學習內容',
},
milestonesName: {
dispatchType: 'UPDATE_FIELD',
dispatchKey: 'milestones',
validate: (value) => {
return validators.allMilestonesNameRequired(value, newMarathon.milestones?.length);
},
message: '請填寫每週/隔週里程碑目標',
},
strategiesDescription: {
dispatchType: 'UPDATE_STRATEGIES_FIELD',
dispatchKey: 'description',
validate: validators.required,
message: '請填寫學習策略',
},
resources: {
dispatchType: 'UPDATE_FIELD',
dispatchKey: 'resources',
validate: validators.required,
message: '請填寫學習資源'
}
};

/**
* @param {string} name - The name of the field to validate.
* @param {*} input - The input value to validate.
* @returns {boolean} - Returns true if the input value passes validation, otherwise false.
*/
const handleValidate = (name, input) => {
const validateResult = validateRules[name]?.validate(input);
const errorMessage = validateRules[name]?.message;
const validateResult = marathonDataMap[name]?.validate(input);
const errorMessage = marathonDataMap[name]?.message;
if (validateResult) {
setErrors((prevErrors) =>
Object.fromEntries(Object.entries(prevErrors).filter(([key]) => key !== name))
Expand All @@ -168,22 +177,75 @@ export default function MarathonForm({
}
return validateResult;
};
const handleValidateAll = () => {
const newErrors = {};
let isValid = true;
Object.entries(marathonDataMap).forEach(([name, fieldData]) => {
const { validate, message } = fieldData;
let input;
if (validate) {
switch (name) {
case 'milestonesName':
input = newMarathon.milestones;
break;
case 'motivationDescription':
input = newMarathon.motivation?.description;
break;
case 'strategiesDescription':
input = newMarathon.strategies?.description;
break;
case 'outcomesDescription':
input = newMarathon.outcomes?.description;
break;
default:
input = newMarathon[name];
break;
}
const validationPassed = validate(input);

if (!validationPassed) {
newErrors[name] = { message: message || "驗證失敗" };
isValid = false;
}
}
});
setErrors(newErrors);
return isValid;
};
const handleOnChange = (
stateDispatchType,
stateDispatchKey,
name,
value,
validateName,
) => {
if (stateDispatchType && stateDispatchKey) {
const type = marathonDataMap[name]?.dispatchType;
const key = marathonDataMap[name]?.dispatchKey;

if (type && key) {
setNewMarathon({
type: stateDispatchType,
payload: { key: stateDispatchKey, value }
type,
payload: { key, value }
});
}
if (validateName) {
handleValidate(validateName, value);

if (name) {
handleValidate(name, value);
}
};

const onNextStep = () => {
const isValid = handleValidateAll();
if (!isValid) {
toast.error('請修正錯誤');
} else {
reduxDispatch(updateNewMarathon(newMarathon));
setCurrentStep(currentStep + 1);
}
};

const onPrevStep = () => {
reduxDispatch(updateNewMarathon(newMarathon));
setCurrentStep(currentStep - 1);
};

useEffect(() => {
setHasLoaded(true);
const storagedErrors = getMarathonErrorsStorage().get();
Expand All @@ -200,11 +262,6 @@ export default function MarathonForm({

useEffect(() => {
getMarathonErrorsStorage().set(errors);
if (Object.keys(errors).length) {
setHasErrors(true);
} else {
setHasErrors(false);
}
}, [errors]);

return (
Expand Down Expand Up @@ -245,7 +302,7 @@ export default function MarathonForm({
title="學習主題名稱"
value={newMarathon.title || ''}
onChange={(e) => {
handleOnChange('UPDATE_FIELD', 'title', e.target.value, 'title');
handleOnChange('title', e.target.value);
}}
sx={{
mb: '8px',
Expand Down Expand Up @@ -278,7 +335,7 @@ export default function MarathonForm({
<StyledTextareaAutosize
value={newMarathon.description || ''}
onChange={(e) => {
handleOnChange('UPDATE_FIELD', 'description', e.target.value, 'description');
handleOnChange('description', e.target.value);
}}
placeholder="範例:因為對剪影片和當 Youtuber 有興趣,我預計會研究搞笑型 Youtuber 的影片腳本與剪輯方式、拍攝我日常生活及練習剪輯,並建立 Youtube 頻道上傳影片。希望能藉此了解如何當一位 Youtuber。"
className={errors.description ? 'error' : ''}
Expand Down Expand Up @@ -323,15 +380,15 @@ export default function MarathonForm({
'生活發生變化',
'影響社會',
'受群體影響',
'其他:請在下方補上其他原因,並詳細說明動機'
'其他:請於下方撰寫'
]}
type="UPDATE_MOTIVATION_FIELD"
onChange={setNewMarathon}
selectedItems={newMarathon?.motivation?.tags || []}
/>
<StyledTextareaAutosize
onChange={(e) => {
handleOnChange('UPDATE_MOTIVATION_FIELD', 'description', e.target.value, 'motivationDescription');
handleOnChange('motivationDescription', e.target.value);
}}
className={errors.motivationDescription ? 'error' : ''}
value={newMarathon?.motivation?.description || ''}
Expand Down Expand Up @@ -360,7 +417,7 @@ export default function MarathonForm({
</Typography>
<StyledTextareaAutosize
onChange={(e) => {
handleOnChange('UPDATE_FIELD', 'goals', e.target.value, 'goals');
handleOnChange('goals', e.target.value);
}}
value={newMarathon.goals || ''}
placeholder="範例:
Expand Down Expand Up @@ -391,7 +448,7 @@ export default function MarathonForm({
</Typography>
<StyledTextareaAutosize
onChange={(e) => {
handleOnChange('UPDATE_FIELD', 'content', e.target.value, 'content');
handleOnChange('content', e.target.value);
}}
value={newMarathon.content || ''}
placeholder="範例:
Expand Down Expand Up @@ -442,15 +499,15 @@ export default function MarathonForm({
"田野調查",
"訪談",
"問卷調查",
"其他:請在下方補上其他原因,並詳細說明動機"
"其他:請於下方撰寫"
]}
type="UPDATE_STRATEGIES_FIELD"
onChange={setNewMarathon}
selectedItems={newMarathon?.strategies?.tags || []}
/>
<StyledTextareaAutosize
onChange={(e) => {
handleOnChange('UPDATE_STRATEGIES_FIELD', 'description', e.target.value, 'strategiesDescription');
handleOnChange('strategiesDescription', e.target.value);
}}
value={newMarathon?.strategies?.description || ''}
placeholder="範例:我預計會研究影片腳本、拍攝與剪輯方式,接著了解拍攝、剪輯與Youtube頻道經營,並同時練習拍攝與剪輯,開始經營頻道。我會用notion整理我收集到的資料以及筆記。"
Expand Down Expand Up @@ -483,7 +540,7 @@ export default function MarathonForm({
placeholder="範例:YouTube 創作者的實用資源"
value={newMarathon.resources || ''}
onChange={(e) => {
handleOnChange('UPDATE_FIELD', 'resources', e.target.value, 'resources');
handleOnChange('resources', e.target.value);
}}
className={errors.resources ? 'error' : 'warning'}
endAdornment={errors.resources ? <ClearIcon sx={{ color: '#EF5364' }} /> : null}
Expand Down Expand Up @@ -543,15 +600,15 @@ export default function MarathonForm({
"舉辦活動",
"開課",
"參與競賽",
"其他:請在下方補上其他原因,並詳細說明動機"
"其他:請於下方撰寫"
]}
type="UPDATE_OUTCOMES_FIELD"
onChange={setNewMarathon}
selectedItems={newMarathon?.outcomes?.tags || []}
/>
<StyledTextareaAutosize
onChange={(e) => {
handleOnChange('UPDATE_OUTCOMES_FIELD', 'description', e.target.value, 'outcomesDescription');
handleOnChange('outcomesDescription', e.target.value);
}}
value={newMarathon?.outcomes?.description || ''}
placeholder="範例:我預計會架設一個Youtube頻道,並上傳至少5支影片,並整理觀眾回饋與相關數據。"
Expand Down
6 changes: 3 additions & 3 deletions components/Marathon/SignUp/MilestoneGroup.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ export default function MilestoneGroup({
// if change frequency, clear all data
setFrequency(e.target.value);
const changedMilestones = calculateMilestones(startDate, e.target.value, []);
onChangeHandler('UPDATE_FIELD', 'milestones', changedMilestones, 'milestonesName');
onChangeHandler('milestonesName', changedMilestones);
};

const handleEndDate = (/** fakeDate */) => {
Expand All @@ -115,7 +115,7 @@ export default function MilestoneGroup({
const changedMilestones = milestones.map((item) => {
return (item._tempId === newMilestone._tempId ? newMilestone : item);
});
onChangeHandler('UPDATE_FIELD', 'milestones', changedMilestones, 'milestonesName');
onChangeHandler('milestonesName', changedMilestones);
};
useEffect(() => {
const weeklyMilestonesLength = 22;
Expand All @@ -132,7 +132,7 @@ export default function MilestoneGroup({
}

if (!isDisabled) {
onChangeHandler('UPDATE_FIELD', 'milestones', initMilestones, 'milestonesName');
onChangeHandler('milestonesName', initMilestones);
}
}, []);

Expand Down
13 changes: 8 additions & 5 deletions components/Marathon/SignUp/StepperBar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import { Box } from '@mui/material';
import Stepper from '@mui/material/Stepper';
import Step from '@mui/material/Step';
import StepLabel from '@mui/material/StepLabel';
import { useNavigation } from '@/contexts/Navigation';

export const StyledSaveBar = styled(Box)`
export const StyledStepperBar = styled(Box)`
background-color: #FFF;
padding: 15px 6.9vw;
display: flex;
Expand All @@ -16,7 +17,7 @@ export const StyledSaveBar = styled(Box)`
box-shadow: 0px 2px 4px -1px rgba(0,0,0,0.2),0px 4px 5px 0px rgba(0,0,0,0.14),0px 1px 10px 0px rgba(0,0,0,0.12);
position: sticky;
z-index: 99;
top: 118px;
top: ${(props) => (props.showPromotionBar ? '108px' : '64px')};
width: 100%;
left: 0;
Expand Down Expand Up @@ -58,9 +59,11 @@ export const StyledSaveBar = styled(Box)`
}
`;

export default function SaveBar({ currentStep }) {
export default function StepperBar({ currentStep }) {
const { showPromotionBar } = useNavigation();

return (
<StyledSaveBar>
<StyledStepperBar showPromotionBar={showPromotionBar}>
<div className="top">
<h2>申請參加學習馬拉松</h2>
</div>
Expand All @@ -77,6 +80,6 @@ export default function SaveBar({ currentStep }) {
</Step>
</Stepper>
</div>
</StyledSaveBar>
</StyledStepperBar>
);
}
Loading

0 comments on commit c8cc7b5

Please sign in to comment.