Skip to content

Commit ae7c24b

Browse files
committed
Added components for billing and preferences
1 parent 38ec626 commit ae7c24b

File tree

6 files changed

+497
-588
lines changed

6 files changed

+497
-588
lines changed
Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
2+
import { loadStripe } from '@stripe/stripe-js';
3+
const STRIPE_KEY = process.env.NEXT_PUBLIC_APP_STRIPE_KEY;
4+
5+
export default function Billing(){
6+
7+
8+
9+
10+
11+
const redirectToCheckout = async (event) => {
12+
try {
13+
const stripe = await loadStripe(STRIPE_KEY);
14+
const subscriptionType = document.getElementById('paymentType').value;
15+
console.log(subscriptionType);
16+
17+
const response = await fetch(
18+
`${process.env.NEXT_PUBLIC_API_URL}/payments/stripe/create-checkout-session`,
19+
{
20+
method: 'POST',
21+
body: JSON.stringify({
22+
subType: subscriptionType,
23+
quantity: 1,
24+
operation: 'subscription',
25+
data: {},
26+
}),
27+
headers: {
28+
'Content-Type': 'application/json',
29+
},
30+
credentials: 'include'
31+
}
32+
);
33+
34+
const session = await response.json();
35+
if (session.error) {
36+
console.log('Creating the stripe session failed');
37+
return;
38+
}
39+
40+
const result = await stripe.redirectToCheckout({
41+
sessionId: session.sessionId,
42+
});
43+
44+
if (result.error) {
45+
console.log(result.error.message);
46+
}
47+
} catch (error) {
48+
console.log(error);
49+
}g
50+
};
51+
52+
const updateCardInfo = async () => {
53+
try {
54+
const stripe = await loadStripe(STRIPE_KEY);
55+
const subscriptionType = document.getElementById('paymentType').value;
56+
57+
const response = await fetch(
58+
`${process.env.NEXT_PUBLIC_API_URL}/payments/stripe/update-card`,
59+
{
60+
method: 'POST',
61+
credentials: 'include',
62+
body: JSON.stringify({
63+
subType: subscriptionType,
64+
}),
65+
headers: {
66+
'Content-Type': 'application/json',
67+
},
68+
}
69+
);
70+
71+
const session = await response.json();
72+
73+
await stripe.redirectToCheckout({
74+
sessionId: session.sessionId,
75+
});
76+
} catch (error) {
77+
console.log(error);
78+
}
79+
};
80+
81+
const cancelSubscription = async () => {
82+
try {
83+
const subscriptionType = document.getElementById('paymentType').value;
84+
const url = `${process.env.NEXT_PUBLIC_API_URL}/payments/stripe/cancel`;
85+
const response = await fetch(url, {
86+
method: 'PUT',
87+
credentials: 'include',
88+
body: JSON.stringify({
89+
subType: subscriptionType,
90+
}),
91+
headers: {
92+
'Content-Type': 'application/json',
93+
},
94+
});
95+
const data = await response.json();
96+
console.log(data.message);
97+
} catch (err) {
98+
console.log(err);
99+
}
100+
};
101+
102+
return(
103+
<div className="flex-1 xl:overflow-y-auto">
104+
<div className="mx-auto max-w-3xl px-4 py-10 sm:px-6 lg:px-8 lg:py-12">
105+
<h1 className="mb-3 text-3xl font-bold tracking-tight text-white">
106+
Billing
107+
</h1>
108+
109+
<div className="w-2/3 rounded-sm bg-neutral-800 px-4 py-4 pb-12">
110+
<div className="flex">
111+
<div>
112+
<h1 className="text-white">FREE</h1>
113+
<h1 className="text-xl text-white">Standard Account</h1>
114+
</div>
115+
<div className="ml-auto">
116+
<button className="mt-2 hidden border border-blue-600 px-2 py-1 text-blue-600 hover:bg-neutral-700">
117+
CHANGE PLAN
118+
</button>
119+
</div>
120+
</div>
121+
<h1 className="mt-4 font-semibold text-white">
122+
Usage Limits
123+
</h1>
124+
125+
<div className="mb-1 flex justify-between">
126+
<span className="text-base font-medium text-white">
127+
Terminal Usage
128+
</span>
129+
<span className="text-sm font-medium text-white">
130+
0%{' '}
131+
</span>
132+
</div>
133+
<div className="h-2.5 w-full rounded-full bg-neutral-700">
134+
<div
135+
className="h-2.5 rounded-full bg-blue-600"
136+
style={{ width: '0%' }}
137+
></div>
138+
</div>
139+
140+
<div className="mb-1 mt-4 flex justify-between">
141+
<span className="text-base font-medium text-white">
142+
CPU Burst Usage
143+
</span>
144+
<span className="text-sm font-medium text-white">0%</span>
145+
</div>
146+
<div className="h-2.5 w-full rounded-full bg-neutral-700">
147+
<div
148+
className="h-2.5 rounded-full bg-blue-600"
149+
style={{ width: '0%' }}
150+
></div>
151+
</div>
152+
153+
<h1 className="mt-4 text-white ">
154+
Container Hardware: Standard Compute
155+
</h1>
156+
</div>
157+
158+
<p className="mt-5 text-white">
159+
CTFGuide currently has a very generous grant from Google Cloud
160+
Platform, which allows us to provide free compute to our
161+
users. However, this grant is limited, and we may have to
162+
start charging for compute in the future. If we do, we will
163+
give you a 30 day notice before we start charging for compute.
164+
</p>
165+
166+
<div className="hidden items-center justify-between text-white">
167+
<hr className="mb-2 mt-2 border-neutral-600 text-white" />
168+
<h1 className="mt-4 text-center text-4xl">
169+
Upgrade to{' '}
170+
<span className="font-bold text-blue-500">
171+
CTFGuide Pro
172+
</span>
173+
</h1>
174+
<div className="grid grid-cols-2 gap-4">
175+
<div
176+
className="hover mt-4 rounded border border-neutral-700 px-4 py-4 text-white"
177+
style={{ cursor: 'pointer' }}
178+
>
179+
<h1 className="text-center text-3xl">Monthly</h1>
180+
<h1 className="text-center text-xl">$4.99/month</h1>
181+
</div>
182+
<div
183+
className="hover mt-4 rounded border border-neutral-700 px-4 py-4 text-white"
184+
style={{ cursor: 'pointer' }}
185+
>
186+
<h1 className="text-center text-3xl">Annually</h1>
187+
<h1 className="text-center text-xl">$35.88/year</h1>
188+
</div>
189+
</div>
190+
<h1 className="mb-1 mt-4 text-center text-xl">
191+
What do you get?
192+
</h1>
193+
<div className="px-2 py-1 text-center">
194+
<p>Access to exclusive learning content.</p>
195+
</div>
196+
<div className="mt-1 px-2 py-1 text-center">
197+
<p>Show of an exclusive CTFGuide Pro badge</p>
198+
</div>
199+
<div className="mt-1 px-2 py-1 text-center">
200+
<p>Animated profile pictures**</p>
201+
</div>
202+
<div className="mt-1 px-2 py-1 text-center">
203+
<p>Increased container time</p>
204+
</div>
205+
<div className="mt-1 px-2 py-1 text-center">
206+
<p>AI Tutor**</p>
207+
</div>
208+
<p className="mt-4 text-sm text-gray-500">
209+
* For the features marked with a star, it means it has not
210+
been released yet. For every month you have CTFGuide Pro, if
211+
the feature has not been implemented yet, you'll be given an
212+
additional free month of Pro.
213+
</p>
214+
</div>
215+
216+
<div className="hidden">
217+
<hr className="mt-4 border-neutral-500"></hr>
218+
<h1 className="mt-4 text-white"> Dev Testing</h1>
219+
220+
<select
221+
id="paymentType"
222+
className="mt-4 border-none bg-neutral-800 py-1 text-white"
223+
>
224+
<option value="CTFGuidePro">CTFGuidePro</option>
225+
<option value="CTFGuideStudentEDU">
226+
CTFGuideStudentsEDU
227+
</option>
228+
<option value="CTFGuideInstitutionEDU">
229+
CTFGuideInstitutionEDU
230+
</option>
231+
</select>
232+
233+
<br></br>
234+
<button
235+
onClick={redirectToCheckout}
236+
className="text-md mt-4 rounded-lg bg-blue-600 px-2 py-1 text-white"
237+
>
238+
Stripe Checkout Demo
239+
</button>
240+
<br></br>
241+
242+
<button
243+
onClick={updateCardInfo}
244+
className="text-md mt-4 rounded-lg bg-blue-600 px-2 py-1 text-white"
245+
>
246+
Update card infomation
247+
</button>
248+
<button
249+
onClick={cancelSubscription}
250+
className="text-md mt-4 rounded-lg bg-blue-600 px-2 py-1 text-white"
251+
>
252+
cancel subscription
253+
</button>
254+
</div>
255+
</div>
256+
</div>
257+
);
258+
}

