-
-
Notifications
You must be signed in to change notification settings - Fork 729
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: edit release plan template (#8723)
- Loading branch information
Showing
10 changed files
with
408 additions
and
31 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
140 changes: 140 additions & 0 deletions
140
frontend/src/component/releases/ReleasePlanTemplate/EditReleasePlanTemplate.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
import { useUiFlag } from 'hooks/useUiFlag'; | ||
import { usePageTitle } from 'hooks/usePageTitle'; | ||
import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; | ||
import { useReleasePlanTemplate } from 'hooks/api/getters/useReleasePlanTemplates/useReleasePlanTemplate'; | ||
import FormTemplate from 'component/common/FormTemplate/FormTemplate'; | ||
import { useTemplateForm } from '../hooks/useTemplateForm'; | ||
import { TemplateForm } from './TemplateForm'; | ||
import { Box, Button, Card, styled } from '@mui/material'; | ||
import { UpdateButton } from 'component/common/UpdateButton/UpdateButton'; | ||
import { ADMIN } from '@server/types/permissions'; | ||
import { useNavigate } from 'react-router-dom'; | ||
import { formatUnknownError } from 'utils/formatUnknownError'; | ||
import useToast from 'hooks/useToast'; | ||
import useReleasePlanTemplatesApi from 'hooks/api/actions/useReleasePlanTemplatesApi/useReleasePlanTemplatesApi'; | ||
|
||
const StyledForm = styled('form')(() => ({ | ||
display: 'flex', | ||
flexDirection: 'column', | ||
height: '100%', | ||
})); | ||
|
||
const StyledMilestoneCard = styled(Card)(({ theme }) => ({ | ||
marginTop: theme.spacing(2), | ||
display: 'flex', | ||
flexDirection: 'column', | ||
justifyContent: 'space-between', | ||
boxShadow: 'none', | ||
border: `1px solid ${theme.palette.divider}`, | ||
[theme.breakpoints.down('sm')]: { | ||
justifyContent: 'center', | ||
}, | ||
transition: 'background-color 0.2s ease-in-out', | ||
backgroundColor: theme.palette.background.default, | ||
'&:hover': { | ||
backgroundColor: theme.palette.neutral.light, | ||
}, | ||
borderRadius: theme.shape.borderRadiusMedium, | ||
})); | ||
|
||
const StyledMilestoneCardBody = styled(Box)(({ theme }) => ({ | ||
padding: theme.spacing(3, 2), | ||
})); | ||
|
||
const StyledMilestoneCardTitle = styled('span')(({ theme }) => ({ | ||
fontWeight: theme.fontWeight.bold, | ||
fontSize: theme.fontSizes.bodySize, | ||
})); | ||
|
||
const StyledButtonContainer = styled('div')(() => ({ | ||
marginTop: 'auto', | ||
display: 'flex', | ||
justifyContent: 'flex-end', | ||
})); | ||
|
||
const StyledCancelButton = styled(Button)(({ theme }) => ({ | ||
marginLeft: theme.spacing(3), | ||
})); | ||
|
||
export const EditReleasePlanTemplate = () => { | ||
const releasePlansEnabled = useUiFlag('releasePlans'); | ||
const templateId = useRequiredPathParam('templateId'); | ||
const { template, loading, error, refetch } = | ||
useReleasePlanTemplate(templateId); | ||
usePageTitle(`Edit template: ${template.name}`); | ||
const navigate = useNavigate(); | ||
const { setToastApiError } = useToast(); | ||
const { updateReleasePlanTemplate } = useReleasePlanTemplatesApi(); | ||
const { | ||
name, | ||
setName, | ||
description, | ||
setDescription, | ||
errors, | ||
clearErrors, | ||
validate, | ||
getTemplatePayload, | ||
} = useTemplateForm(template.name, template.description); | ||
|
||
const handleCancel = () => { | ||
navigate('/release-management'); | ||
}; | ||
const handleSubmit = async (e: React.FormEvent) => { | ||
e.preventDefault(); | ||
clearErrors(); | ||
const isValid = validate(); | ||
if (isValid) { | ||
const payload = getTemplatePayload(); | ||
try { | ||
await updateReleasePlanTemplate({ | ||
...payload, | ||
id: templateId, | ||
milestones: template.milestones, | ||
}); | ||
navigate('/release-management'); | ||
} catch (error: unknown) { | ||
setToastApiError(formatUnknownError(error)); | ||
} | ||
} | ||
}; | ||
|
||
if (!releasePlansEnabled) { | ||
return null; | ||
} | ||
|
||
return ( | ||
<> | ||
<FormTemplate | ||
title={`Edit template ${template.name}`} | ||
description='Edit a release plan template that makes it easier for you and your team to release features.' | ||
> | ||
<StyledForm onSubmit={handleSubmit}> | ||
<TemplateForm | ||
name={name} | ||
setName={setName} | ||
description={description} | ||
setDescription={setDescription} | ||
errors={errors} | ||
clearErrors={clearErrors} | ||
/> | ||
|
||
{template.milestones.map((milestone) => ( | ||
<StyledMilestoneCard key={milestone.id}> | ||
<StyledMilestoneCardBody> | ||
<StyledMilestoneCardTitle> | ||
{milestone.name} | ||
</StyledMilestoneCardTitle> | ||
</StyledMilestoneCardBody> | ||
</StyledMilestoneCard> | ||
))} | ||
<StyledButtonContainer> | ||
<UpdateButton name='template' permission={ADMIN} /> | ||
<StyledCancelButton onClick={handleCancel}> | ||
Cancel | ||
</StyledCancelButton> | ||
</StyledButtonContainer> | ||
</StyledForm> | ||
</FormTemplate> | ||
</> | ||
); | ||
}; |
57 changes: 57 additions & 0 deletions
57
frontend/src/component/releases/ReleasePlanTemplate/TemplateForm.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import Input from 'component/common/Input/Input'; | ||
import { styled } from '@mui/material'; | ||
|
||
const StyledInputDescription = styled('p')(({ theme }) => ({ | ||
marginBottom: theme.spacing(1), | ||
})); | ||
|
||
const StyledInput = styled(Input)(({ theme }) => ({ | ||
width: '100%', | ||
marginBottom: theme.spacing(2), | ||
})); | ||
|
||
interface ITemplateForm { | ||
name: string; | ||
setName: React.Dispatch<React.SetStateAction<string>>; | ||
description: string; | ||
setDescription: React.Dispatch<React.SetStateAction<string>>; | ||
errors: { [key: string]: string }; | ||
clearErrors: () => void; | ||
} | ||
|
||
export const TemplateForm: React.FC<ITemplateForm> = ({ | ||
name, | ||
setName, | ||
description, | ||
setDescription, | ||
errors, | ||
clearErrors, | ||
}) => { | ||
return ( | ||
<> | ||
<StyledInputDescription> | ||
What would you like to call your template? | ||
</StyledInputDescription> | ||
<StyledInput | ||
label='Template name' | ||
value={name} | ||
onChange={(e) => setName(e.target.value)} | ||
error={Boolean(errors.name)} | ||
errorText={errors.name} | ||
onFocus={() => clearErrors()} | ||
autoFocus | ||
/> | ||
<StyledInputDescription> | ||
What's the purpose of this template? | ||
</StyledInputDescription> | ||
<StyledInput | ||
label='Template description (optional)' | ||
value={description} | ||
onChange={(e) => setDescription(e.target.value)} | ||
error={Boolean(errors.description)} | ||
errorText={errors.description} | ||
onFocus={() => clearErrors()} | ||
/> | ||
</> | ||
); | ||
}; |
Oops, something went wrong.