mirror of
https://github.com/Unleash/unleash.git
synced 2025-04-24 01:18:01 +02:00
https://linear.app/unleash/issue/2-3321/improve-release-template-name-uniqueness-error-response-messages https://linear.app/unleash/issue/2-3285/milestone-name-uniqueness Slightly improves UX in our release plan template form validation. 
127 lines
3.5 KiB
TypeScript
127 lines
3.5 KiB
TypeScript
import { useReleasePlanTemplates } from 'hooks/api/getters/useReleasePlanTemplates/useReleasePlanTemplates';
|
|
import { useOptionalPathParam } from 'hooks/useOptionalPathParam';
|
|
import type { IReleasePlanMilestonePayload } from 'interfaces/releasePlans';
|
|
import { useEffect, useState } from 'react';
|
|
import { v4 as uuidv4 } from 'uuid';
|
|
|
|
export interface IExtendedMilestonePayload
|
|
extends IReleasePlanMilestonePayload {
|
|
startExpanded?: boolean;
|
|
}
|
|
|
|
export const useTemplateForm = (
|
|
initialName = '',
|
|
initialDescription = '',
|
|
initialMilestones: IExtendedMilestonePayload[] = [
|
|
{ id: uuidv4(), name: 'Milestone 1', sortOrder: 0 },
|
|
],
|
|
) => {
|
|
const templateId = useOptionalPathParam('templateId');
|
|
const { templates } = useReleasePlanTemplates();
|
|
|
|
const [name, setName] = useState(initialName);
|
|
const [description, setDescription] = useState(initialDescription);
|
|
const [milestones, setMilestones] = useState(initialMilestones);
|
|
const [errors, setErrors] = useState({});
|
|
|
|
useEffect(() => {
|
|
setName(initialName);
|
|
}, [initialName]);
|
|
|
|
useEffect(() => {
|
|
setDescription(initialDescription);
|
|
}, [initialDescription]);
|
|
|
|
useEffect(() => {
|
|
setMilestones(initialMilestones);
|
|
}, [initialMilestones.length]);
|
|
|
|
const validate = () => {
|
|
let valid = true;
|
|
|
|
if (name.length === 0) {
|
|
setErrors((prev) => ({ ...prev, name: 'Name can not be empty.' }));
|
|
valid = false;
|
|
}
|
|
|
|
if (
|
|
templates.some(
|
|
(template) =>
|
|
template.name === name && template.id !== templateId,
|
|
)
|
|
) {
|
|
setErrors((prev) => ({
|
|
...prev,
|
|
name: 'A template with this name already exists.',
|
|
}));
|
|
valid = false;
|
|
}
|
|
|
|
if (milestones.length === 0) {
|
|
setErrors((prev) => ({
|
|
...prev,
|
|
milestones: 'At least one milestone is required.',
|
|
}));
|
|
valid = false;
|
|
}
|
|
|
|
const errors: Record<string, string> = {};
|
|
const nameSet = new Set();
|
|
milestones.forEach((m) => {
|
|
if (!m.name || m.name.length === 0) {
|
|
errors[m.id] = 'Milestone must have a valid name.';
|
|
errors[`${m.id}_name`] = 'Milestone must have a valid name.';
|
|
}
|
|
|
|
if (!m.strategies || m.strategies.length === 0) {
|
|
errors[m.id] = 'Milestone must have at least one strategy.';
|
|
}
|
|
|
|
if (nameSet.has(m.name)) {
|
|
errors[m.id] = 'Milestone names must be unique.';
|
|
} else if (m.name) {
|
|
nameSet.add(m.name);
|
|
}
|
|
});
|
|
|
|
if (Object.keys(errors).length > 0) {
|
|
setErrors((prev) => ({
|
|
...prev,
|
|
...errors,
|
|
milestones:
|
|
'All milestones must have unique names and at least one strategy each.',
|
|
}));
|
|
valid = false;
|
|
}
|
|
|
|
return valid;
|
|
};
|
|
|
|
const clearErrors = () => {
|
|
setErrors({});
|
|
};
|
|
|
|
const getTemplatePayload = () => {
|
|
return {
|
|
name,
|
|
description,
|
|
milestones: milestones.map(
|
|
({ startExpanded, ...milestone }) => milestone,
|
|
),
|
|
};
|
|
};
|
|
|
|
return {
|
|
name,
|
|
setName,
|
|
description,
|
|
setDescription,
|
|
milestones,
|
|
setMilestones,
|
|
errors,
|
|
clearErrors,
|
|
validate,
|
|
getTemplatePayload,
|
|
};
|
|
};
|