@@ -103,7 +103,7 @@ function createToastContainer() {
103103}
104104
105105// ---------------- Donation Logic ----------------
106- function verifyDonateInput ( amount , sponsor_channel , is_corporate , triggerEl , on_verified ) {
106+ function verifyDonateInputs ( amount , sponsor_channel , is_corporate , triggerEl , on_verified ) {
107107 const channelSelect = document . getElementById ( "channel-options" ) ;
108108 const channel = channelSelect ? channelSelect . value : "" ;
109109 const value = parseCurrency ( amount ) ;
@@ -183,7 +183,7 @@ function initEventHandlers() {
183183 const is_corporate = document . getElementById ( "btn-corporate-tiers" ) . classList . contains ( "active" ) ;
184184 const channel = document . getElementById ( "channel-options" ) . value ;
185185
186- verifyDonateInput ( amount_str , channel , is_corporate , e . currentTarget , ( verified_channel ) => {
186+ verifyDonateInputs ( amount_str , channel , is_corporate , e . currentTarget , ( verified_channel ) => {
187187 // Generate order id if amount or cycle changed
188188 if ( ( window . cv_sel_amount !== amount_str ) || ( window . cv_is_monthly !== is_monthly ) ) {
189189 window . cv_sel_amount = amount_str ;
@@ -194,15 +194,24 @@ function initEventHandlers() {
194194 console . log ( `Amount: $${ amount_str } ${ is_monthly ? '/month' : '' } , Channel: ${ verified_channel } ` ) ;
195195
196196 const amount = parseCurrency ( amount_str ) ;
197- const tier_info = is_corporate ? corporateTiers [ selected . id ] : individualTiers [ selected . id ] ;
197+ let prod_id = null ;
198+ let osc_tier = null ;
199+ if ( ! is_custom ) {
200+ const tier_info = is_corporate ? corporateTiers [ selected . id ] : individualTiers [ selected . id ] ;
201+ prod_id = tier_info . prod_id ;
202+ osc_tier = tier_info . osc_tier ;
203+ }
204+ else {
205+ prod_id = 'custom' ;
206+ }
198207
199208 // Payment redirect logic
200209 if ( verified_channel === 'paypal' ) {
201210 let baseUrl = ! sandbox ? 'simdsoft.com' : 'local.simdsoft.com' ;
202211 const actionUrl = `https://${ baseUrl } /onlinepay/uniorder.php` ;
203212 const form = $ ( '#unipayment' ) ;
204213 form . attr ( 'action' , actionUrl ) ;
205- form . children ( '#WIDprod' ) . attr ( 'value' , tier_info . prod_id ) ;
214+ form . children ( '#WIDprod' ) . attr ( 'value' , prod_id ) ;
206215 form . children ( '#WIDout_trade_no' ) . attr ( 'value' , window . cv_orderid ) ;
207216 form . children ( '#WIDmonthly' ) . attr ( 'value' , is_monthly ? '1' : '0' ) ;
208217 form . children ( '#WIDamount' ) . attr ( 'value' , amount . toString ( ) ) ;
@@ -214,8 +223,7 @@ function initEventHandlers() {
214223 } else if ( verified_channel === 'osc' ) {
215224 let actionUrl = '#' ;
216225 if ( is_corporate ) {
217- if ( is_monthly ) {
218- const osc_tier = tier_info . osc_teir ;
226+ if ( osc_tier !== null ) {
219227 actionUrl = `https://opencollective.com/axmol/contribute/${ osc_tier } /checkout?interval=month&amount=${ amount } &contributeAs=me` ;
220228 }
221229 } else {
@@ -321,6 +329,63 @@ function initEventHandlers() {
321329 perPage = parseInt ( e . target . value , 10 ) ;
322330 loadData ( 1 ) ;
323331 } ) ;
332+
333+ // ---------------- Custom amount formatting ----------------
334+ const amountInput = document . getElementById ( 'amount-input' ) ;
335+
336+ // Format while typing
337+ function formatCurrency ( e ) {
338+ let caretPos = e . target . selectionStart ;
339+ const originalLen = e . target . value . length ;
340+
341+ let value = e . target . value
342+ . replace ( / [ ^ 0 - 9 . ] / g, '' ) // remove non-numeric
343+ . replace ( / ( \. .* ) \. / g, '$1' ) // only one decimal point
344+ . replace ( / ( \. \d { 2 } ) .* / g, '$1' ) // max two decimals
345+ . replace ( / ^ 0 + ( \d ) / , '$1' ) ; // no leading zeros
346+
347+ let [ integer , decimal ] = value . split ( '.' ) ;
348+ decimal = decimal ? '.' + decimal . slice ( 0 , 2 ) : '' ;
349+
350+ if ( integer ) {
351+ integer = integer . replace ( / \B (? = ( \d { 3 } ) + (? ! \d ) ) / g, "," ) ;
352+ }
353+
354+ const newValue = ( integer || '0' ) + ( decimal || '' ) ;
355+ e . target . value = newValue ;
356+
357+ // restore caret position
358+ const newLen = newValue . length ;
359+ caretPos = newLen - ( originalLen - caretPos ) ;
360+ e . target . setSelectionRange ( caretPos , caretPos ) ;
361+ }
362+
363+ amountInput . addEventListener ( 'input' , formatCurrency ) ;
364+
365+ // Auto select Custom card when focusing input
366+ amountInput . addEventListener ( 'focus' , ( ) => {
367+ document . getElementById ( 'custom' ) . checked = true ;
368+ document . querySelectorAll ( '.currency-symbol' ) . forEach ( el => el . style . opacity = 1 ) ;
369+ } ) ;
370+
371+ // Format on blur
372+ amountInput . addEventListener ( 'blur' , ( e ) => {
373+ let numericValue = e . target . value . replace ( / , / g, '' ) ;
374+ if ( numericValue !== '' ) {
375+ let val = parseCurrency ( numericValue ) ;
376+ if ( val < 1 ) { numericValue = '1.00' ; }
377+ }
378+
379+ if ( numericValue && ! isNaN ( numericValue ) ) {
380+ let formatted = parseCurrency ( numericValue ) . toFixed ( 2 )
381+ . replace ( / \d (? = ( \d { 3 } ) + \. ) / g, '$&,' ) ;
382+ e . target . value = formatted ;
383+ }
384+
385+ if ( ! e . target . value ) {
386+ document . querySelectorAll ( '.currency-symbol' ) . forEach ( el => el . style . opacity = 0 ) ;
387+ }
388+ } ) ;
324389}
325390
326391// ---------------- Data Loading ----------------
0 commit comments