@@ -9,17 +9,27 @@ interface CreateCurrencyModalProps {
99 groups : Array < { id : string ; name : string ; isAdmin : boolean } > ;
1010}
1111
12+ const MAX_NEGATIVE_LIMIT = 1_000_000_000 ;
13+
1214export default function CreateCurrencyModal ( { open, onOpenChange, groups } : CreateCurrencyModalProps ) {
1315 const [ name , setName ] = useState ( "" ) ;
1416 const [ description , setDescription ] = useState ( "" ) ;
1517 const [ groupId , setGroupId ] = useState ( "" ) ;
1618 const [ allowNegative , setAllowNegative ] = useState ( false ) ;
19+ const [ maxNegativeInput , setMaxNegativeInput ] = useState ( "" ) ;
20+ const [ error , setError ] = useState < string | null > ( null ) ;
1721 const queryClient = useQueryClient ( ) ;
1822
1923 const adminGroups = groups . filter ( g => g . isAdmin ) ;
2024
2125 const createMutation = useMutation ( {
22- mutationFn : async ( data : { name : string ; description ?: string ; groupId : string ; allowNegative : boolean } ) => {
26+ mutationFn : async ( data : {
27+ name : string ;
28+ description ?: string ;
29+ groupId : string ;
30+ allowNegative : boolean ;
31+ maxNegativeBalance : number | null ;
32+ } ) => {
2333 const response = await apiClient . post ( "/api/currencies" , data ) ;
2434 return response . data ;
2535 } ,
@@ -30,6 +40,8 @@ export default function CreateCurrencyModal({ open, onOpenChange, groups }: Crea
3040 setDescription ( "" ) ;
3141 setGroupId ( "" ) ;
3242 setAllowNegative ( false ) ;
43+ setMaxNegativeInput ( "" ) ;
44+ setError ( null ) ;
3345 onOpenChange ( false ) ;
3446 } ,
3547 } ) ;
@@ -52,9 +64,35 @@ export default function CreateCurrencyModal({ open, onOpenChange, groups }: Crea
5264 < form
5365 onSubmit = { ( e ) => {
5466 e . preventDefault ( ) ;
55- if ( name && groupId ) {
56- createMutation . mutate ( { name, description, groupId, allowNegative } ) ;
67+ setError ( null ) ;
68+ if ( ! name || ! groupId ) return ;
69+
70+ let maxNegativeValue : number | null = null ;
71+ if ( allowNegative ) {
72+ const trimmed = maxNegativeInput . trim ( ) ;
73+ if ( trimmed ) {
74+ const magnitude = parseFloat ( trimmed ) ;
75+ if ( Number . isNaN ( magnitude ) || magnitude < 0 ) {
76+ setError ( "Enter a valid non-negative number for max negative balance." ) ;
77+ return ;
78+ }
79+ if ( magnitude > MAX_NEGATIVE_LIMIT ) {
80+ setError ( `Max negative cannot exceed ${ MAX_NEGATIVE_LIMIT . toLocaleString ( ) } .` ) ;
81+ return ;
82+ }
83+ maxNegativeValue = magnitude === 0 ? 0 : - Math . abs ( magnitude ) ;
84+ }
85+ } else {
86+ setMaxNegativeInput ( "" ) ;
5787 }
88+
89+ createMutation . mutate ( {
90+ name,
91+ description,
92+ groupId,
93+ allowNegative,
94+ maxNegativeBalance : maxNegativeValue ,
95+ } ) ;
5896 } }
5997 className = "space-y-4"
6098 >
@@ -102,7 +140,10 @@ export default function CreateCurrencyModal({ open, onOpenChange, groups }: Crea
102140 < div className = "grid grid-cols-2 gap-3" >
103141 < button
104142 type = "button"
105- onClick = { ( ) => setAllowNegative ( false ) }
143+ onClick = { ( ) => {
144+ setAllowNegative ( false ) ;
145+ setMaxNegativeInput ( "" ) ;
146+ } }
106147 className = { `p-4 border-2 rounded-lg text-left transition-all ${
107148 ! allowNegative
108149 ? "border-primary bg-primary/5"
@@ -131,6 +172,31 @@ export default function CreateCurrencyModal({ open, onOpenChange, groups }: Crea
131172 </ div >
132173 </ div >
133174
175+ { allowNegative && (
176+ < div >
177+ < label className = "block text-sm font-medium mb-1" > Max negative balance (absolute value)</ label >
178+ < input
179+ type = "number"
180+ min = { 0 }
181+ max = { MAX_NEGATIVE_LIMIT }
182+ step = { 0.01 }
183+ value = { maxNegativeInput }
184+ onChange = { ( e ) => setMaxNegativeInput ( e . target . value ) }
185+ placeholder = "Leave blank for no cap"
186+ className = "w-full px-3 py-2 border rounded-md"
187+ />
188+ < p className = "text-xs text-muted-foreground mt-1" >
189+ Limit how far any account can go below zero (max { MAX_NEGATIVE_LIMIT . toLocaleString ( ) } ).
190+ </ p >
191+ </ div >
192+ ) }
193+
194+ { error && (
195+ < div className = "bg-red-50 border border-red-200 text-red-700 px-4 py-2 rounded-md" >
196+ { error }
197+ </ div >
198+ ) }
199+
134200 < div className = "flex gap-2 justify-end pt-2" >
135201 < button
136202 type = "button"
0 commit comments