src/components/settingComponents/generalPage.jsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ export default function General(){
2222
const [pfp, setPfp] = useState(`https://robohash.org/KshitijIsCool.png?set=set1&size=150x150`);
2323
const [open, setOpen] = useState(true);
2424

25+
26+
function pfpChange() {
27+
pfpChanged = true;
28+
}
2529

2630
const handlePopupOpen = () => {
2731
setIsPopupOpen(true);
@@ -64,7 +68,6 @@ export default function General(){
6468
setSelectedImage(file);
6569
}
6670

67-
6871
const handleSaveChanges = async () => {
6972
if (!selectedImage) {
7073
console.log("No image selected");
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import { getCookie } from '@/utils/request';
2+
3+
export default function Preferences(){
4+
5+
function savePreferences() {
6+
document.getElementById('savePreferences').innerHTML = 'Saving...';
7+
8+
var data = JSON.stringify({
9+
FRIEND_ACCEPT: document.getElementById('friend-notif').checked,
10+
CHALLENGE_VERIFY: document.getElementById('challenge-notif').checked,
11+
});
12+
13+
var xhr = new XMLHttpRequest();
14+
15+
xhr.addEventListener('readystatechange', function() {
16+
if (this.readyState === 4) {
17+
document.getElementById('savePreferences').innerHTML = 'Save';
18+
}
19+
});
20+
21+
xhr.open('PUT', `${process.env.NEXT_PUBLIC_API_URL}/account/preferences`);
22+
23+
xhr.setRequestHeader('Content-Type', 'application/json');
24+
let token = getCookie();
25+
xhr.setRequestHeader('Authorization', 'Bearer ' + token);
26+
xhr.withCredentials = true;
27+
28+
xhr.send(data);
29+
}
30+
31+
32+
return(
33+
<div className="flex-1 xl:overflow-y-auto">
34+
<div className="mx-auto max-w-3xl px-4 py-10 sm:px-6 lg:px-8 lg:py-12">
35+
<h1 className="text-3xl font-bold tracking-tight text-white">
36+
Email Preferences
37+
</h1>
38+
39+
<div className="mt-6 space-y-8 ">
40+
<fieldset>
41+
<legend className="sr-only">Notifications</legend>
42+
<div className="space-y-5">
43+
<div className="relative flex items-start">
44+
<div className="flex h-6 items-center">
45+
<input
46+
id="friend-notif"
47+
aria-describedby="comments-description"
48+
name="comments"
49+
type="checkbox"
50+
className="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-600"
51+
/>
52+
</div>
53+
<div className="ml-3 text-sm leading-6">
54+
<label
55+
htmlFor="comments"
56+
className="font-medium text-white"
57+
>
58+
Friend Requests
59+
</label>
60+
<p
61+
id="comments-description"
62+
className="text-neutral-400"
63+
>
64+
Get notified when someones accepts or sends you a
65+
friend request.
66+
</p>
67+
</div>
68+
</div>
69+
<div className="relative flex items-start">
70+
<div className="flex h-6 items-center">
71+
<input
72+
id="challenge-notif"
73+
aria-describedby="candidates-description"
74+
name="candidates"
75+
type="checkbox"
76+
className="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-600"
77+
/>
78+
</div>
79+
<div className="ml-3 text-sm leading-6">
80+
<label
81+
htmlFor="candidates"
82+
className="font-medium text-white"
83+
>
84+
Creator Notifications
85+
</label>
86+
<p
87+
id="candidates-description"
88+
className="text-neutral-400"
89+
>
90+
Get notified when your challenges get verified.
91+
</p>
92+
</div>
93+
</div>
94+
</div>
95+
</fieldset>
96+
97+
<div className="flex justify-end gap-x-3 pt-8">
98+
<button
99+
id="savePreferences"
100+
onClick={savePreferences}
101+
className="inline-flex justify-center rounded-md bg-blue-600 px-3 py-2 text-sm font-semibold text-white shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600"
102+
>
103+
Save
104+
</button>
105+
</div>
106+
</div>
107+
</div>
108+
</div>
109+
);
110+
}

0 commit comments

Comments
 (0)