1
+ import React , { useEffect , useState } from "react" ;
1
2
import { ArrowIcon } from "lowcoder-design" ;
2
3
import styled from "styled-components" ;
3
4
import { trans } from "i18n" ;
4
5
import { useParams } from "react-router-dom" ;
5
6
import { HeaderBack } from "../permission/styledComponents" ;
6
7
import history from "util/history" ;
7
8
import { 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" ;
9
11
10
- const FieldWrapper = styled . div `
11
- margin-bottom: 32px;
12
- width: 408px;
13
- margin-top: 40px;
14
- ` ;
12
+ const { Text } = Typography ;
15
13
16
14
const Wrapper = styled . div `
17
15
padding: 32px 24px;
18
16
` ;
19
17
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
+
20
38
export function SubscriptionDetail ( ) {
21
39
const { subscriptionId } = useParams < { subscriptionId : string } > ( ) ;
22
40
const { productId } = useParams < { productId : string } > ( ) ;
23
41
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 ) ;
25
58
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
+ } ;
27
104
28
105
return (
29
106
< Wrapper >
@@ -32,10 +109,118 @@ export function SubscriptionDetail() {
32
109
{ trans ( "settings.subscription" ) }
33
110
</ span >
34
111
< ArrowIcon />
112
+ < span > { trans ( "subscription.details" ) } </ span >
35
113
</ 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 >
39
224
</ Wrapper >
40
225
) ;
41
226
}
0 commit comments