Skip to content

Commit 991e0cd

Browse files
siddharthkpCompuIves
authored andcommitted
Pro checkout (codesandbox#3096)
* clone patron and create a new page * there is a form * redirect patrons * starting to look good now * no height dance * bring the redirect back * improve login ux * Pro page for user * hook up controls to pro page * open payment info directly in modal * are you happy now typemom * add check for currentModal * Change type to plan * redirect pro users away from patron * fix dates + plan * attempt to clean up before adding more * even more cleanup * add expiring page * add loading state for reactivate * disable loading button * remove patron link from navigation * hide patron in menu for non-patrons * correct implementation for navigation patron * patrons always go to patron\ * change price to 12 for pro under the hoooood
1 parent 41fa22d commit 991e0cd

File tree

23 files changed

+877
-39
lines changed

23 files changed

+877
-39
lines changed

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@
1414
[![first-timers-only Friendly](https://img.shields.io/badge/first--timers--only-friendly-blue.svg)](http://www.firsttimersonly.com/)
1515
[![lerna](https://img.shields.io/badge/maintained%20with-lerna-cc00ff.svg)](https://lerna.js.org/)
1616

17-
An instantly ready, full-featured online IDE for web development on any device with a browser. Enabling you to start new projects quickly and prototype rapidly. With CodeSandbox, you can create web apps, experiment with code, test ideas, and share creations easily.
17+
An instantly ready, full-featured online IDE for web development on any device
18+
with a browser. Enabling you to start new projects quickly and prototype
19+
rapidly. With CodeSandbox, you can create web apps, experiment with code, test
20+
ideas, and share creations easily.
1821

1922
## Other CodeSandbox repositories
2023

packages/app/src/app/overmind/actions.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,13 +74,18 @@ type ModalName =
7474
| 'share'
7575
| 'signInForTemplates'
7676
| 'userSurvey';
77-
export const modalOpened: Action<{ modal: ModalName; message?: string }> = (
78-
{ state, effects },
79-
{ modal, message }
80-
) => {
77+
78+
export const modalOpened: Action<{
79+
modal: ModalName;
80+
message?: string;
81+
itemId?: string;
82+
}> = ({ state, effects }, { modal, message, itemId }) => {
8183
effects.analytics.track('Open Modal', { modal });
8284
state.currentModalMessage = message;
8385
state.currentModal = modal;
86+
if (state.currentModal === 'preferences') {
87+
state.preferences.itemId = itemId;
88+
}
8489
};
8590

8691
export const modalClosed: Action = ({ state, effects }) => {

packages/app/src/app/pages/Dashboard/Sidebar/Item/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ export class Item extends React.Component {
5656
<Route path={path}>
5757
{res => {
5858
const isActive =
59-
(!noActive && (res.match && res.match.isExact)) || active;
59+
(!noActive && res.match && res.match.isExact) || active;
6060
const isOpen =
6161
this.state.open === undefined ? isActive : this.state.open;
6262

packages/app/src/app/pages/Dashboard/Sidebar/TemplateItem/MyTemplateItem/index.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,14 @@ interface ITemplateItemProps {
1414
connectDropTarget?: any;
1515
}
1616

17-
const TemplateItemComponent: React.FC<
18-
ITemplateItemProps & RouteComponentProps
19-
> = ({ currentPath, isOver, canDrop, connectDropTarget, teamId }) => {
17+
const TemplateItemComponent: React.FC<ITemplateItemProps &
18+
RouteComponentProps> = ({
19+
currentPath,
20+
isOver,
21+
canDrop,
22+
connectDropTarget,
23+
teamId,
24+
}) => {
2025
const url = teamId
2126
? `/dashboard/teams/${teamId}/templates`
2227
: `/dashboard/templates`;

packages/app/src/app/pages/Patron/PricingModal/PricingChoice/ChangeSubscription/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ export const ChangeSubscription: React.FC<IChangeSubscriptionProps> = ({
101101
<LinkButton
102102
onClick={e => {
103103
e.preventDefault();
104-
modalOpened({ modal: 'preferences' });
104+
modalOpened({ modal: 'preferences', itemId: 'paymentInfo' });
105105
}}
106106
>
107107
user preferences

packages/app/src/app/pages/Patron/index.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,14 @@ import { PricingModal } from './PricingModal';
1111
import { Content } from './elements';
1212

1313
const Patron: React.FC = () => {
14-
const { actions } = useOvermind();
14+
const {
15+
state: { user },
16+
actions,
17+
} = useOvermind();
18+
19+
if (user && user.subscription && user.subscription.plan === 'pro') {
20+
location.href = '/pro';
21+
}
1522

1623
useEffect(() => {
1724
actions.patron.patronMounted();
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
/**
2+
* Adapted from app/components/CheckoutForm
3+
* that used to be used in Patron page
4+
*/
5+
6+
import React from 'react';
7+
import { format } from 'date-fns';
8+
import {
9+
injectStripe,
10+
CardElement,
11+
ReactStripeElements,
12+
} from 'react-stripe-elements';
13+
14+
import { logError } from '@codesandbox/common/lib/utils/analytics';
15+
16+
import {
17+
CardContainer,
18+
Form,
19+
FormField,
20+
Label,
21+
Input,
22+
Button,
23+
ErrorText,
24+
HelpText,
25+
} from './elements';
26+
27+
interface Props {
28+
name: string;
29+
isLoading: boolean;
30+
subscribe: (params: { token: string; coupon: string }) => void;
31+
stripe?: ReactStripeElements.StripeProps;
32+
error?: Error | string;
33+
hasCoupon?: boolean;
34+
disabled?: boolean;
35+
}
36+
37+
interface State {
38+
errors: {
39+
name?: string;
40+
stripe?: string;
41+
};
42+
name: string;
43+
coupon: string;
44+
loading: boolean;
45+
}
46+
47+
class CheckoutFormComponent extends React.PureComponent<Props, State> {
48+
state: State = {
49+
errors: {},
50+
name: this.props.name || '',
51+
coupon: '',
52+
loading: false,
53+
};
54+
55+
UNSAFE_componentWillReceiveProps(nextProps: Props) {
56+
if (nextProps.name !== this.props.name) {
57+
this.setState({ errors: {}, name: nextProps.name });
58+
}
59+
}
60+
61+
setName = (e: React.ChangeEvent<HTMLInputElement>) => {
62+
if (e) {
63+
this.setState({ errors: {}, name: e.target.value });
64+
}
65+
};
66+
67+
setCoupon = (e: React.ChangeEvent<HTMLInputElement>) => {
68+
if (e) {
69+
this.setState({ errors: {}, coupon: e.target.value });
70+
}
71+
};
72+
73+
handleSubmit = async (e: React.FormEvent) => {
74+
e.preventDefault();
75+
if (!this.state.name) {
76+
return this.setState({ errors: { name: 'Please provide a name ' } });
77+
}
78+
79+
this.setState({ loading: true, errors: {} });
80+
81+
// Within the context of `Elements`, this call to createToken knows which Element to
82+
// tokenize, since there's only one in this group.
83+
const { token, error } = await this.props.stripe.createToken({
84+
name: this.state.name,
85+
});
86+
if (error) {
87+
return this.setState({
88+
loading: false,
89+
errors: {
90+
stripe: error.message,
91+
},
92+
});
93+
}
94+
95+
try {
96+
await this.props.subscribe({
97+
token: token.id,
98+
coupon: this.state.coupon,
99+
});
100+
} catch (err) {
101+
logError(err);
102+
103+
return this.setState({
104+
loading: false,
105+
errors: {
106+
stripe: err.message,
107+
},
108+
});
109+
}
110+
111+
return this.setState({
112+
loading: false,
113+
});
114+
};
115+
116+
render() {
117+
const { isLoading, error, hasCoupon = false, disabled } = this.props;
118+
const { errors, loading: stateLoading } = this.state;
119+
120+
const loading = isLoading || stateLoading;
121+
122+
const stripeError = errors.stripe || error;
123+
124+
return (
125+
<Form onSubmit={this.handleSubmit} disabled={disabled}>
126+
<FormField>
127+
<Label htmlFor="cardholder-name">Cardholder Name</Label>
128+
<Input
129+
id="cardholder-name"
130+
value={this.state.name}
131+
onChange={this.setName}
132+
placeholder="Please enter your name"
133+
disabled={disabled}
134+
/>
135+
{errors.name != null && <ErrorText>{errors.name}</ErrorText>}
136+
</FormField>
137+
138+
<FormField>
139+
<Label htmlFor="card-number">Card</Label>
140+
141+
<CardContainer>
142+
<CardElement
143+
id="card-number"
144+
style={{ base: { color: 'white', fontWeight: '500' } }}
145+
disabled={disabled}
146+
/>
147+
</CardContainer>
148+
{stripeError != null && <ErrorText>{stripeError}</ErrorText>}
149+
</FormField>
150+
151+
{hasCoupon && (
152+
<FormField>
153+
<Label htmlFor="coupon">Coupon</Label>
154+
<div>
155+
<Input
156+
id="coupon"
157+
value={this.state.coupon}
158+
onChange={this.setCoupon}
159+
placeholder="Coupon or Discount Code"
160+
disabled={disabled}
161+
/>
162+
</div>
163+
</FormField>
164+
)}
165+
166+
<Button
167+
type="submit"
168+
disabled={loading || disabled}
169+
style={{ marginTop: '1rem', width: 300 }}
170+
>
171+
{loading ? 'Creating Subscription...' : 'Subscribe to Pro'}
172+
</Button>
173+
174+
<HelpText>
175+
You will be billed now and on the <b>{format(new Date(), 'do')}</b> of
176+
each month thereafter. You can cancel or change your subscription at
177+
any time.
178+
</HelpText>
179+
</Form>
180+
);
181+
}
182+
}
183+
184+
export const CheckoutForm = injectStripe(CheckoutFormComponent);

0 commit comments

Comments
 (0)