Skip to content

Commit

Permalink
Feature/cit 26 user payments (#50)
Browse files Browse the repository at this point in the history
* most functionality working

* payment done

* Trigger build

* CIT-26: add api change

* yes

* CIT-26: fix Miraj's unforgivable mistakes, add alerts to event creation, add link

* [CIT-26] Cleanup, fix success URL

[CIT-26] Fix login redirect

[CIT-26] Fix 1 last redirect

[CIT-26] Use normal redirect w/o NextResponse

[CIT-26] Fix redirect

* [CIT-26] Bugfix on all redirects

* [CIT-26] Bugfix on all redirects

---------

Co-authored-by: Ishaan <[email protected]>
Co-authored-by: Daniel <[email protected]>
Co-authored-by: Ishaan Upadhyay <[email protected]>
  • Loading branch information
4 people authored Aug 10, 2023
1 parent 4ebec0d commit 5d797e1
Show file tree
Hide file tree
Showing 18 changed files with 455 additions and 83 deletions.
16 changes: 13 additions & 3 deletions citrus/app/(organizers)/organizer/signup/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

"use client"

import { useState, FormEvent, ChangeEvent} from "react";
import { useState, FormEvent, ChangeEvent } from "react";
import styles from "./signup.module.css";

import {
Expand All @@ -18,6 +18,8 @@ import {
FormHelperText,
InputRightElement
} from "@chakra-ui/react";
import { BASE_URL } from "@/lib/vars";
import { redirect } from "next/navigation";


const App = (): JSX.Element => {
Expand Down Expand Up @@ -48,13 +50,21 @@ const App = (): JSX.Element => {
// Handle form submission here
const res = await fetch('/api/organizers', {
method: 'POST',
body: JSON.stringify({org_id : org_id, display_name: org_id, password: password, email: email }),
body: JSON.stringify({ org_id: org_id, display_name: org_id, password: password, email: email }),
});
if (res.status === 200) {
redirect(BASE_URL + "/organizer/login");
} else if (res.body) {
const body = await res.json();
window.alert(body.error);
} else {
window.alert("Error signing up. Please try again.");
}
};

return (
<Flex
className={styles["custom-background"]}
className={styles["custom-background"]}
flexDirection="column"
width="100vw"
height="100vh"
Expand Down
3 changes: 3 additions & 0 deletions citrus/app/(users)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ export default async function RootLayout({
<li className='flex-1'>
<a href="/groups">Chats</a>
</li>
<li className='flex-1'>
<a href="/pricing">Premium!</a>
</li>
<NavBarLogin />
<li>
<a href="/organizer">Organizer portal</a>
Expand Down
2 changes: 1 addition & 1 deletion citrus/app/(users)/login/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ const App = (): JSX.Element => {
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
// Handle form submission here
signIn('user-credentials', {username: username, password: password, callbackUrl: '/user'})
signIn('user-credentials', {username: username, password: password, callbackUrl: '/myExperiences'})
};

return (
Expand Down
File renamed without changes.
35 changes: 35 additions & 0 deletions citrus/app/(users)/pricing/page.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
'use client'
import axios from "axios"
import { useState, useEffect } from "react"
import PricingCard from "@/components/PricingCard"

const Pricing = () => {
const [prices, setPrices] = useState([]);

useEffect(() => {
fetchPrices()
}, [])


const fetchPrices = async () => {
const {data} = await axios.get('/api/getproducts')
setPrices(data)
}

return (
<div className="w-9/12 m-auto">
<div className="mx-auto max-w-4xl text-center items-center">
<h2 className="text-3xl font-semibold leading-7 text-[#f1592a]">Pricing</h2>
<p className="mt-2 text-4xl font-bold tracking-tight sm:text-5xl">Choose the right plan for you!</p>
<p className="mx-auto mt-6 max-w-2xl text-lg leading-8 sm:text-center">Check out all the information below</p>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-8 max-w-[1040px] items-center mx-auto">
{prices && prices.map((price) => (
<PricingCard price={price} key={price.id} />
))}
</div>
</div>
)
}

export default Pricing
11 changes: 10 additions & 1 deletion citrus/app/(users)/signup/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

import { useState, FormEvent, ChangeEvent} from "react";
import styles from "./signup.module.css";
import { redirect } from 'next/navigation'
import { BASE_URL } from "@/lib/vars";

import {
Flex,
Expand All @@ -19,7 +21,6 @@ import {
InputRightElement
} from "@chakra-ui/react";


const App = (): JSX.Element => {
const [showPassword, setShowPassword] = useState(false);
const [email, setEmail] = useState("");
Expand Down Expand Up @@ -51,6 +52,14 @@ const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
method: 'POST',
body: JSON.stringify({"username" : username, "password": password, email: email }),
});
if (res.status === 200) {
redirect(BASE_URL + "/login");
} else if (res.body) {
const body = await res.json();
window.alert(body.error);
} else {
window.alert("Error signing up. Please try again.");
}
};

return (
Expand Down
59 changes: 59 additions & 0 deletions citrus/app/(users)/success/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import axios from "axios"
import { stripe } from "@/app/api/payment/route";
import { redirect } from "next/navigation"
import * as db from "@/lib/db"
import { BASE_URL } from "@/lib/vars"
import { getServerSession } from "next-auth";
import { authOptions } from "@/app/api/auth/[...nextauth]/route";


export default async function Page({ params }: { params: { id: string } }) {

const session = await getServerSession(authOptions)

if (!session || !session.user) {
redirect(BASE_URL + '/login')
}

const prisma = db.getClient();

let update = {
premium: true,
}

const currentSession = await stripe.checkout.sessions.retrieve(
params.id
)


if (currentSession.payment_status === 'paid') {

try {
await prisma.users.update({
where: {username: session.user.name},
data: update
});
} catch (e) {
return db.handleError(e);
}

// Replace with stripe.checkout.sessions.expire()
if (currentSession.status == 'open'){
const expireSession = await stripe.checkout.sessions.expire(
params.id
)
}
}


return (
<div className="w-9/12 m-auto">
<div className="mx-auto max-w-4xl text-center items-center">
<h2 className="text-3xl font-semibold leading-7 text-[#f1592a]">Success!</h2>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-8 max-w-[1040px] items-center mx-auto">

</div>
</div>
)
}
3 changes: 2 additions & 1 deletion citrus/app/api/experiences/[id]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { attending_status_type } from '@prisma/client';
import { redirect } from 'next/navigation';
import { getServerSession } from 'next-auth';
import { authOptions } from '@/app/api/auth/[...nextauth]/route';
import { BASE_URL } from '@/lib/vars';

const prisma = db.getClient();

Expand Down Expand Up @@ -135,7 +136,7 @@ export async function PUT(request: Request,
const body = await request.json() || undefined;

if (session === null && addUser != null) {
redirect('/login')
redirect(BASE_URL + '/login')
}


Expand Down
15 changes: 14 additions & 1 deletion citrus/app/api/experiences/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,6 @@ export async function GET(request: Request) {
* @apiSuccessExample Success-Response:
* HTTP/1.1 200 OK
* {
* "id": "1",
* "name": "Event 1",
* "description": "This is the first event",
* "location": "New York",
Expand All @@ -240,6 +239,20 @@ export async function POST(request: Request) {
const end_time = new Date(body.end_time);

try {
if (body.user_id) {
const user = await prisma.users.findUnique({
where: {
username: body.user_id
}
});
if (!user) {
return NextResponse.json({ error: "Organizing user does not exist." }, { status: 400 });
}
if (user.premium == false) {
return NextResponse.json({ error: "Organizing user does not have premium." }, { status: 400 });
}
}

const event = await prisma.experiences.create({
data: {
name: body.name,
Expand Down
12 changes: 12 additions & 0 deletions citrus/app/api/getproducts/route.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import Stripe from "stripe";
import { NextResponse, NextRequest } from 'next/server';


export async function GET(request) {
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
const prices = await stripe.prices.list({
limit: 1,
});

return NextResponse.json(prices.data.reverse());
}
36 changes: 36 additions & 0 deletions citrus/app/api/payment/route.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import Stripe from "stripe";
import { NextResponse, NextRequest } from "next/server";
import { redirect } from "next/navigation";
import * as db from "@/lib/db"
import { getServerSession } from "next-auth";
import { authOptions } from "../auth/[...nextauth]/route";
import { BASE_URL } from "@/lib/vars";


export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY)

export async function POST (request) {
const session = await getServerSession(authOptions);


if (!session || !session.user) {
redirect(BASE_URL + '/login')
}

let data = await request.json();
let priceId = data.priceId

const stripeSession = await stripe.checkout.sessions.create({
line_items: [
{
price: priceId,
quantity: 1
}
],
mode: 'payment',
success_url: BASE_URL + '/success/{CHECKOUT_SESSION_ID}',
cancel_url: BASE_URL+ '/cancel',
})

return NextResponse.json(stripeSession.url)
}
11 changes: 11 additions & 0 deletions citrus/components/AddEvent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,17 @@ export default function AddEvent() {
body: JSON.stringify({"name" : name, "description": description, "capacity": parseInt(capacity), "location": location,
"start_time": start, "end_time": end, "category": category, "tags": tags.split(","), ...organizer_fields }),
});

if (res.status === 200) {
window.alert("Event created successfully!");
}
else if (res.body) {
const body = await res.json();
window.alert("Event creation failed. " + body.error);
}
else {
window.alert("Event creation failed. " + res.statusText);
}
};

if (!session) {
Expand Down
6 changes: 3 additions & 3 deletions citrus/components/EventCardHolder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,11 @@ export default function EventCardHolder() {
link = 'dashboard';
}
}
else if(session?.user && route.includes('user')){
else if(session?.user && route.includes('myExperiences')){
if (session?.user) {
//basePathName = '/api/experiences?current_user_id=' + session.user.name;
basePathName = '/api/statuses';
link = 'user';
link = 'myExperiences';
}
}
else {
Expand All @@ -97,7 +97,7 @@ export default function EventCardHolder() {


useEffect(() => {
if ((session?.user && (route.includes('organizer')) || route.includes('experiences') || route.includes('user'))) {
if ((session?.user && (route.includes('organizer')) || route.includes('experiences') || route.includes('myExperiences'))) {
fetch(apiPathName)
.then(res => res.json())
.then(data => {
Expand Down
53 changes: 53 additions & 0 deletions citrus/components/PricingCard.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import axios from "axios"
import Link from "next/link"
import {AiFillCheckCircle} from 'react-icons/ai'

const PricingCard = ({price}) => {



// POST request
const handleSubscription = async (e) => {
e.preventDefault();
const { data } = await axios.post('/api/payment',
{
priceId: price.id
},
{
headers: {
"Content-Type": "application/json",
},
}
);
window.location.assign(data)
}

return (
<div className="border-gray-100 shadow-2xl border-4 text-center mt-10 max-w-[1040px]">
<div>
<div className="h-28 items-center font-bold">
<h4 className="text-3xl">{price.nickname}</h4>
<p>Premium Plan</p>
</div>
<div>
<div className="flex flex-col items-center justify-center pt-4">
<h1 className="text-5xl font-bold">
{(price.unit_amount / 100).toLocaleString('en-US', {
style: 'currency',
currency: 'USD'
})}
</h1>
</div>
<ul className="flex justify-center">
<li className="text-xl font-bold" >Premium plan description</li>
</ul>
<button className="mt-8 flex w-full justify-center rounded-md border border-transparent bg-[#f1592a] py-2 px-4 text-sm font-medium text-white shadow-sm" onClick={handleSubscription}>
Subscribe
</button>
</div>
</div>
</div>
)
}

export default PricingCard
1 change: 1 addition & 0 deletions citrus/lib/vars.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const BASE_URL = process.env.PROTOCOL_SCHEME + process.env.VERCEL_URL;
Loading

1 comment on commit 5d797e1

@vercel
Copy link

@vercel vercel bot commented on 5d797e1 Aug 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mirajismail is attempting to deploy a commit to a Personal Account on Vercel that is not owned by them.

In order for the commit to be deployed, @mirajismail must be granted access to the connected Vercel project.

If you're the owner of the Personal Account, transfer the project to a Vercel Team and start collaborating, or learn more.

Please sign in to comment.