1+ import React , { useEffect , useState } from "react" ;
12import { ArrowIcon } from "lowcoder-design" ;
23import styled from "styled-components" ;
34import { trans } from "i18n" ;
45import { useParams } from "react-router-dom" ;
56import { HeaderBack } from "../permission/styledComponents" ;
67import history from "util/history" ;
78import { SUBSCRIPTION_SETTING } from "constants/routesURL" ;
8- import { getProduct } from '@lowcoder-ee/api/subscriptionApi' ;
9+ import { getProduct , getSubscriptionDetails , getInvoices , getCustomerPortalSession } from "api/subscriptionApi" ;
10+ import { Skeleton , Timeline , Card , Descriptions , Table , Typography , Button , message } from "antd" ;
911
10- const FieldWrapper = styled . div `
11- margin-bottom: 32px;
12- width: 408px;
13- margin-top: 40px;
14- ` ;
12+ const { Text } = Typography ;
1513
1614const Wrapper = styled . div `
1715 padding: 32px 24px;
1816` ;
1917
18+ const InvoiceLink = styled . a `
19+ color: #1d39c4;
20+ &:hover {
21+ text-decoration: underline;
22+ }
23+ ` ;
24+
25+ const CardWrapper = styled ( Card ) `
26+ width: 100%;
27+ margin-bottom: 24px;
28+ ` ;
29+
30+ const TimelineWrapper = styled . div `
31+ margin-top: 24px;
32+ ` ;
33+
34+ const ManageSubscriptionButton = styled ( Button ) `
35+ margin-top: 24px;
36+ ` ;
37+
2038export function SubscriptionDetail ( ) {
2139 const { subscriptionId } = useParams < { subscriptionId : string } > ( ) ;
2240 const { productId } = useParams < { productId : string } > ( ) ;
2341
24- const product = getProduct ( productId ) ;
42+ const [ product , setProduct ] = useState < any > ( null ) ;
43+ const [ subscription , setSubscription ] = useState < any > ( null ) ;
44+ const [ invoices , setInvoices ] = useState < any [ ] > ( [ ] ) ;
45+ const [ loading , setLoading ] = useState < boolean > ( true ) ;
46+
47+ useEffect ( ( ) => {
48+ const fetchData = async ( ) => {
49+ setLoading ( true ) ;
50+ try {
51+ // Fetch product details
52+ const productData = await getProduct ( productId ) ;
53+ setProduct ( productData ) ;
54+
55+ // Fetch enriched subscription details, including usage records
56+ const subscriptionDetails = await getSubscriptionDetails ( subscriptionId ) ;
57+ setSubscription ( subscriptionDetails ) ;
2558
26- console . log ( "product" , product ) ;
59+ // Fetch invoices separately using the previous function
60+ const invoiceData = await getInvoices ( subscriptionId ) ;
61+ setInvoices ( invoiceData ) ;
62+ } catch ( error ) {
63+ console . error ( "Error loading subscription details:" , error ) ;
64+ } finally {
65+ setLoading ( false ) ;
66+ }
67+ } ;
68+
69+ fetchData ( ) ;
70+ } , [ subscriptionId , productId ] ) ;
71+
72+ if ( loading ) {
73+ return < Skeleton style = { { margin : "40px" } } active paragraph = { { rows : 8 } } /> ;
74+ }
75+
76+ // Extracting data from the enriched response
77+ const subscriptionDetails = subscription ? subscription [ 0 ] : { } ;
78+ const usageRecords = subscription ? subscription [ 1 ] ?. data || [ ] : [ ] ;
79+
80+ const statusColor = subscriptionDetails ?. status === "active" ? "green" : "red" ;
81+ const customerId = subscriptionDetails ?. customer ; // Get the customer ID from subscription details
82+
83+ // Handle Customer Portal Session Redirect
84+ const handleCustomerPortalRedirect = async ( ) => {
85+ try {
86+ if ( ! customerId ) {
87+ message . error ( "Customer ID not available for the subscription." ) ;
88+ return ;
89+ }
90+
91+ // Get the Customer Portal session URL
92+ const portalSession = await getCustomerPortalSession ( customerId ) ;
93+ if ( portalSession && portalSession . url ) {
94+ // Redirect to the Stripe Customer Portal
95+ window . location . href = portalSession . url ;
96+ } else {
97+ message . error ( "Failed to generate customer portal session link." ) ;
98+ }
99+ } catch ( error ) {
100+ console . error ( "Error redirecting to customer portal:" , error ) ;
101+ message . error ( "An error occurred while redirecting to the customer portal." ) ;
102+ }
103+ } ;
27104
28105 return (
29106 < Wrapper >
@@ -32,10 +109,118 @@ export function SubscriptionDetail() {
32109 { trans ( "settings.subscription" ) }
33110 </ span >
34111 < ArrowIcon />
112+ < span > { trans ( "subscription.details" ) } </ span >
35113 </ HeaderBack >
36- < div >
37- < h1 > { `Subscription ID: ${ subscriptionId } ` } </ h1 >
38- </ div >
114+
115+ { /* Subscription Details Card */ }
116+ < CardWrapper title = { trans ( "subscription.subscriptionDetails" ) } style = { { marginTop : "40px" } } >
117+ < Descriptions bordered column = { 2 } >
118+ < Descriptions . Item label = { trans ( "subscription.productName" ) } >
119+ { product ?. name || "N/A" }
120+ </ Descriptions . Item >
121+ < Descriptions . Item contentStyle = { { color : statusColor } } label = { trans ( "subscription.status" ) } >
122+ { subscriptionDetails ?. status || "N/A" }
123+ </ Descriptions . Item >
124+ < Descriptions . Item label = { trans ( "subscription.startDate" ) } >
125+ { new Date ( subscriptionDetails ?. start_date * 1000 ) . toLocaleDateString ( ) || "N/A" }
126+ </ Descriptions . Item >
127+ < Descriptions . Item label = { trans ( "subscription.currentPeriodEnd" ) } >
128+ { new Date ( subscriptionDetails ?. current_period_end * 1000 ) . toLocaleDateString ( ) || "N/A" }
129+ </ Descriptions . Item >
130+ </ Descriptions >
131+ </ CardWrapper >
132+
133+ { /* Invoice Information Card */ }
134+ { invoices ?. length > 0 ? (
135+ invoices . map ( ( invoice : any ) => (
136+ < CardWrapper key = { invoice . id } title = { `${ trans ( "subscription.invoiceNumber" ) } - ${ invoice . number } ` } >
137+ { /* Invoice Summary */ }
138+ < Descriptions bordered size = "small" column = { 1 } >
139+ < Descriptions . Item label = { trans ( "subscription.customer" ) } >
140+ { invoice . customer_name || invoice . customer_email }
141+ </ Descriptions . Item >
142+ < Descriptions . Item label = { trans ( "subscription.billingReason" ) } >
143+ { invoice . billing_reason === "subscription_cycle" ? trans ( "subscription.subscriptionCycle" ) : "N/A" }
144+ </ Descriptions . Item >
145+ < Descriptions . Item label = { trans ( "subscription.status" ) } >
146+ < Text style = { { color : invoice . status === "paid" ? "green" : "red" } } >
147+ { invoice . status . charAt ( 0 ) . toUpperCase ( ) + invoice . status . slice ( 1 ) }
148+ </ Text >
149+ </ Descriptions . Item >
150+ < Descriptions . Item label = { trans ( "subscription.links" ) } >
151+ < InvoiceLink href = { invoice . hosted_invoice_url } target = "_blank" rel = "noopener noreferrer" >
152+ { trans ( "subscription.viewInvoice" ) }
153+ </ InvoiceLink > { " " }
154+ |{ " " }
155+ < InvoiceLink href = { invoice . invoice_pdf } target = "_blank" rel = "noopener noreferrer" >
156+ { trans ( "subscription.downloadPDF" ) }
157+ </ InvoiceLink >
158+ </ Descriptions . Item >
159+ </ Descriptions >
160+
161+ { /* Line Items Table */ }
162+ < Table
163+ style = { { marginTop : "16px" } }
164+ dataSource = { invoice . lines . data . filter ( ( lineItem : any ) => lineItem . amount !== 0 ) } // Filter out line items with amount = 0
165+ pagination = { false }
166+ rowKey = { ( lineItem ) => lineItem . id }
167+ columns = { [
168+ {
169+ title : trans ( "subscription.itemDescription" ) ,
170+ dataIndex : "description" ,
171+ key : "description" ,
172+ } ,
173+ {
174+ title : trans ( "subscription.amount" ) ,
175+ dataIndex : "amount" ,
176+ key : "amount" ,
177+ render : ( amount : number ) => `${ ( amount / 100 ) . toFixed ( 2 ) } ${ invoice . currency ?. toUpperCase ( ) } ` ,
178+ } ,
179+ {
180+ title : trans ( "subscription.periodStart" ) ,
181+ dataIndex : [ "period" , "start" ] ,
182+ key : "period_start" ,
183+ render : ( start : number ) => new Date ( start * 1000 ) . toLocaleDateString ( ) ,
184+ } ,
185+ {
186+ title : trans ( "subscription.periodEnd" ) ,
187+ dataIndex : [ "period" , "end" ] ,
188+ key : "period_end" ,
189+ render : ( end : number ) => new Date ( end * 1000 ) . toLocaleDateString ( ) ,
190+ } ,
191+ ] }
192+ />
193+ </ CardWrapper >
194+ ) )
195+ ) : (
196+ < CardWrapper title = { trans ( "subscription.invoices" ) } >
197+ < p > { trans ( "subscription.noInvoices" ) } </ p >
198+ </ CardWrapper >
199+ ) }
200+
201+ { /* Cost/Volume Development Timeline */ }
202+ < CardWrapper title = { trans ( "subscription.costVolumeDevelopment" ) } >
203+ < TimelineWrapper >
204+ < Timeline >
205+ { usageRecords ?. length > 0 ? (
206+ usageRecords . map ( ( record : any , index : number ) => (
207+ < Timeline . Item key = { index } color = { record . total_usage > 0 ? "green" : "gray" } >
208+ { `Usage for ${ record . total_usage } units on ${ new Date ( record . period . start * 1000 ) . toLocaleDateString ( ) } ` }
209+ </ Timeline . Item >
210+ ) )
211+ ) : (
212+ < Timeline . Item color = "gray" > { trans ( "subscription.noUsageRecords" ) } </ Timeline . Item >
213+ ) }
214+ </ Timeline >
215+ </ TimelineWrapper >
216+ </ CardWrapper >
217+
218+ { /* Manage Subscription Button */ }
219+ < CardWrapper title = { trans ( "subscription.manageSubscription" ) } style = { { marginBottom : "60px" } } >
220+ < ManageSubscriptionButton type = "primary" onClick = { handleCustomerPortalRedirect } >
221+ { trans ( "subscription.manageSubscription" ) }
222+ </ ManageSubscriptionButton >
223+ </ CardWrapper >
39224 </ Wrapper >
40225 ) ;
41226}
0 commit comments