@@ -4,134 +4,193 @@ import { useAppState } from 'store/app.context';
44import { CancellationModeEnum } from '@config' ;
55import { BILLABLEMETRIC_CODE_ENUM } from '@impler/shared' ;
66
7+ interface SubOSComponents {
8+ PlanSelector : any ;
9+ PlanCard : any ;
10+ }
11+
12+ interface SubOSHooks {
13+ useCustomerPortal : any ;
14+ }
15+
16+ interface SubOSApis {
17+ plansApi : any ;
18+ subscriptionApi : any ;
19+ transactionApi ?: any ;
20+ }
21+
22+ // eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
23+ interface ITransactionParams {
24+ page ?: number ;
25+ limit ?: number ;
26+ }
27+
28+ interface CancelSubscriptionParams {
29+ reasons : string [ ] ;
30+ }
31+
32+ const MAX_RETRY_ATTEMPTS = 2 ;
33+ const DEFAULT_RETRY_DELAY = 1000 ;
34+ const DEFAULT_TRANSACTION_LIMIT = 10 ;
35+ const DEFAULT_APP_NAME = 'Impler' ;
36+ const DEFAULT_APP_VERSION = '1.0.0' ;
37+
738export const useSubOSIntegration = ( ) => {
839 const { profileInfo } = useAppState ( ) ;
940
10- // SubOS components and utilities
11- const [ subOSComponents , setSubOSComponents ] = useState < {
12- PlanSelector : any ;
13- PlanCard : any ;
14- } | null > ( null ) ;
15- const [ subOSHooks , setSubOSHooks ] = useState < {
16- useCustomerPortal : any ;
17- } | null > ( null ) ;
18- const [ subOSApis , setSubOSApis ] = useState < {
19- plansApi : any ;
20- subscriptionApi : any ;
21- transactionApi ?: any ;
22- } | null > ( null ) ;
23-
24- // Configuration state
41+ const [ subOSComponents , setSubOSComponents ] = useState < SubOSComponents | null > ( null ) ;
42+ const [ subOSHooks , setSubOSHooks ] = useState < SubOSHooks | null > ( null ) ;
43+ const [ subOSApis , setSubOSApis ] = useState < SubOSApis | null > ( null ) ;
44+
2545 const [ isConfigured , setIsConfigured ] = useState ( false ) ;
2646 const [ loading , setLoading ] = useState ( true ) ;
2747 const [ error , setError ] = useState < string | null > ( null ) ;
2848
29- // Data states
3049 const [ subscription , setSubscription ] = useState < any > ( null ) ;
3150 const [ selectedPlan , setSelectedPlan ] = useState < Plan | null > ( null ) ;
3251
33- // Track if initial data has been fetched
3452 const initialFetchDone = useRef ( false ) ;
3553 const initializingSubOS = useRef ( false ) ;
3654
37- // Initialize SubOS
55+ const handleError = useCallback ( ( err : unknown , context : string ) => {
56+ const errorMessage = err instanceof Error ? err . message : String ( err ) ;
57+ console . error ( `${ context } :` , err ) ;
58+ setError ( errorMessage ) ;
59+
60+ return errorMessage ;
61+ } , [ ] ) ;
62+
63+ const isNetworkError = ( err : unknown ) : boolean => {
64+ return err instanceof Error && err . message . includes ( 'network' ) ;
65+ } ;
66+
67+ const delay = ( ms : number ) : Promise < void > => {
68+ return new Promise ( ( resolve ) => setTimeout ( resolve , ms ) ) ;
69+ } ;
70+
71+ const clearError = useCallback ( ( ) => {
72+ setError ( null ) ;
73+ } , [ ] ) ;
74+
3875 const initializeSubOS = useCallback ( async ( ) => {
39- if ( initializingSubOS . current || isConfigured ) return ;
76+ if ( initializingSubOS . current || isConfigured ) {
77+ return ;
78+ }
4079
4180 initializingSubOS . current = true ;
81+
4282 try {
4383 setLoading ( true ) ;
4484 setError ( null ) ;
4585
46- // Configure SubOS using environment variables with fallbacks
86+ // Configure/Initilize SubOS
4787 configureSubOS ( {
4888 apiEndpoint : process . env . NEXT_PUBLIC_SUBOS_API_ENDPOINT ,
4989 projectId : process . env . NEXT_PUBLIC_SUBOS_PROJECT_ID ,
5090 stripePublishableKey : process . env . NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY ,
51- appName : process . env . NEXT_PUBLIC_APP_NAME || 'Impler' ,
91+ appName : process . env . NEXT_PUBLIC_APP_NAME || DEFAULT_APP_NAME ,
5292 appEnvironment : process . env . NODE_ENV ,
53- appVersion : process . env . NEXT_PUBLIC_APP_VERSION || '1.0.0' ,
93+ appVersion : process . env . NEXT_PUBLIC_APP_VERSION || DEFAULT_APP_VERSION ,
5494 debug : process . env . NODE_ENV === 'development' ,
5595 } ) ;
5696
57- // Import SubOS module
5897 const subos = await import ( 'subos-frontend' ) ;
5998
60- // Extract components
61- const components = {
99+ setSubOSComponents ( {
62100 PlanSelector : subos . PlanSelector ,
63101 PlanCard : subos . PlanCard ,
64- } ;
102+ } ) ;
65103
66- // Extract hooks
67- const hooks = {
104+ setSubOSHooks ( {
68105 useCustomerPortal : subos . useCustomerPortal ,
69- } ;
106+ } ) ;
70107
71- // Extract APIs
72- const apis = {
108+ setSubOSApis ( {
73109 plansApi : subos . plansApi ,
74110 subscriptionApi : subos . subscriptionApi ,
75111 transactionApi : subos . transactionApi ,
76- } ;
112+ } ) ;
77113
78- setSubOSComponents ( components ) ;
79- setSubOSHooks ( hooks ) ;
80- setSubOSApis ( apis ) ;
81114 setIsConfigured ( true ) ;
82115 } catch ( err ) {
83- console . error ( 'Failed to initialize SubOS:' , err ) ;
84- setError ( err instanceof Error ? err . message : String ( err ) ) ;
116+ handleError ( err , 'Failed to initialize SubOS' ) ;
85117 } finally {
86118 setLoading ( false ) ;
87119 initializingSubOS . current = false ;
88120 }
89- } , [ isConfigured ] ) ;
121+ } , [ isConfigured , handleError ] ) ;
90122
91- // Fetch subscription with retry logic
92123 const fetchSubscription = useCallback (
93124 async ( retryCount = 0 ) : Promise < any > => {
94125 if ( ! subOSApis ?. subscriptionApi || ! isConfigured || ! profileInfo ?. email ) {
95126 return null ;
96127 }
97128
98129 try {
99- const subscriptionData = await subOSApis . subscriptionApi . getActiveSubscription ( profileInfo . email ) ;
130+ const response = await subOSApis . subscriptionApi . getActiveSubscription ( profileInfo . email ) ;
100131
101- if ( subscriptionData ?. data ) {
102- setSubscription ( subscriptionData . data ) ;
132+ if ( response ?. data ) {
133+ setSubscription ( response . data ) ;
103134 }
104135
105- return subscriptionData ;
136+ return response ;
106137 } catch ( err ) {
107- console . error ( 'Error fetching subscription:' , err ) ;
108- if ( retryCount < 2 && err instanceof Error && err . message . includes ( 'network' ) ) {
109- await new Promise ( ( resolve ) => setTimeout ( resolve , 1000 * ( retryCount + 1 ) ) ) ;
138+ // Retry logic for network errors
139+ if ( retryCount < MAX_RETRY_ATTEMPTS && isNetworkError ( err ) ) {
140+ await delay ( DEFAULT_RETRY_DELAY * ( retryCount + 1 ) ) ;
110141
111142 return fetchSubscription ( retryCount + 1 ) ;
112143 }
113- setError ( err instanceof Error ? err . message : String ( err ) ) ;
144+
145+ handleError ( err , 'Error fetching subscription' ) ;
114146
115147 return null ;
116148 }
117149 } ,
118- [ subOSApis , isConfigured , profileInfo ?. email ]
150+ [ subOSApis , isConfigured , profileInfo ?. email , handleError ]
151+ ) ;
152+
153+ const cancelSubscription = useCallback (
154+ // eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
155+ async ( { reasons } : CancelSubscriptionParams ) => {
156+ // Validation
157+ if ( ! subOSApis ?. subscriptionApi || ! isConfigured || ! profileInfo ?. email ) {
158+ throw new Error ( 'SubOS API not properly configured or email not available' ) ;
159+ }
160+
161+ try {
162+ const response = await subOSApis . subscriptionApi . cancelSubscription ( profileInfo . email , {
163+ cancellationMode : CancellationModeEnum . END_OF_PERIOD ,
164+ } ) ;
165+
166+ return response . data ;
167+ } catch ( err ) {
168+ handleError ( err , 'Error cancelling subscription' ) ;
169+ throw err ;
170+ }
171+ } ,
172+ [ subOSApis , isConfigured , profileInfo ?. email , handleError ]
119173 ) ;
120174
121- // Handle plan selection and checkout
122175 const selectPlan = useCallback (
123176 async ( plan : Plan ) => {
124- if ( ! subOSApis ?. plansApi || ! profileInfo ?. email ) return null ;
177+ // Validation
178+ if ( ! subOSApis ?. plansApi || ! profileInfo ?. email ) {
179+ return null ;
180+ }
125181
126182 try {
127183 setSelectedPlan ( plan ) ;
184+
128185 const response = await subOSApis . plansApi . createPaymentSession ( plan . code , {
129186 returnUrl : `${ window . location . origin } /subscription_status` ,
130187 externalId : profileInfo . email ,
131188 billableMetricCode : BILLABLEMETRIC_CODE_ENUM . ROWS ,
132189 } ) ;
133190
191+ // Extract checkout URL
134192 const checkoutUrl = response ?. data ?. checkoutUrl || response ?. data ?. url ;
193+
135194 if ( response ?. success && checkoutUrl ) {
136195 window . location . href = checkoutUrl ;
137196
@@ -140,18 +199,17 @@ export const useSubOSIntegration = () => {
140199
141200 return null ;
142201 } catch ( err ) {
143- console . error ( 'Error selecting plan:' , err ) ;
144- setError ( err instanceof Error ? err . message : String ( err ) ) ;
202+ handleError ( err , 'Error selecting plan' ) ;
145203
146204 return null ;
147205 }
148206 } ,
149- [ subOSApis , profileInfo ?. email ]
207+ [ subOSApis , profileInfo ?. email , handleError ]
150208 ) ;
151209
152- // Fetch transactions
153210 const fetchTransactions = useCallback (
154- async ( page = 1 , limit = 10 ) => {
211+ async ( page = 1 , limit = DEFAULT_TRANSACTION_LIMIT ) => {
212+ // Validation
155213 if ( ! subOSApis ?. transactionApi || ! isConfigured || ! profileInfo ?. email ) {
156214 return [ ] ;
157215 }
@@ -161,44 +219,18 @@ export const useSubOSIntegration = () => {
161219
162220 return response ?. data || [ ] ;
163221 } catch ( err ) {
164- console . error ( 'Error fetching transactions:' , err ) ;
165- setError ( err instanceof Error ? err . message : String ( err ) ) ;
222+ handleError ( err , 'Error fetching transactions' ) ;
166223
167224 return [ ] ;
168225 }
169226 } ,
170- [ subOSApis , isConfigured , profileInfo ?. email ]
171- ) ;
172-
173- // Handle subscription cancellation
174- const cancelSubscription = useCallback (
175- // eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
176- async ( { reasons } : { reasons : string [ ] } ) => {
177- if ( ! subOSApis ?. subscriptionApi || ! isConfigured || ! profileInfo ?. email ) {
178- throw new Error ( 'SubOS API not properly configured or email not available' ) ;
179- }
180-
181- try {
182- const response = await subOSApis . subscriptionApi . cancelSubscription ( profileInfo . email , {
183- cancellationMode : CancellationModeEnum . END_OF_PERIOD ,
184- } ) ;
185-
186- return response . data ;
187- } catch ( err ) {
188- console . error ( 'Error cancelling subscription:' , err ) ;
189- setError ( err instanceof Error ? err . message : String ( err ) ) ;
190- throw err ;
191- }
192- } ,
193- [ subOSApis , isConfigured , profileInfo ?. email ]
227+ [ subOSApis , isConfigured , profileInfo ?. email , handleError ]
194228 ) ;
195229
196- // Initialize on mount
197230 useEffect ( ( ) => {
198231 initializeSubOS ( ) ;
199232 } , [ initializeSubOS ] ) ;
200233
201- // Fetch initial data when configured
202234 useEffect ( ( ) => {
203235 if ( isConfigured && ! initialFetchDone . current ) {
204236 initialFetchDone . current = true ;
@@ -207,28 +239,31 @@ export const useSubOSIntegration = () => {
207239 } , [ isConfigured , fetchSubscription ] ) ;
208240
209241 return {
210- // Configuration state
211242 isConfigured,
212243 loading,
213244 error,
214245
215- // SubOS resources
246+ // SubOS Resources
216247 components : subOSComponents ,
217248 hooks : subOSHooks ,
218249 apis : subOSApis ,
219250
220- // Data
251+ // Data State
221252 subscription,
222253 selectedPlan,
223254
224- // Actions
255+ // Subscription Actions
225256 fetchSubscription,
226- fetchTransactions,
227- selectPlan,
228257 cancelSubscription,
229258
230- // Utilities
259+ // Plan Actions
260+ selectPlan,
261+
262+ // Transaction Actions
263+ fetchTransactions,
264+
265+ // Utility Actions
231266 reinitialize : initializeSubOS ,
232- clearError : ( ) => setError ( null ) ,
267+ clearError,
233268 } ;
234269} ;
0 commit comments