11import React , { useState , useEffect } from 'react' ;
2- import { Input , Switch , Tooltip } from 'antd' ;
2+ import { Input , Switch , Tooltip , Select , InputNumber , Space } from 'antd' ;
33import { BaseButton } from '@app/components/common/BaseButton/BaseButton' ;
4- import { PlusOutlined , DollarOutlined , DatabaseOutlined , DeleteOutlined , ThunderboltOutlined } from '@ant-design/icons' ;
4+ import { PlusOutlined , DatabaseOutlined , DeleteOutlined , ThunderboltOutlined } from '@ant-design/icons' ;
55import * as S from '@app/pages/uiComponentsPages/UIComponentsPage.styles' ;
66import type { SubscriptionTier } from '@app/constants/relaySettings' ;
77import styled from 'styled-components' ;
88
9+ // Helper functions for data limit parsing and formatting
10+ interface DataLimit {
11+ amount : number ;
12+ unit : 'MB' | 'GB' ;
13+ }
14+
15+ const parseDataLimit = ( dataLimitString : string ) : DataLimit => {
16+ const match = dataLimitString . match ( / ^ ( \d + ) \s * ( M B | G B ) / i) ;
17+ if ( match ) {
18+ return {
19+ amount : parseInt ( match [ 1 ] , 10 ) ,
20+ unit : match [ 2 ] . toUpperCase ( ) as 'MB' | 'GB'
21+ } ;
22+ }
23+ // Default fallback
24+ return { amount : 1 , unit : 'GB' } ;
25+ } ;
26+
27+ const formatDataLimit = ( amount : number , unit : 'MB' | 'GB' ) : string => {
28+ return `${ amount } ${ unit } per month` ;
29+ } ;
30+
931// Styled components for better UI
1032const TierCard = styled . div `
1133 background: linear-gradient(145deg, #1b1b38 0%, #161632 100%);
@@ -128,6 +150,52 @@ const InputIcon = styled.div`
128150 }
129151` ;
130152
153+ const DataLimitInputGroup = styled . div `
154+ display: flex;
155+ gap: 8px;
156+ align-items: flex-start;
157+ ` ;
158+
159+ const StyledInputNumber = styled ( InputNumber ) `
160+ flex: 1;
161+ background-color: #1b1b38 !important;
162+ border-color: #313131 !important;
163+ color: white !important;
164+ border-radius: 8px !important;
165+ height: 48px !important;
166+
167+ .ant-input-number-input {
168+ color: white !important;
169+ }
170+
171+ &.ant-input-number-focused {
172+ border-color: #4e4e8b !important;
173+ box-shadow: 0 0 0 2px rgba(78, 78, 139, 0.2) !important;
174+ }
175+ ` ;
176+
177+ const StyledSelect = styled ( Select ) `
178+ width: 120px !important;
179+
180+ .ant-select-selector {
181+ background-color: #1b1b38 !important;
182+ border-color: #313131 !important;
183+ height: 48px !important;
184+ display: flex !important;
185+ align-items: center !important;
186+ border-radius: 8px !important;
187+ }
188+
189+ .ant-select-selection-item {
190+ color: white !important;
191+ }
192+
193+ &.ant-select-focused .ant-select-selector {
194+ border-color: #4e4e8b !important;
195+ box-shadow: 0 0 0 2px rgba(78, 78, 139, 0.2) !important;
196+ }
197+ ` ;
198+
131199interface SubscriptionTiersManagerProps {
132200 tiers ?: SubscriptionTier [ ] ;
133201 onChange : ( tiers : SubscriptionTier [ ] ) => void ;
@@ -149,73 +217,160 @@ const SubscriptionTiersManager: React.FC<SubscriptionTiersManagerProps> = ({
149217 { data_limit : '10 GB per month' , price : '15000' }
150218 ] ;
151219
152- // Initialize with properly formatted tiers from props or default
153- const [ currentTiers , setCurrentTiers ] = useState < SubscriptionTier [ ] > ( ( ) => {
154- return tiers . length > 0 ? tiers . map ( tier => ( {
155- data_limit : tier . data_limit . includes ( 'per month' ) ? tier . data_limit : `${ tier . data_limit } per month` ,
156- price : tier . price
157- } ) ) : defaultTiers ;
220+ // Initialize tiers with data_limit parsed into amount and unit
221+ const [ currentTiers , setCurrentTiers ] = useState < ( SubscriptionTier & { amount : number ; unit : 'MB' | 'GB' } ) [ ] > ( ( ) => {
222+ return tiers . length > 0 ? tiers . map ( tier => {
223+ const dataLimit = parseDataLimit ( tier . data_limit ) ;
224+ return {
225+ data_limit : tier . data_limit . includes ( 'per month' ) ? tier . data_limit : `${ tier . data_limit } per month` ,
226+ price : tier . price ,
227+ amount : dataLimit . amount ,
228+ unit : dataLimit . unit
229+ } ;
230+ } ) : defaultTiers . map ( tier => {
231+ const dataLimit = parseDataLimit ( tier . data_limit ) ;
232+ return {
233+ data_limit : tier . data_limit ,
234+ price : tier . price ,
235+ amount : dataLimit . amount ,
236+ unit : dataLimit . unit
237+ } ;
238+ } ) ;
158239 } ) ;
159240
241+ // Parse free tier limit into amount and unit
242+ const parsedFreeTierLimit = parseDataLimit ( freeTierLimit ) ;
243+ const [ freeTierAmount , setFreeTierAmount ] = useState < number > ( parsedFreeTierLimit . amount ) ;
244+ const [ freeTierUnit , setFreeTierUnit ] = useState < 'MB' | 'GB' > ( parsedFreeTierLimit . unit ) ;
245+
160246 // Update current tiers when props change
161247 useEffect ( ( ) => {
162248 if ( tiers . length > 0 ) {
163- const formattedTiers = tiers . map ( tier => ( {
164- data_limit : tier . data_limit . includes ( 'per month' ) ? tier . data_limit : `${ tier . data_limit } per month` ,
165- price : tier . price
166- } ) ) ;
167-
168- // Only update if the formatted tiers are different from current
169- const currentTiersString = JSON . stringify ( currentTiers ) ;
170- const formattedTiersString = JSON . stringify ( formattedTiers ) ;
171-
172- if ( currentTiersString !== formattedTiersString ) {
173- setCurrentTiers ( formattedTiers ) ;
174- }
249+ // Use functional update pattern to avoid dependency on currentTiers
250+ setCurrentTiers ( prevTiers => {
251+ const formattedTiers = tiers . map ( tier => {
252+ const dataLimit = parseDataLimit ( tier . data_limit ) ;
253+ return {
254+ data_limit : tier . data_limit . includes ( 'per month' ) ? tier . data_limit : `${ tier . data_limit } per month` ,
255+ price : tier . price ,
256+ amount : dataLimit . amount ,
257+ unit : dataLimit . unit
258+ } ;
259+ } ) ;
260+
261+ // Only update if the formatted tiers are different from current
262+ const currentTierDataOnly = prevTiers . map ( ( { data_limit, price } ) => ( { data_limit, price } ) ) ;
263+ const formattedTierDataOnly = formattedTiers . map ( ( { data_limit, price } ) => ( { data_limit, price } ) ) ;
264+
265+ if ( JSON . stringify ( currentTierDataOnly ) !== JSON . stringify ( formattedTierDataOnly ) ) {
266+ return formattedTiers ;
267+ }
268+ return prevTiers ;
269+ } ) ;
175270 }
176- } , [ tiers , currentTiers ] ) ;
271+ } , [ tiers ] ) ;
177272
178- const handleUpdateTier = ( index : number , field : keyof SubscriptionTier , value : string ) => {
273+ // Update free tier amount and unit when freeTierLimit prop changes
274+ useEffect ( ( ) => {
275+ const parsed = parseDataLimit ( freeTierLimit ) ;
276+ setFreeTierAmount ( parsed . amount ) ;
277+ setFreeTierUnit ( parsed . unit ) ;
278+ } , [ freeTierLimit ] ) ;
279+
280+ const handleUpdateTierPrice = ( index : number , value : string ) => {
179281 const newTiers = currentTiers . map ( ( tier , i ) => {
180282 if ( i === index ) {
181- if ( field === 'data_limit' ) {
182- const formattedValue = value . includes ( 'per month' ) ? value : `${ value } per month` ;
183- return { ...tier , [ field ] : formattedValue } ;
184- }
185- return { ...tier , [ field ] : value } ;
283+ return { ...tier , price : value } ;
284+ }
285+ return tier ;
286+ } ) ;
287+
288+ setCurrentTiers ( newTiers ) ;
289+ onChange ( newTiers . map ( ( { data_limit, price } ) => ( { data_limit, price } ) ) ) ;
290+ } ;
291+
292+ // Fixed type signature for InputNumber's onChange
293+ const handleUpdateTierAmount = ( index : number , value : string | number | null ) => {
294+ if ( value === null ) return ;
295+
296+ const numValue = typeof value === 'string' ? parseInt ( value , 10 ) : value ;
297+
298+ const newTiers = currentTiers . map ( ( tier , i ) => {
299+ if ( i === index ) {
300+ const newDataLimit = formatDataLimit ( numValue , tier . unit ) ;
301+ return {
302+ ...tier ,
303+ amount : numValue ,
304+ data_limit : newDataLimit
305+ } ;
306+ }
307+ return tier ;
308+ } ) ;
309+
310+ setCurrentTiers ( newTiers ) ;
311+ onChange ( newTiers . map ( ( { data_limit, price } ) => ( { data_limit, price } ) ) ) ;
312+ } ;
313+
314+ // Fixed type signature for Select's onChange
315+ const handleUpdateTierUnit = ( index : number , value : unknown ) => {
316+ const unit = value as 'MB' | 'GB' ;
317+
318+ const newTiers = currentTiers . map ( ( tier , i ) => {
319+ if ( i === index ) {
320+ const newDataLimit = formatDataLimit ( tier . amount , unit ) ;
321+ return {
322+ ...tier ,
323+ unit,
324+ data_limit : newDataLimit
325+ } ;
186326 }
187327 return tier ;
188328 } ) ;
189329
190330 setCurrentTiers ( newTiers ) ;
191- onChange ( newTiers ) ;
331+ onChange ( newTiers . map ( ( { data_limit , price } ) => ( { data_limit , price } ) ) ) ;
192332 } ;
193333
194334 const addTier = ( ) => {
195335 if ( currentTiers . length < 3 ) {
196- const newTier : SubscriptionTier = {
336+ const newTier = {
197337 data_limit : '1 GB per month' ,
198- price : '10000'
338+ price : '10000' ,
339+ amount : 1 ,
340+ unit : 'GB' as 'MB' | 'GB'
199341 } ;
200342 const updatedTiers = [ ...currentTiers , newTier ] ;
201343 setCurrentTiers ( updatedTiers ) ;
202- onChange ( updatedTiers ) ;
344+ onChange ( updatedTiers . map ( ( { data_limit , price } ) => ( { data_limit , price } ) ) ) ;
203345 }
204346 } ;
205347
206348 const removeTier = ( index : number ) => {
207- const newTier = currentTiers . filter ( ( _ , i ) => i !== index ) ;
208- setCurrentTiers ( newTier ) ;
209- onChange ( newTier ) ;
349+ const newTiers = currentTiers . filter ( ( _ , i ) => i !== index ) ;
350+ setCurrentTiers ( newTiers ) ;
351+ onChange ( newTiers . map ( ( { data_limit , price } ) => ( { data_limit , price } ) ) ) ;
210352 } ;
211353
212354 const toggleFreeTier = ( checked : boolean ) => {
213355 onFreeTierChange ( checked , checked ? freeTierLimit : '100 MB per month' ) ;
214356 } ;
215357
216- const updateFreeTierLimit = ( value : string ) => {
217- const formattedValue = value . includes ( 'per month' ) ? value : `${ value } per month` ;
218- onFreeTierChange ( freeTierEnabled , formattedValue ) ;
358+ // Fixed type signature for InputNumber's onChange
359+ const updateFreeTierAmount = ( value : string | number | null ) => {
360+ if ( value === null ) return ;
361+
362+ const numValue = typeof value === 'string' ? parseInt ( value , 10 ) : value ;
363+ setFreeTierAmount ( numValue ) ;
364+ const newLimit = formatDataLimit ( numValue , freeTierUnit ) ;
365+ onFreeTierChange ( freeTierEnabled , newLimit ) ;
366+ } ;
367+
368+ // Fixed type signature for Select's onChange
369+ const updateFreeTierUnit = ( value : unknown ) => {
370+ const unit = value as 'MB' | 'GB' ;
371+ setFreeTierUnit ( unit ) ;
372+ const newLimit = formatDataLimit ( freeTierAmount , unit ) ;
373+ onFreeTierChange ( freeTierEnabled , newLimit ) ;
219374 } ;
220375
221376 return (
@@ -248,20 +403,22 @@ const SubscriptionTiersManager: React.FC<SubscriptionTiersManagerProps> = ({
248403 < DatabaseOutlined />
249404 < InputLabel > Data Limit</ InputLabel >
250405 </ InputIcon >
251- < Input
252- value = { freeTierLimit }
253- onChange = { ( e ) => updateFreeTierLimit ( e . target . value ) }
254- placeholder = "e.g., 100 MB per month"
255- style = { {
256- width : '100%' ,
257- backgroundColor : '#1b1b38' ,
258- borderColor : '#313131' ,
259- color : 'white' ,
260- height : '48px' ,
261- borderRadius : '8px'
262- } }
263- prefix = { < DatabaseOutlined style = { { color : '#a9a9c8' } } /> }
264- />
406+ < DataLimitInputGroup >
407+ < StyledInputNumber
408+ min = { 1 }
409+ value = { freeTierAmount }
410+ onChange = { updateFreeTierAmount }
411+ prefix = { < DatabaseOutlined style = { { color : '#a9a9c8' } } /> }
412+ />
413+ < StyledSelect
414+ value = { freeTierUnit }
415+ onChange = { updateFreeTierUnit }
416+ options = { [
417+ { value : 'MB' , label : 'MB' } ,
418+ { value : 'GB' , label : 'GB' }
419+ ] }
420+ />
421+ </ DataLimitInputGroup >
265422 </ InputGroup >
266423
267424 < InputGroup style = { { flex : 1 } } >
@@ -311,20 +468,22 @@ const SubscriptionTiersManager: React.FC<SubscriptionTiersManagerProps> = ({
311468 < DatabaseOutlined />
312469 < InputLabel > Data Limit</ InputLabel >
313470 </ InputIcon >
314- < Input
315- value = { tier . data_limit }
316- onChange = { ( e ) => handleUpdateTier ( index , 'data_limit' , e . target . value ) }
317- placeholder = "e.g., 1 GB per month"
318- style = { {
319- width : '100%' ,
320- backgroundColor : '#1b1b38' ,
321- borderColor : '#313131' ,
322- color : 'white' ,
323- height : '48px' ,
324- borderRadius : '8px'
325- } }
326- prefix = { < DatabaseOutlined style = { { color : '#a9a9c8' } } /> }
327- />
471+ < DataLimitInputGroup >
472+ < StyledInputNumber
473+ min = { 1 }
474+ value = { tier . amount }
475+ onChange = { ( value ) => handleUpdateTierAmount ( index , value ) }
476+ prefix = { < DatabaseOutlined style = { { color : '#a9a9c8' } } /> }
477+ />
478+ < StyledSelect
479+ value = { tier . unit }
480+ onChange = { ( value ) => handleUpdateTierUnit ( index , value ) }
481+ options = { [
482+ { value : 'MB' , label : 'MB' } ,
483+ { value : 'GB' , label : 'GB' }
484+ ] }
485+ />
486+ </ DataLimitInputGroup >
328487 </ InputGroup >
329488
330489 < InputGroup style = { { flex : 1 } } >
@@ -335,7 +494,7 @@ const SubscriptionTiersManager: React.FC<SubscriptionTiersManagerProps> = ({
335494 < Input
336495 type = "number"
337496 value = { tier . price }
338- onChange = { ( e ) => handleUpdateTier ( index , 'price' , e . target . value ) }
497+ onChange = { ( e ) => handleUpdateTierPrice ( index , e . target . value ) }
339498 placeholder = "Price in sats"
340499 style = { {
341500 width : '100%' ,
0 commit comments