Skip to content

Commit c8e4be4

Browse files
committed
refactor: update SubscriptionsStore to return structured subscription data, enhance SubscriptionList component for better rendering, and simplify SubscriptionsBlock integration
1 parent 9e512dd commit c8e4be4

File tree

3 files changed

+90
-88
lines changed

3 files changed

+90
-88
lines changed

src/features/billing/model/subscriptions.store.ts

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,16 @@ import { Subscription } from './interfaces/subscription';
1010
import { liteproxysStore, restApiTiersStore } from 'src/shared/stores';
1111

1212
export class SubscriptionsStore {
13-
get subscriptions(): Subscription[] {
13+
get subscriptions(): { liteproxy: Subscription | null; restApi: Subscription | null } {
1414
const reastApiTier = restApiTiersStore.selectedTier$.value;
1515
const liteproxyTier = liteproxysStore.selectedTier$.value;
1616

1717
return mapTierToSubscription(reastApiTier, liteproxyTier);
1818
}
1919

2020
get subscriptionsLoading(): boolean {
21-
return restApiTiersStore.selectedTier$.isLoading;
21+
console.log(restApiTiersStore.selectedTier$.isLoading, liteproxysStore.selectedTier$.isLoading);
22+
return restApiTiersStore.selectedTier$.isLoading || liteproxysStore.selectedTier$.isLoading;
2223
}
2324

2425
constructor() {
@@ -33,29 +34,37 @@ export class SubscriptionsStore {
3334
}
3435

3536
fetchSubscriptions = createAsyncAction(async () => {
36-
await restApiTiersStore.fetchSelectedTier();
37+
restApiTiersStore.fetchSelectedTier();
38+
liteproxysStore.fetchSelectedTier();
3739
});
3840
}
3941

4042
function mapTierToSubscription(
4143
reastApiTier: RestApiSelectedTier | null,
4244
liteproxyTier: DTOProjectLiteproxyTierDetail | null
43-
): Subscription[] {
45+
): { liteproxy: Subscription | null; restApi: Subscription | null } {
46+
const liteproxyNextPayment =
47+
liteproxyTier?.name === 'Free'
48+
? undefined
49+
: liteproxyTier?.next_payment
50+
? new Date(liteproxyTier.next_payment)
51+
: undefined;
52+
4453
const liteproxySubscription: Subscription | null = liteproxyTier && {
4554
id: `liteproxy-${liteproxyTier.id}`,
46-
plan: `Liteproxy «${liteproxyTier.name}»`,
55+
plan: `Liteservers «${liteproxyTier.name}»`,
4756
interval: 'Monthly',
48-
renewsDate: liteproxyTier.next_payment ? new Date(liteproxyTier.next_payment) : undefined,
57+
renewsDate: liteproxyNextPayment ? new Date(liteproxyNextPayment) : undefined,
4958
price: new UsdCurrencyAmount(liteproxyTier.usd_price)
5059
};
5160

52-
const tonapiSubscription: Subscription | null = reastApiTier && {
61+
const restApiSubscription: Subscription | null = reastApiTier && {
5362
id: `tonapi-${reastApiTier.id}`,
54-
plan: `TonAPI «${reastApiTier.name}»`,
63+
plan: `REST API «${reastApiTier.name}»`,
5564
interval: reastApiTier.type === 'monthly' ? 'Monthly' : 'Pay as you go',
5665
renewsDate: reastApiTier.renewsDate,
5766
price: reastApiTier?.price
5867
};
5968

60-
return [liteproxySubscription, tonapiSubscription].filter(s => s !== null);
69+
return { liteproxy: liteproxySubscription, restApi: restApiSubscription };
6170
}

src/features/billing/ui/SubscriptionList.tsx

Lines changed: 68 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,15 @@ import {
77
MenuItem,
88
Skeleton,
99
Spinner,
10-
Text
10+
Text,
11+
Divider
1112
} from '@chakra-ui/react';
1213
import { FunctionComponent } from 'react';
1314
import { toDate, VerticalDotsIcon16, MenuButtonIcon } from 'src/shared';
1415
import { observer } from 'mobx-react-lite';
1516
import { SubscriptionsStore } from 'src/features/billing';
1617
import { useNavigate } from 'react-router-dom';
18+
import { Subscription } from '../model/interfaces/subscription';
1719

1820
interface SubscriptionListProps {
1921
subscriptionsStore: SubscriptionsStore;
@@ -22,24 +24,81 @@ interface SubscriptionListProps {
2224
hasSubscriptions?: boolean;
2325
}
2426

27+
interface SubscriptionListItemProps {
28+
subscription?: Subscription;
29+
}
30+
31+
const SubscriptionListItem: FunctionComponent<SubscriptionListItemProps> = ({ subscription }) => {
32+
const navigate = useNavigate();
33+
34+
return (
35+
<Flex justify="space-between" py="1" gap="4">
36+
<Flex flexDirection="column" flex="1">
37+
{subscription ? (
38+
<>
39+
<Text fontSize="md" fontWeight="semibold">
40+
{subscription.plan}
41+
</Text>
42+
<Text fontSize="sm" color="text.secondary">
43+
{subscription.interval}
44+
{subscription.renewsDate && ` · Renews ${toDate(subscription.renewsDate)}`}
45+
</Text>
46+
</>
47+
) : (
48+
<>
49+
<Skeleton h="5" w="150px" />
50+
<Skeleton h="4" w="200px" />
51+
</>
52+
)}
53+
</Flex>
54+
<Box gap="1">
55+
{subscription ? (
56+
<Flex align="center">
57+
<Text fontSize="md" fontWeight="semibold">
58+
{subscription.price.stringCurrencyAmount}
59+
</Text>
60+
<Menu placement="bottom-end">
61+
<MenuButtonIcon
62+
aria-label="options"
63+
icon={<VerticalDotsIcon16 />}
64+
ml="2"
65+
/>
66+
<MenuList w="132px">
67+
<MenuItem onClick={() => navigate('/tonapi/pricing')}>
68+
<Text textStyle="label2">Change plan</Text>
69+
</MenuItem>
70+
</MenuList>
71+
</Menu>
72+
</Flex>
73+
) : (
74+
<>
75+
<Skeleton h="5" w="80px" />
76+
<Box w="24px" />
77+
</>
78+
)}
79+
</Box>
80+
</Flex>
81+
);
82+
};
83+
2584
const SubscriptionList: FunctionComponent<SubscriptionListProps> = ({
2685
subscriptionsStore,
27-
isLoading = subscriptionsStore.subscriptionsLoading,
28-
hasEverLoaded = false,
29-
hasSubscriptions = subscriptionsStore.subscriptions.length > 0,
3086
...props
3187
}) => {
32-
const navigate = useNavigate();
88+
const isLoading = subscriptionsStore.subscriptionsLoading;
3389

3490
// First load - show Spinner
35-
if (!hasEverLoaded && isLoading) {
91+
if (isLoading) {
3692
return (
3793
<Center h="96px">
3894
<Spinner />
3995
</Center>
4096
);
4197
}
4298

99+
const { liteproxy, restApi } = subscriptionsStore.subscriptions;
100+
const hasSubscriptions = Boolean(liteproxy || restApi);
101+
43102
// No data and not loading - show Empty message
44103
if (!isLoading && !hasSubscriptions) {
45104
return (
@@ -52,63 +111,9 @@ const SubscriptionList: FunctionComponent<SubscriptionListProps> = ({
52111
// Card list with data or skeleton
53112
return (
54113
<Box minH="100px" {...props}>
55-
{isLoading && hasEverLoaded
56-
? // Show skeleton while loading if data has been loaded before
57-
Array.from({ length: 1 }).map((_, index) => (
58-
<Flex
59-
key={index}
60-
align="center"
61-
justify="space-between"
62-
py="2"
63-
gap="4"
64-
>
65-
<Flex flexDirection="column" gap="1" flex="1">
66-
<Skeleton h="5" w="150px" />
67-
<Skeleton h="4" w="200px" />
68-
</Flex>
69-
<Flex align="center" gap="4" flexShrink={0}>
70-
<Skeleton h="5" w="80px" />
71-
<Box w="24px" />
72-
</Flex>
73-
</Flex>
74-
))
75-
: // Show real data
76-
subscriptionsStore.subscriptions.map(subscription => (
77-
<Flex
78-
key={subscription.id}
79-
align="center"
80-
justify="space-between"
81-
py="2"
82-
gap="4"
83-
>
84-
<Flex flexDirection="column" gap="1" flex="1">
85-
<Text fontSize="md" fontWeight="semibold">
86-
{subscription.plan}
87-
</Text>
88-
<Text fontSize="sm" color="text.secondary">
89-
{subscription.interval} · { subscription.renewsDate && `Renews ${toDate(subscription.renewsDate)}`}
90-
</Text>
91-
</Flex>
92-
<Flex align="center" gap="4" flexShrink={0}>
93-
<Text fontSize="md" fontWeight="semibold">
94-
{subscription.price.stringCurrencyAmount}
95-
</Text>
96-
<Menu placement="bottom-end">
97-
<MenuButtonIcon
98-
aria-label="options"
99-
icon={<VerticalDotsIcon16 />}
100-
/>
101-
<MenuList w="132px">
102-
<MenuItem
103-
onClick={() => navigate('/tonapi/pricing')}
104-
>
105-
<Text textStyle="label2">Change plan</Text>
106-
</MenuItem>
107-
</MenuList>
108-
</Menu>
109-
</Flex>
110-
</Flex>
111-
))}
114+
<SubscriptionListItem subscription={!isLoading && restApi ? restApi : undefined} />
115+
<Divider />
116+
<SubscriptionListItem subscription={!isLoading && liteproxy ? liteproxy : undefined} />
112117
</Box>
113118
);
114119
};

src/pages/balance/SubscriptionsBlock.tsx

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,14 @@ import { SubscriptionsStore, SubscriptionList } from 'src/features/billing';
77

88
const SubscriptionsBlock: FC = () => {
99
const subscriptionsStore = useLocalObservable(() => new SubscriptionsStore());
10-
const [hasEverLoaded, setHasEverLoaded] = useState(false);
11-
const hasSubscriptions = subscriptionsStore.subscriptions.length > 0;
12-
const isLoading = subscriptionsStore.subscriptionsLoading;
13-
14-
useEffect(() => {
15-
if (!isLoading && hasSubscriptions) {
16-
setHasEverLoaded(true);
17-
}
18-
}, [isLoading, hasSubscriptions]);
1910

2011
return (
2112
<Overlay height="auto" p="0" display="flex" flexDirection="column" flex="1" minW="330px">
2213
<Box px="6" py="5">
23-
<Text fontSize="lg" fontWeight="semibold" mb="2">Active Plans</Text>
24-
<SubscriptionList
25-
subscriptionsStore={subscriptionsStore}
26-
isLoading={isLoading}
27-
hasEverLoaded={hasEverLoaded}
28-
hasSubscriptions={hasSubscriptions}
29-
/>
14+
<Text fontSize="lg" mb="1" fontWeight="semibold">
15+
Active Plans
16+
</Text>
17+
<SubscriptionList subscriptionsStore={subscriptionsStore} />
3018
</Box>
3119
</Overlay>
3220
);

0 commit comments

Comments
 (0)