Skip to content

Commit

Permalink
feat(payment): created proper routes to pay using stripe
Browse files Browse the repository at this point in the history
  • Loading branch information
elliotsaha committed Mar 22, 2024
1 parent 2b20184 commit e53ed8e
Show file tree
Hide file tree
Showing 9 changed files with 440 additions and 77 deletions.
61 changes: 59 additions & 2 deletions src/app/(pages)/events/detail/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
"use client";
import { useState } from "react";
import { useEffect, useState } from "react";
import {
Box,
useToast,
Container,
Button,
Text,
Expand All @@ -19,15 +20,58 @@ import { TennisEvent } from "@types";
import { useQuery } from "@tanstack/react-query";
import { useRouter } from "next/navigation";
import { format, parseISO } from "date-fns";
import { useSearchParams } from "next/navigation";
import {
LocationPinIcon,
ClockIcon,
SingleUserIcon,
UserFriendsIcon,
} from "@icons";
import { getClientSession } from "@utils";

const EventDetail = ({ params }: { params: { id: string } }) => {
const router = useRouter();
const statusToast = useToast();
const searchParams = useSearchParams();

const soldOut = searchParams.get("sold-out");
const unsuccessfulPayment = searchParams.get("unsuccessful-payment");
const successfulPayment = searchParams.get("successful-payment");
const purchased = searchParams.get("purchased");

useEffect(() => {
if (soldOut === "true") {
statusToast({
id: "sold_out",
title: "Unfortunately, this event just sold out.",
status: "error",
});
}

if (purchased === "true") {
statusToast({
id: "purchased",
title: "You have already purchased this ticket.",
status: "error",
});
}

if (unsuccessfulPayment === "true") {
statusToast({
id: "unsuccessful_payment",
title: "An error has occurred and you will be issued a refund.",
status: "error",
});
}

if (successfulPayment === "true") {
statusToast({
id: "successful_payment",
title: "Successfully purchased ticket.",
status: "success",
});
}
}, [soldOut, statusToast, unsuccessfulPayment, successfulPayment, purchased]);

const getEvent = async () => {
const event = await axios.post(
Expand All @@ -53,8 +97,13 @@ const EventDetail = ({ params }: { params: { id: string } }) => {
const [purchaseLoading, setPurchaseLoading] = useState(false);

const purchaseTicket = async () => {
const session = await getClientSession();
try {
setPurchaseLoading(true);

if (!session) {
router.push(`/login/?redirect=/events/detail/${params.id}`);
}
const res = await axios.post(
`${process.env.NEXT_PUBLIC_HOSTNAME}/api/events/ticket/purchase`,
{ event_id: params.id },
Expand Down Expand Up @@ -87,8 +136,16 @@ const EventDetail = ({ params }: { params: { id: string } }) => {
colorScheme="brand"
onClick={purchaseTicket}
isLoading={purchaseLoading}
loadingText="Reserving Ticket"
isDisabled={
(!data.reserved && data.available_tickets <= 0) || data.purchased
}
>
Buy Ticket
{data.purchased
? "Purchased Ticket"
: !data.reserved && data.available_tickets <= 0
? "Sold out"
: "Buy Ticket"}
</Button>
</Flex>
);
Expand Down
135 changes: 89 additions & 46 deletions src/app/(pages)/events/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
"use client";
import {
useToast,
Container,
Box,
Img,
Text,
Heading,
Expand All @@ -15,6 +17,7 @@ import {
Button,
Avatar,
AvatarGroup,
Badge,
} from "@chakra-ui/react";
import { useQuery } from "@tanstack/react-query";
import axios from "axios";
Expand All @@ -28,6 +31,8 @@ import {
UserFriendsIcon,
} from "@icons";
import Link from "next/link";
import { useSearchParams } from "next/navigation";
import { useEffect } from "react";

// get all events
const getEvents = async () => {
Expand All @@ -43,6 +48,20 @@ const Events = () => {
queryFn: getEvents,
});

const searchParams = useSearchParams();
const unsuccessfulPayment = searchParams.get("unsuccessful-payment");

const statusToast = useToast();

useEffect(() => {
if (unsuccessfulPayment === "true") {
statusToast({
id: "unsuccessful_payment",
title: "An error has occurred and you were not charged",
status: "error",
});
}
}, [unsuccessfulPayment, statusToast]);
return (
<Container maxW="container.xl" py={{ base: "12", sm: "20" }}>
<Flex flexDir="column">
Expand Down Expand Up @@ -81,6 +100,7 @@ const Events = () => {
<Flex flexDir="column" gap="4">
{data.map((i, idx) => (
<Card
minH={{ base: "unset", md: "60" }}
key={idx}
direction={{ base: "column", md: "row" }}
overflow="hidden"
Expand Down Expand Up @@ -161,21 +181,45 @@ const Events = () => {
</Text>
</Flex>

<Flex
alignItems={{ base: "flex-start", sm: "center" }}
flexDir={{ base: "column", sm: "row" }}
>
<Flex alignItems="center" gap="1">
<Icon
as={UserFriendsIcon}
color="gray.500"
fontSize="14"
/>
<Text fontWeight="semibold">Attendees:</Text>
{i.opening_status === "Open" && (
<Flex
alignItems={{ base: "flex-start", sm: "center" }}
flexDir={{ base: "column", sm: "row" }}
>
<Flex alignItems="center" gap="1">
<Icon
as={UserFriendsIcon}
color="gray.500"
fontSize="14"
/>
<Text fontWeight="semibold">Attendees:</Text>
<AvatarGroup
size="xs"
max={3}
display={{ base: "none", sm: "flex" }}
>
<Avatar
name="Segun Adebayo"
src="https://bit.ly/sage-adebayo"
/>
<Avatar
name="Kent Dodds"
src="https://bit.ly/kent-c-dodds"
/>
<Avatar
name="Prosper Otemuyiwa"
src="https://bit.ly/prosper-baba"
/>
<Avatar
name="Christian Nwamba"
src="https://bit.ly/code-beast"
/>
</AvatarGroup>
</Flex>
<AvatarGroup
size="xs"
size="sm"
max={3}
display={{ base: "none", sm: "flex" }}
display={{ base: "flex", sm: "none" }}
>
<Avatar
name="Segun Adebayo"
Expand All @@ -195,41 +239,40 @@ const Events = () => {
/>
</AvatarGroup>
</Flex>
<AvatarGroup
size="sm"
max={3}
display={{ base: "flex", sm: "none" }}
>
<Avatar
name="Segun Adebayo"
src="https://bit.ly/sage-adebayo"
/>
<Avatar
name="Kent Dodds"
src="https://bit.ly/kent-c-dodds"
/>
<Avatar
name="Prosper Otemuyiwa"
src="https://bit.ly/prosper-baba"
/>
<Avatar
name="Christian Nwamba"
src="https://bit.ly/code-beast"
/>
</AvatarGroup>
</Flex>
)}
</Flex>
</CardBody>
<Button
as={Link}
href={`/events/detail/${i.id}`}
colorScheme="brand"
position={{ base: "relative", md: "absolute" }}
right={{ base: "unset", md: "4" }}
bottom={{ base: "unset", md: "4" }}
>
Join Event
</Button>
{i.opening_status === "Open" ? (
<Button
as={Link}
href={`/events/detail/${i.id}`}
colorScheme="brand"
position={{ base: "relative", md: "absolute" }}
right={{ base: "unset", md: "4" }}
bottom={{ base: "unset", md: "4" }}
>
Join Event
</Button>
) : (
<Box
position={{ base: "relative", md: "absolute" }}
right={{ base: "unset", md: "4" }}
bottom={{ base: "unset", md: "4" }}
>
<Badge
size="xl"
colorScheme="orange"
fontSize="14"
w="100%"
textAlign="center"
px="2"
py="1"
borderRadius="6"
>
OPENING SOON
</Badge>
</Box>
)}
</Stack>
</Card>
))}
Expand Down
4 changes: 3 additions & 1 deletion src/app/api/events/create/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,15 @@ export const POST = async (request: NextRequest) => {
event_id: res.id,
event_name: res.name,
ticket_price: res.ticket_price,
available_tickets: res.initial_tickets,
available_tickets: res.available_tickets,
reserved_tickets: [],
attendees: [],
reservation_expire_tasks: [],
});

return ServerResponse.success(attendeeList);
} catch (e) {
console.log(e);
logger.error(e);
return ServerResponse.serverError(e);
}
Expand Down
41 changes: 38 additions & 3 deletions src/app/api/events/detail/route.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import { contentfulClient } from "@lib";
import { ServerResponse } from "@helpers";
import { contentfulClient, connectToDatabase } from "@lib";
import { ServerResponse, getSession } from "@helpers";
import { TypeEventSkeleton } from "@types";
import { Asset } from "contentful";
import z from "zod";
import { NextRequest } from "next/server";
import { AttendeeList } from "@models";

const detailSchema = z.object({
id: z.string({ required_error: "Event ID is required" }),
});

export const POST = async (request: NextRequest) => {
await connectToDatabase();

try {
const { id } = await request.json();

Expand All @@ -22,6 +25,35 @@ export const POST = async (request: NextRequest) => {
return ServerResponse.userError("Invalid event ID");
}

const attendeeList = await AttendeeList.findOne({
event_id: res.sys.id,
});

let availableTickets = res.fields.amountOfTickets;

if (attendeeList) {
availableTickets = attendeeList?.available_tickets;
}

let reserved = false;
let purchased = false;

const { session } = await getSession(request);

if (session && attendeeList) {
if (
attendeeList.reservation_expire_tasks.find(
(e) => e.user_id === session.user.userId,
)
) {
reserved = true;
}

if (attendeeList.attendees.includes(session.user.userId)) {
purchased = true;
}
}

const event = {
id: res.sys.id,
name: res.fields.name,
Expand All @@ -31,14 +63,17 @@ export const POST = async (request: NextRequest) => {
cover_image: `https:${(res.fields.coverImage as Asset)?.fields?.file
?.url}`,
description: res.fields.description,
initial_tickets: res.fields.amountOfTickets,
available_tickets: availableTickets,
reserved,
purchased,
};

return ServerResponse.success(event);
} else {
return ServerResponse.userError("Invalid event ID");
}
} catch (e) {
console.log(e);
return ServerResponse.userError("Event not found");
}
};
Loading

0 comments on commit e53ed8e

Please sign in to comment.