Skip to content

Commit d31473d

Browse files
authored
Merge pull request #1 from opsway-io/stripe
stripe
2 parents 45ba238 + 51db1c7 commit d31473d

File tree

7 files changed

+8859
-7106
lines changed

7 files changed

+8859
-7106
lines changed

.vscode/settings.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,8 @@
33
{
44
"mode": "auto"
55
}
6-
]
6+
],
7+
"files.watcherExclude": {
8+
"**/target": true
9+
}
710
}

apps/dashboard/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<!doctype html>
22
<html lang="en">
33
<head>
4+
<script async src="https://js.stripe.com/v3/pricing-table.js"></script>
45
<title>opsway.io</title>
56

67
<meta name="title" content="opsway.io" />

apps/dashboard/src/api/endpoints/teams/index.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,3 +165,45 @@ export async function postCreateCheckoutSession(
165165
window.location.href = body;
166166
}
167167
}
168+
169+
export interface IPostCustomerPortalResponse {
170+
url: string;
171+
}
172+
173+
export async function postCustomerPortal(
174+
teamId: string | number,
175+
): Promise<IPostCustomerPortalResponse> {
176+
const response = await client.post<IPostCustomerPortalResponse>(`/v1/teams/${teamId}/customer-portal`);
177+
178+
return response.data;
179+
}
180+
181+
export interface IGetProductsResponse {
182+
products: {
183+
id: string;
184+
name: string;
185+
price: number;
186+
currency: string;
187+
marketing_features: string[];
188+
}[];
189+
}
190+
191+
export async function getProducts(
192+
teamId: string | number,
193+
): Promise<IGetProductsResponse> {
194+
const response = await client.get<IGetProductsResponse>(`/v1/teams/${teamId}/products`);
195+
196+
return response.data;
197+
}
198+
199+
export interface IGetCustomerSessionResponse {
200+
sessionId: string;
201+
}
202+
203+
export async function getCustomerSession(
204+
teamId: string | number,
205+
): Promise<IGetCustomerSessionResponse> {
206+
const response = await client.get<IGetCustomerSessionResponse>(`/v1/teams/${teamId}/customer-session`);
207+
208+
return response.data;
209+
}

apps/dashboard/src/hooks/team.query.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,3 +108,40 @@ export const usePostCreateCheckoutSession = (
108108
},
109109
);
110110
};
111+
112+
export const usePostCustomerPortal = (
113+
) => {
114+
const currentTeamId = useAuthenticationStore((state) => state.currentTeamId);
115+
return useQuery([], () => {
116+
if (!currentTeamId) {
117+
return Promise.resolve(null);
118+
}
119+
return TeamsAPI.postCustomerPortal(currentTeamId);
120+
});
121+
};
122+
123+
124+
export const useGetProducts = (
125+
) => {
126+
const currentTeamId = useAuthenticationStore((state) => state.currentTeamId);
127+
return useQuery(["products"], () => {
128+
if (!currentTeamId) {
129+
return Promise.resolve(null);
130+
}
131+
return TeamsAPI.getProducts(currentTeamId);
132+
});
133+
};
134+
135+
136+
export const useGetCustomerSession = (
137+
) => {
138+
const currentTeamId = useAuthenticationStore((state) => state.currentTeamId);
139+
return useQuery([], () => {
140+
if (!currentTeamId) {
141+
return Promise.resolve(null);
142+
}
143+
return TeamsAPI.getCustomerSession(currentTeamId);
144+
}
145+
146+
);
147+
};

