@@ -25,6 +25,7 @@ import { BaseDrawer, ConfirmDrawer } from "@/components/Drawers"
2525import { GrossToNetDiscountForm } from "@/components/GrossToNetDiscountForm"
2626import Big from "big.js"
2727import { toast } from "sonner"
28+ import { useForm } from "react-hook-form"
2829
2930function Loader ( ) {
3031 return (
@@ -34,8 +35,78 @@ function Loader() {
3435 )
3536}
3637
38+ interface TimeToLiveFormValues {
39+ ttl ?: Date
40+ }
41+
42+ interface TimeToLiveFormResult {
43+ ttl : Date
44+ }
45+
46+ interface TimeToLiveFormProps {
47+ submitButtonText ?: string
48+ onSubmit : ( values : TimeToLiveFormResult ) => void
49+ }
50+
51+ const TimeToLiveForm = ( { onSubmit, submitButtonText = "Submit" } : TimeToLiveFormProps ) => {
52+ const {
53+ watch,
54+ register,
55+ handleSubmit,
56+ formState : { isValid, errors } ,
57+ } = useForm < TimeToLiveFormValues > ( {
58+ mode : "all" ,
59+ } )
60+
61+ const { ttl } = watch ( )
62+
63+ return (
64+ < form
65+ className = "flex flex-col gap-2 min-w-[8rem]"
66+ onSubmit = { ( e ) => {
67+ handleSubmit ( ( ) => {
68+ if ( errors . root !== undefined || ttl === undefined ) return
69+
70+ onSubmit ( {
71+ ttl,
72+ } )
73+ } ) ( e ) . catch ( ( ) => {
74+ // TODO
75+ } )
76+ } }
77+ >
78+ < div className = "flex flex-col" >
79+ < div
80+ className = { cn (
81+ "flex gap-2 justify-between items-center font-semibold" ,
82+ "peer flex h-[58px] w-full rounded-[8px] border bg-elevation-200 px-4 text-sm transition-all duration-200 ease-in-out outline-none focus:outline-none" ,
83+ "file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:ring-0" ,
84+ ) }
85+ >
86+ < label htmlFor = { "ttl" } > Valid until</ label >
87+
88+ < input
89+ id = "ttl"
90+ step = "1"
91+ type = "number"
92+ className = "bg-transparent text-right focus:outline-none [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none"
93+ { ...register ( "ttl" , {
94+ required : true ,
95+ } ) }
96+ />
97+ </ div >
98+ </ div >
99+
100+ < Button type = "submit" size = "sm" className = "my-[16px]" disabled = { ! isValid } >
101+ { submitButtonText }
102+ </ Button >
103+ </ form >
104+ )
105+ }
106+
37107interface OfferFormResult {
38108 discount : Parameters < Parameters < typeof GrossToNetDiscountForm > [ 0 ] [ "onSubmit" ] > [ 0 ]
109+ ttl : Parameters < Parameters < typeof TimeToLiveForm > [ 0 ] [ "onSubmit" ] > [ 0 ]
39110}
40111
41112interface OfferFormProps {
@@ -44,16 +115,32 @@ interface OfferFormProps {
44115}
45116
46117function OfferForm ( { onSubmit, discount } : OfferFormProps ) {
118+ const [ discountResult , setDiscountResult ] = useState < OfferFormResult [ "discount" ] > ( )
47119 return (
48120 < >
49- < GrossToNetDiscountForm
50- { ...discount }
51- startDate = { discount . startDate ?? new Date ( Date . now ( ) ) }
52- onSubmit = { ( values ) => onSubmit ( {
53- discount : values
54- } ) }
55- submitButtonText = "Next"
56- />
121+ { ! discountResult ? (
122+ < >
123+ < GrossToNetDiscountForm
124+ { ...discount }
125+ startDate = { discount . startDate ?? new Date ( Date . now ( ) ) }
126+ onSubmit = { setDiscountResult }
127+ submitButtonText = "Next"
128+ />
129+ </ >
130+ ) : (
131+ < >
132+ < TimeToLiveForm
133+ { ...discount }
134+ onSubmit = { ( ttlResult ) =>
135+ onSubmit ( {
136+ discount : discountResult ,
137+ ttl : ttlResult ,
138+ } )
139+ }
140+ submitButtonText = "Next"
141+ />
142+ </ >
143+ ) }
57144 </ >
58145 )
59146}
@@ -203,67 +290,68 @@ function QuoteActions({ value, isFetching }: { value: InfoReply; isFetching: boo
203290 }
204291
205292 return (
206- < div className = "flex items-center gap-2" >
207- < DenyConfirmDrawer
208- title = "Confirm denying quote"
209- open = { denyConfirmDrawerOpen }
210- onOpenChange = { setDenyConfirmDrawerOpen }
211- onSubmit = { ( ) => {
212- onDenyQuote ( )
213- setDenyConfirmDrawerOpen ( false )
214- } }
215- >
216- < Button
217- className = "flex-1"
218- disabled = { isFetching || denyQuote . isPending || value . status !== "pending" }
219- variant = { value . status !== "pending" ? "outline" : "destructive" }
220- >
221- Deny { denyQuote . isPending && < LoaderIcon className = "stroke-1 animate-spin" /> }
222- </ Button >
223- </ DenyConfirmDrawer >
224- < OfferFormDrawer
225- title = "Offer quote"
226- description = "Make an offer to the current holder of this bill"
227- value = { value }
228- open = { offerFormDrawerOpen }
229- onOpenChange = { setOfferFormDrawerOpen }
230- onSubmit = { ( data ) => {
231- setOfferFormData ( data )
232- setOfferConfirmDrawerOpen ( true )
233- setOfferFormDrawerOpen ( false )
234- } }
235- >
236- < Button className = "flex-1" disabled = { isFetching || offerQuote . isPending || value . status !== "pending" } >
237- Offer { offerQuote . isPending && < LoaderIcon className = "stroke-1 animate-spin" /> }
238- </ Button >
239- </ OfferFormDrawer >
240- < OfferConfirmDrawer
241- title = "Confirm offering quote"
242- description = "Review your inputs and confirm the offer"
243- open = { offerConfirmDrawerOpen }
244- onOpenChange = { setOfferConfirmDrawerOpen }
245- onSubmit = { ( ) => {
246- if ( ! offerFormData ) return
247- onOfferQuote ( offerFormData )
248- setOfferConfirmDrawerOpen ( false )
249- } }
293+ < div className = "flex items-center gap-2" >
294+ < DenyConfirmDrawer
295+ title = "Confirm denying quote"
296+ open = { denyConfirmDrawerOpen }
297+ onOpenChange = { setDenyConfirmDrawerOpen }
298+ onSubmit = { ( ) => {
299+ onDenyQuote ( )
300+ setDenyConfirmDrawerOpen ( false )
301+ } }
302+ >
303+ < Button
304+ className = "flex-1"
305+ disabled = { isFetching || denyQuote . isPending || value . status !== "pending" }
306+ variant = { value . status !== "pending" ? "outline" : "destructive" }
250307 >
251- < div className = "flex flex-col justify-center gap-1 py-8 mb-8" >
252- < span >
253- < span className = "font-bold" > Effective discount (relative):</ span > { " " }
254- { effectiveDiscount ?. mul ( new Big ( "100" ) ) . toFixed ( 2 ) } %
255- </ span >
256- < span >
257- < span className = "font-bold" > Effective discount (absolute):</ span > { " " }
258- { offerFormData ?. discount . gross . value . minus ( offerFormData ?. discount . net . value ) . toFixed ( 0 ) } { offerFormData ?. discount . net . currency }
259- </ span >
260- < span >
261- < span className = "font-bold" > Net amount:</ span > { offerFormData ?. discount . net . value . round ( 0 ) . toFixed ( 0 ) } { " " }
262- { offerFormData ?. discount . net . currency }
263- </ span >
264- </ div >
265- </ OfferConfirmDrawer >
266- </ div >
308+ Deny { denyQuote . isPending && < LoaderIcon className = "stroke-1 animate-spin" /> }
309+ </ Button >
310+ </ DenyConfirmDrawer >
311+ < OfferFormDrawer
312+ title = "Offer quote"
313+ description = "Make an offer to the current holder of this bill"
314+ value = { value }
315+ open = { offerFormDrawerOpen }
316+ onOpenChange = { setOfferFormDrawerOpen }
317+ onSubmit = { ( data ) => {
318+ setOfferFormData ( data )
319+ setOfferConfirmDrawerOpen ( true )
320+ setOfferFormDrawerOpen ( false )
321+ } }
322+ >
323+ < Button className = "flex-1" disabled = { isFetching || offerQuote . isPending || value . status !== "pending" } >
324+ Offer { offerQuote . isPending && < LoaderIcon className = "stroke-1 animate-spin" /> }
325+ </ Button >
326+ </ OfferFormDrawer >
327+ < OfferConfirmDrawer
328+ title = "Confirm offering quote"
329+ description = "Review your inputs and confirm the offer"
330+ open = { offerConfirmDrawerOpen }
331+ onOpenChange = { setOfferConfirmDrawerOpen }
332+ onSubmit = { ( ) => {
333+ if ( ! offerFormData ) return
334+ onOfferQuote ( offerFormData )
335+ setOfferConfirmDrawerOpen ( false )
336+ } }
337+ >
338+ < div className = "flex flex-col justify-center gap-1 py-8 mb-8" >
339+ < span >
340+ < span className = "font-bold" > Effective discount (relative):</ span > { " " }
341+ { effectiveDiscount ?. mul ( new Big ( "100" ) ) . toFixed ( 2 ) } %
342+ </ span >
343+ < span >
344+ < span className = "font-bold" > Effective discount (absolute):</ span > { " " }
345+ { offerFormData ?. discount . gross . value . minus ( offerFormData ?. discount . net . value ) . toFixed ( 0 ) } { " " }
346+ { offerFormData ?. discount . net . currency }
347+ </ span >
348+ < span >
349+ < span className = "font-bold" > Net amount:</ span > { offerFormData ?. discount . net . value . round ( 0 ) . toFixed ( 0 ) } { " " }
350+ { offerFormData ?. discount . net . currency }
351+ </ span >
352+ </ div >
353+ </ OfferConfirmDrawer >
354+ </ div >
267355 )
268356}
269357
0 commit comments