fix: prevent accidental signatures (#1515)

![CleanShot 2024-12-06 at 03 30
39](https://github.com/user-attachments/assets/d47dc820-f19d-43b7-a60d-914fc9ab24b8)

![CleanShot 2024-12-06 at 03 32
34](https://github.com/user-attachments/assets/0db98735-8c91-469b-873c-adb19d0fff7b)
This commit is contained in:
Ephraim Duncan
2024-12-08 03:17:58 +00:00
committed by GitHub
parent a88ae1cc1e
commit dd162205fa
23 changed files with 443 additions and 292 deletions

View File

@ -61,6 +61,7 @@ export const AddSignatureFormPartial = ({
}: AddSignatureFormProps) => {
const { currentStep, totalSteps } = useStep();
const [validateUninsertedFields, setValidateUninsertedFields] = useState(false);
const [isSignatureValid, setIsSignatureValid] = useState(false);
// Refined schema which takes into account whether to allow an empty name or signature.
const refinedSchema = ZAddSignatureFormSchema.superRefine((val, ctx) => {
@ -336,9 +337,17 @@ export const AddSignatureFormPartial = ({
className="h-44 w-full"
defaultValue={field.value}
onBlur={field.onBlur}
onValidityChange={(isValid) => {
setIsSignatureValid(isValid);
if (!isValid) {
field.onChange(null);
}
}}
onChange={(value) => {
onFormValueChange(FieldType.SIGNATURE);
field.onChange(value);
if (isSignatureValid) {
onFormValueChange(FieldType.SIGNATURE);
field.onChange(value);
}
}}
/>
</CardContent>

View File

@ -97,6 +97,8 @@ export type SignaturePadProps = Omit<HTMLAttributes<HTMLCanvasElement>, 'onChang
disabled?: boolean;
allowTypedSignature?: boolean;
defaultValue?: string;
onValidityChange?: (isValid: boolean) => void;
minCoverageThreshold?: number;
};
export const SignaturePad = ({
@ -106,6 +108,8 @@ export const SignaturePad = ({
onChange,
disabled = false,
allowTypedSignature,
onValidityChange,
minCoverageThreshold = 0.01,
...props
}: SignaturePadProps) => {
const $el = useRef<HTMLCanvasElement>(null);
@ -134,6 +138,29 @@ export const SignaturePad = ({
} satisfies StrokeOptions;
}, []);
const checkSignatureValidity = () => {
if ($el.current) {
const ctx = $el.current.getContext('2d');
if (ctx) {
const imageData = ctx.getImageData(0, 0, $el.current.width, $el.current.height);
const data = imageData.data;
let filledPixels = 0;
const totalPixels = data.length / 4;
for (let i = 0; i < data.length; i += 4) {
if (data[i + 3] > 0) filledPixels++;
}
const filledPercentage = filledPixels / totalPixels;
const isValid = filledPercentage > minCoverageThreshold;
onValidityChange?.(isValid);
return isValid;
}
}
};
const onMouseDown = (event: MouseEvent | PointerEvent | TouchEvent) => {
if (event.cancelable) {
event.preventDefault();
@ -218,7 +245,6 @@ export const SignaturePad = ({
if (ctx) {
ctx.restore();
ctx.imageSmoothingEnabled = true;
ctx.imageSmoothingQuality = 'high';
ctx.fillStyle = selectedColor;
@ -230,7 +256,11 @@ export const SignaturePad = ({
ctx.fill(pathData);
});
onChange?.($el.current.toDataURL());
const isValidSignature = checkSignatureValidity();
if (isValidSignature) {
onChange?.($el.current.toDataURL());
}
ctx.save();
}
}