apps/dashboard/src/pages/Dashboard/Monitors/Detail/index.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,12 +175,15 @@ const MonitorDetailView: FunctionComponent = () => {
175175
sx={{
176176
maxHeight: 32,
177177
}}
178-
onChange={(_, value) => setTimeInterval(value)}
178+
onChange={(_, value) => {
179+
if (value != null) {
180+
setTimeInterval(value)
181+
}}}
179182
>
180183
<ToggleButton value={86400000}>Day</ToggleButton>
181184
<ToggleButton value={604800000}>Week</ToggleButton>
182185
<ToggleButton value={18144000000}>Month</ToggleButton>
183-
<ToggleButton value={18144000000}>Custom</ToggleButton>
186+
<ToggleButton value={217728000000}>Year</ToggleButton>
184187
</ToggleButtonGroup>
185188
</Stack>
186189
</Stack>

apps/dashboard/src/pages/Dashboard/Team/tabs/Subscription.tsx

Lines changed: 67 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -14,85 +14,94 @@ import {
1414
ListItemText,
1515
Typography,
1616
useTheme,
17-
Dialog,
18-
DialogActions,
19-
DialogContent,
20-
DialogContentText,
2117
} from "@mui/material";
2218
import { BsCheckLg } from "react-icons/bs";
23-
import { useCurrentTeam } from "../../../../hooks/team.query";
24-
import * as TeamsAPI from "../../../../api/endpoints/teams";
19+
import {
20+
useCurrentTeam,
21+
usePostCustomerPortal,
22+
useGetProducts,
23+
} from "../../../../hooks/team.query";
24+
25+
declare global {
26+
namespace JSX {
27+
interface IntrinsicElements {
28+
"stripe-pricing-table": React.DetailedHTMLProps<
29+
React.HTMLAttributes<HTMLElement>,
30+
HTMLElement
31+
>;
32+
}
33+
}
34+
}
2535

2636
const TeamPlanTabView: FunctionComponent = () => {
2737
const { data: team, isLoading: isLoadingTeam } = useCurrentTeam();
38+
const { data: customerPortal, isLoading: isLoadingPortal } =
39+
usePostCustomerPortal();
40+
const { data: products, isLoading: isLoadingProduct } =
41+
useGetProducts();
2842

2943
return (
3044
<>
31-
<Card>
45+
{isLoadingTeam ? (
46+
<Placeholder />
47+
) : (
48+
<>
49+
{team?.paymentPlan ? (
50+
<Card>
3251
<CardContent>
33-
{isLoadingTeam ? (
52+
{isLoadingProduct ? (
3453
<Placeholder />
3554
) : (
3655
<Grid container spacing={2} justifyContent="center">
56+
{products?.products.map((product) => (
3757
<Grid item>
3858
<PricingCard
39-
title="Free"
59+
key={product.id}
60+
title={product.name}
4061
description="For small teams"
41-
price="0£"
42-
features={[
43-
"Up to 10 users",
44-
"Up to 5 monitors",
45-
"Up to 5 status pages",
46-
"1 month of history",
47-
]}
48-
selected={team?.paymentPlan === "FREE"}
49-
teamId={team?.id!}
50-
priceLookupKey={"FREE"}
51-
/>
52-
</Grid>
53-
<Grid item>
54-
<PricingCard
55-
title="Team"
56-
description="For medium teams"
57-
price="20£"
58-
features={[
59-
"Up to 25 users",
60-
"Up to 25 monitors",
61-
"Up to 10 status pages",
62-
"Up to 5 on-call schedules",
63-
"1 year of history",
64-
]}
65-
selected={team?.paymentPlan === "TEAM"}
66-
teamId={team?.id!}
67-
priceLookupKey={"TEAM"}
68-
/>
69-
</Grid>
70-
<Grid item>
71-
<PricingCard
72-
title="Enterprise"
73-
description="For large teams"
74-
price="100£"
75-
features={[
76-
"Unlimited users",
77-
"Unlimited monitors",
78-
"Unlimited status pages",
79-
"Unlimited On-call schedules",
80-
"1 year of history",
81-
"Priority support",
82-
]}
83-
selected={team?.paymentPlan === "ENTERPRISE"}
84-
teamId={team?.id!}
85-
priceLookupKey={"ENTERPRISE"}
86-
/>
62+
price={product.currency + " " + product.price.toString()}
63+
features={product.marketing_features ? product.marketing_features : []}
64+
selected={team?.paymentPlan.toLocaleLowerCase() === product.name.toLocaleLowerCase()}
65+
/>
8766
</Grid>
67+
))}
8868
</Grid>
8969
)}
9070
</CardContent>
71+
<Button
72+
variant="contained"
73+
color="success"
74+
fullWidth
75+
sx={{
76+
marginTop: "auto",
77+
}}
78+
component="a"
79+
href={customerPortal?.url}
80+
>
81+
Manage Subscriptions
82+
</Button>
9183
</Card>
84+
) : (
85+
<Card>
86+
<CardHeader align="center" title="Choose a Subscription"/>
87+
<CardContent>
88+
<stripe-pricing-table
89+
pricing-table-id="prctbl_1PzENqAAd26uMXu2gnpkNNGV"
90+
publishable-key="pk_test_51NjhPuAAd26uMXu2mDsC5CrJzCokmFCMDEiyZFGanTQAy2exlztxyuLDpg2TXC26LK8j9wqnACLAAwEyWS0AJ4r500U1rDn672"
91+
client-reference-id={team?.id}
92+
// customer-session-client-secret={teamSubscription?.sessionId}
93+
></stripe-pricing-table>
94+
</CardContent>
95+
</Card>
96+
)}
97+
</>
98+
)}
99+
92100
</>
93101
);
94102
};
95103

104+
96105
export default TeamPlanTabView;
97106

98107
interface PricingCardProps {
@@ -101,8 +110,6 @@ interface PricingCardProps {
101110
price: string;
102111
features: string[];
103112
selected: boolean;
104-
teamId: number;
105-
priceLookupKey: string;
106113
}
107114

108115
const PricingCard: FunctionComponent<PricingCardProps> = ({
@@ -111,24 +118,8 @@ const PricingCard: FunctionComponent<PricingCardProps> = ({
111118
price,
112119
features,
113120
selected,
114-
teamId,
115-
priceLookupKey,
116121
}) => {
117122
const theme = useTheme();
118-
const [open, setOpen] = useState(false);
119-
120-
const handleClickOpen = () => {
121-
setOpen(true);
122-
};
123-
124-
const handleClose = () => {
125-
setOpen(false);
126-
};
127-
128-
const onSubscribe = () => {
129-
TeamsAPI.postCreateCheckoutSession(teamId, priceLookupKey)
130-
handleClose();
131-
};
132123

133124
return (
134125
<Card
@@ -140,14 +131,15 @@ const PricingCard: FunctionComponent<PricingCardProps> = ({
140131
flexDirection: "column",
141132
border: "none",
142133
}}
134+
variant={selected ? "outlined" : "elevation"}
143135
>
144136
<Box
145137
sx={{
146138
padding: 2,
147139
}}
148140
>
149141
<Typography variant="h4" color="text.primary">
150-
{title}
142+
{selected ? title + " (Current Plan)" : title}
151143
</Typography>
152144

153145
<Typography variant="body1" color="text.secondary">
@@ -202,53 +194,7 @@ const PricingCard: FunctionComponent<PricingCardProps> = ({
202194
</ListItem>
203195
))}
204196
</List>
205-
206-
{selected ? (
207-
<Button
208-
disabled
209-
variant="contained"
210-
color="success"
211-
fullWidth
212-
sx={{
213-
marginTop: "auto",
214-
}}
215-
component="a"
216-
>
217-
Selected
218-
</Button>
219-
) : (
220-
<Button
221-
variant="contained"
222-
color="success"
223-
fullWidth
224-
sx={{
225-
marginTop: "auto",
226-
}}
227-
component="a"
228-
onClick={handleClickOpen}
229-
>
230-
Select
231-
</Button>
232-
)}
233-
<Dialog
234-
open={open}
235-
onClose={handleClose}
236-
aria-labelledby="alert-dialog-title"
237-
aria-describedby="alert-dialog-description"
238-
>
239-
<DialogContent>
240-
<DialogContentText id="alert-dialog-description">
241-
This Action will change your subscription plan to {priceLookupKey}
242-
</DialogContentText>
243-
</DialogContent>
244-
<DialogActions>
245-
<Button onClick={handleClose}>CLOSE</Button>
246-
<Button onClick={onSubscribe} autoFocus>
247-
CHANGE PLAN
248-
</Button>
249-
</DialogActions>
250-
</Dialog>
251197
</CardContent>
252198
</Card>
253199
);
254-
};
200+
};

0 commit comments

Comments
 (0)