U5 — the gate everything else has to pass

continuous_integration.yaml: six blocking checks, then a separate
publish per environment so a Vite build never gets tagged for the
wrong one, then a call into last commit's deploy workflow. Along the
way: the lint list had drifted (two problems not in the requirement,
one already fixed), and the Umami-origin gate needed a variable pair
of its own since the backend's side of that comparison lives on the
host, not in CI. Pinned the two vulnerable packages while at it.
This commit is contained in:
2026-07-28 16:07:03 +02:00
parent bd2a963498
commit 9f4ae475e7
12 changed files with 691 additions and 36 deletions
@@ -1,4 +1,4 @@
import { useEffect, useState } from 'react';
import { useState } from 'react';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { toast } from 'sonner';
@@ -50,14 +50,14 @@ export function AddCmsInstanceDialog({ open, onOpenChange }: AddCmsInstanceDialo
mode: 'onTouched',
});
useEffect(() => {
if (!open) {
const handleOpenChange = (nextOpen: boolean) => {
if (!nextOpen) {
setServerError(null);
addInstance.reset();
reset();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [open]);
onOpenChange(nextOpen);
};
const onSubmit = handleSubmit(async (values) => {
setServerError(null);
@@ -71,7 +71,7 @@ export function AddCmsInstanceDialog({ open, onOpenChange }: AddCmsInstanceDialo
});
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<Dialog open={open} onOpenChange={handleOpenChange}>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle>{t('cms.add.title')}</DialogTitle>
+20 -14
View File
@@ -1,5 +1,5 @@
import { useEffect, useState } from 'react';
import { useForm, Controller } from 'react-hook-form';
import { useState } from 'react';
import { useForm, useWatch, Controller } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { toast } from 'sonner';
import { useTranslation } from 'react-i18next';
@@ -60,7 +60,6 @@ export function SetStatusDialog({ instance, open, onOpenChange }: SetStatusDialo
register,
handleSubmit,
control,
watch,
reset,
formState: { errors, isSubmitting },
} = useForm<SetStatusFormData>({
@@ -69,21 +68,28 @@ export function SetStatusDialog({ instance, open, onOpenChange }: SetStatusDialo
defaultValues: { status: 'Available', disableMessage: '' },
});
const selectedStatus = watch('status');
const selectedStatus = useWatch({ control, name: 'status' });
useEffect(() => {
if (!open || !instance) {
// Adjusts form values when the target instance changes, without an Effect
// (https://react.dev/learn/you-might-not-need-an-effect#adjusting-some-state-when-a-prop-changes).
const [syncedInstance, setSyncedInstance] = useState(instance);
if (instance !== syncedInstance) {
setSyncedInstance(instance);
if (instance) {
reset({ status: instance.status, disableMessage: instance.disableMessage ?? '' });
} else {
reset({ status: 'Available', disableMessage: '' });
}
}
const handleOpenChange = (nextOpen: boolean) => {
if (!nextOpen) {
setServerError(null);
updateStatus.reset();
reset({ status: 'Available', disableMessage: '' });
} else {
reset({
status: instance.status,
disableMessage: instance.disableMessage ?? '',
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [open, instance]);
onOpenChange(nextOpen);
};
const onSubmit = handleSubmit(async (values) => {
if (!instance) return;
@@ -100,7 +106,7 @@ export function SetStatusDialog({ instance, open, onOpenChange }: SetStatusDialo
});
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<Dialog open={open} onOpenChange={handleOpenChange}>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle>{t('cms.setStatus.title')}</DialogTitle>
+1
View File
@@ -29,4 +29,5 @@ function Badge({ className, variant, ...props }: BadgeProps) {
return <div className={cn(badgeVariants({ variant }), className)} {...props} />;
}
// eslint-disable-next-line react-refresh/only-export-components
export { Badge, badgeVariants };
@@ -1,4 +1,4 @@
import { useEffect, useState } from 'react';
import { useState } from 'react';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { toast } from 'sonner';
@@ -45,17 +45,6 @@ export function InviteUserDialog({ open, onOpenChange }: InviteUserDialogProps)
},
});
useEffect(() => {
if (!open) {
setStep(1);
setInviteLink(null);
setServerError(null);
inviteUser.reset();
reset();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [open]);
const {
register,
handleSubmit,
@@ -66,6 +55,17 @@ export function InviteUserDialog({ open, onOpenChange }: InviteUserDialogProps)
resolver: zodResolver(inviteUserSchema),
});
const handleOpenChange = (nextOpen: boolean) => {
if (!nextOpen) {
setStep(1);
setInviteLink(null);
setServerError(null);
inviteUser.reset();
reset();
}
onOpenChange(nextOpen);
};
const onSubmit = handleSubmit(async (values) => {
setServerError(null);
try {
@@ -84,7 +84,7 @@ export function InviteUserDialog({ open, onOpenChange }: InviteUserDialogProps)
};
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<Dialog open={open} onOpenChange={handleOpenChange}>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle>
+7 -3
View File
@@ -1,4 +1,4 @@
import { useState, useEffect } from 'react';
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { toast } from 'sonner';
import { Lock } from 'lucide-react';
@@ -35,12 +35,16 @@ export function SettingsPage() {
const [selectedMode, setSelectedMode] = useState<AvailabilityStatus>('Available');
const [reason, setReason] = useState('');
useEffect(() => {
// Adjusts local edit state when the fetched availability changes, without an Effect
// (https://react.dev/learn/you-might-not-need-an-effect#adjusting-some-state-when-a-prop-changes).
const [syncedAvailability, setSyncedAvailability] = useState(availability);
if (availability !== syncedAvailability) {
setSyncedAvailability(availability);
if (availability) {
setSelectedMode(availability.status);
setReason(availability.message ?? '');
}
}, [availability]);
}
const isMasterControlled = availability?.isMasterControlled ?? false;