From d08cda8b1557bfabf6df1cc21038d9f308a322ed Mon Sep 17 00:00:00 2001 From: Kacper Szarkiewicz Date: Sun, 18 Jan 2026 13:25:05 +0100 Subject: [PATCH 01/17] implement hold and submit button --- .../src/components/HoldButton/index.tsx | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 apps/frontend/src/components/HoldButton/index.tsx diff --git a/apps/frontend/src/components/HoldButton/index.tsx b/apps/frontend/src/components/HoldButton/index.tsx new file mode 100644 index 000000000..c7177abba --- /dev/null +++ b/apps/frontend/src/components/HoldButton/index.tsx @@ -0,0 +1,82 @@ +import { useCallback, useRef, useState } from "react"; +import { cn } from "../../helpers/cn"; + +interface HoldButtonProps { + children: React.ReactNode; + onComplete: () => void; + onHoldStart?: () => Promise | boolean; + duration?: number; + disabled?: boolean; + error?: boolean; + className?: string; + holdClassName?: string; +} + +export function HoldButton({ + children, + onComplete, + onHoldStart, + duration = 2000, + disabled = false, + error = false, + className, + holdClassName +}: HoldButtonProps) { + const [isHolding, setIsHolding] = useState(false); + const holdTimeoutRef = useRef | null>(null); + + const handlePointerDown = useCallback(async () => { + if (disabled) return; + + if (onHoldStart) { + const canProceed = await onHoldStart(); + if (!canProceed) return; + } + + setIsHolding(true); + holdTimeoutRef.current = setTimeout(() => { + setIsHolding(false); + onComplete(); + }, duration); + }, [disabled, onHoldStart, onComplete, duration]); + + const handlePointerUp = useCallback(() => { + setIsHolding(false); + if (holdTimeoutRef.current) { + clearTimeout(holdTimeoutRef.current); + holdTimeoutRef.current = null; + } + }, []); + + return ( + + ); +} From db9ff5d5471dc0260bd5240a2b5102fb935a53db Mon Sep 17 00:00:00 2001 From: Kacper Szarkiewicz Date: Sun, 18 Jan 2026 13:55:36 +0100 Subject: [PATCH 02/17] create TextArea component --- .../src/components/TextArea/index.tsx | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 apps/frontend/src/components/TextArea/index.tsx diff --git a/apps/frontend/src/components/TextArea/index.tsx b/apps/frontend/src/components/TextArea/index.tsx new file mode 100644 index 000000000..bc47396b2 --- /dev/null +++ b/apps/frontend/src/components/TextArea/index.tsx @@ -0,0 +1,20 @@ +import { TextareaHTMLAttributes } from "react"; +import { UseFormRegisterReturn } from "react-hook-form"; +import { cn } from "../../helpers/cn"; + +export interface TextAreaProps extends TextareaHTMLAttributes { + register?: UseFormRegisterReturn; + error?: boolean; +} + +export const TextArea = ({ className, register, error, ...rest }: TextAreaProps) => ( +