Skip to content

Commit

Permalink
Merge pull request #47 from Marx-wrld/Account-section-build
Browse files Browse the repository at this point in the history
Account section build
  • Loading branch information
Marx-wrld authored Aug 16, 2023
2 parents 239e869 + 13283bd commit 835d798
Show file tree
Hide file tree
Showing 18 changed files with 257 additions and 40 deletions.
2 changes: 1 addition & 1 deletion app/(site)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export default async function Home() {
text-3xl
font-semibold
">
Welcome back
Vocalshare
</h1>
<div className="
grid
Expand Down
71 changes: 71 additions & 0 deletions app/account/components/AccountContent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
"use client";

import Button from "@/components/Button";
import useSubscribeModal from "@/hooks/useSubscribeModal";
import { useUser } from "@/hooks/useUser";
import { postData } from "@/libs/helpers";
import { useRouter } from "next/navigation";
import { useEffect, useState } from "react";
import toast from "react-hot-toast";

const AccountContent = () => {
const router = useRouter();
const subscribeModal = useSubscribeModal();
const { isLoading, subscription, user } = useUser();

const [loading, setLoading] = useState(false);

useEffect(() => {
if (!isLoading && !user) {
router.replace('/');
}
}, [isLoading, user, router]);

const redirectToCustomerPortal = async () => {
setLoading(true);
try{
const { url, error } = await postData({
url: '/api/create-portal-link'
});
window.location.assign(url);
} catch (error) {
if (error) {
toast.error((error as Error).message);
}
}
setLoading(false);
}

return (
<div className="mb-7 px-6">
{!subscription && (
<div className="flex flex-col gap-y-4">
<p>No active plan</p>
<Button
onClick={subscribeModal.onOpen}
className="w-[300px]"
>
Subscribe
</Button>
</div>
)}
{subscription && (
<div className="flex flex-col gap-y-4">
<p>
You are currently on the <b>{subscription?.prices?.products?.name}plan.</b>
</p>

<Button
disabled={loading || isLoading}
onClick={redirectToCustomerPortal}
className="w-[300px]"
>
Open customer portal
</Button>
</div>
)}
</div>
);
}

export default AccountContent;
24 changes: 24 additions & 0 deletions app/account/error.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//Adding loading and error handling to our app (getSongs)

"use client";

import Box from "@/components/Box";

const Error = () => {
return (
<Box className="
h-full
flex
items-center
justify-center
">
<div className="
text-neutral-400
">
Something went wrong!
</div>
</Box>
);
}

export default Error;
19 changes: 19 additions & 0 deletions app/account/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
"use client";

import Box from "@/components/Box";
import { BounceLoader } from "react-spinners";

const loading = () => {
return (
<Box className="
h-full
flex
items-center
justify-center
">
<BounceLoader color="#22c55e" size={40} />
</Box>
);
}

export default loading;
37 changes: 37 additions & 0 deletions app/account/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import Header from "@/components/Header";
import AccountContent from "./components/AccountContent";

const Account = () => {
return (
<div className="
bg-neutral-900
rounded-lg
h-full
w-full
overflow-hidden
overflow-y-auto
">
<Header className="
from-bg-neutral-900
">
<div className="
mb-2
flex
flex-col
gap-y-6
">
<h1 className="
text-white
text-3xl
font-semibold
">
Account Settings
</h1>
</div>
</Header>
<AccountContent />
</div>
);
}

export default Account;
4 changes: 2 additions & 2 deletions app/api/create-checkout-session/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,11 @@ export async function POST (
metadata
},
success_url: `${getUrl()}/account`,
cancel_url: `${getUrl()}`
cancel_url: `${getUrl()}/`
});

return NextResponse.json({ sessionId: session.id });
} catch (error) {
} catch (error: any) {
console.log(error);
return new NextResponse('Internal Error', { status: 500 });
};
Expand Down
2 changes: 1 addition & 1 deletion app/api/webhooks/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export async function POST(
if (checkoutSession.mode === 'subscription') {
const subscriptionId = checkoutSession.subscription;
await manageSubscriptionStatusChange(
subscriptionId as string,checkoutSession.customer as string,
subscriptionId as string, checkoutSession.customer as string,
true
);
}
Expand Down
9 changes: 5 additions & 4 deletions components/AuthModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,11 @@ const AuthModal = () => { // we'll use this component to render the login and re
}

return (
<Modal title="Welcome back"
description="Sign in to your account to continue"
isOpen={isOpen}
onChange={onChange}
<Modal
title="Welcome back"
description="Sign in to your account to continue"
isOpen={isOpen}
onChange={onChange}
>
<Auth
theme="dark"
Expand Down
1 change: 0 additions & 1 deletion components/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement> { //This is the type of the props that the button element accepts without having to declare them

}

const Button = forwardRef<HTMLButtonElement, ButtonProps> (({ //This is the type of the props that the Button component accepts without having to write them out
className,
children,
Expand Down
4 changes: 3 additions & 1 deletion components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { useUser } from "@/hooks/useUser";
import { useSupabaseClient } from "@supabase/auth-helpers-react";
import { FaUserAlt } from "react-icons/fa";
import toast from "react-hot-toast";
import usePlayer from "@/hooks/usePlayer";

interface HeaderProps {
children: React.ReactNode;
Expand All @@ -23,6 +24,7 @@ const Header: React.FC<HeaderProps> = ({
className
}) => {

const player = usePlayer(); //we're getting the player from the context
const authModal = useAuthModal(); // this will help us trigger our modal, we don't want to keep it just open
const router = useRouter();

Expand All @@ -32,7 +34,7 @@ const Header: React.FC<HeaderProps> = ({

const handleLogout = async () => {
const { error } = await supabaseClient.auth.signOut();
//reset any playing songs
player.reset(); //(reset any playing songs), Anytime we click logout it shuts down any playing songs
router.refresh();

if (error) {
Expand Down
10 changes: 8 additions & 2 deletions components/Library.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import useUploadModal from "@/hooks/useUploadModal";
import { Song } from "@/types";
import MediaItem from "./MediaItem";
import useOnPlay from "@/hooks/useOnPlay";
import useSubscribeModal from "@/hooks/useSubscribeModal";


interface LibraryProps {
Expand All @@ -18,11 +19,13 @@ const Library: React.FC<LibraryProps> = ({
songs //extracting the songs
}) => {

const subscribeModal = useSubscribeModal();

const authModal = useAuthModal();

const uploadModal = useUploadModal();

const { user } = useUser();
const { user, subscription } = useUser();

const onPlay = useOnPlay(songs); //Passing all songs in our playlist to the onPlay hook

Expand All @@ -31,7 +34,10 @@ const Library: React.FC<LibraryProps> = ({
return authModal.onOpen();

}
//Will add code to handle subscription after integrating stripe and if no subscription will trigger the subscription modal which we will build
// code to handle subscription after integrating stripe and if no subscription will trigger the subscription modal which we will build
if (!subscription) {
return subscribeModal.onOpen();
}

return uploadModal.onOpen();
};
Expand Down
1 change: 0 additions & 1 deletion components/Modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
import * as Dialog from "@radix-ui/react-dialog";
import { IoMdClose } from "react-icons/io";


interface ModalProps {
isOpen: boolean;
onChange: (open: boolean) => void;
Expand Down
66 changes: 63 additions & 3 deletions components/SubscribeModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@
import { Price, ProductWithPrice } from "@/types";
import Modal from "./Modal";
import Button from "./Button";
import { useState } from "react";
import { useUser } from "@/hooks/useUser";
import toast from "react-hot-toast";
import { postData } from "@/libs/helpers";
import { getStripe } from "@/libs/stripeClient";
import useSubscribeModal from "@/hooks/useSubscribeModal";

//creating props for products so that we can pass products to the SubscribeModal in ModalProvider
interface SubscribeModalProps {
Expand All @@ -23,6 +29,46 @@ const formatPrice = (price: Price) => {
const SubscribeModal: React.FC<SubscribeModalProps> = ({
products
}) => {

const subscribeModal = useSubscribeModal();
const { user, isLoading, subscription } = useUser(); //we're getting the user, isLoading and subscription from the useUser hook
const [priceIdLoading, setPriceIdLoading] = useState<string>(); //we're setting the priceIdLoading to a string

const onChange = (open: boolean) => {
if(!open) {
subscribeModal.onClose();
}
}

const handleCheckout = async (price: Price) => {
setPriceIdLoading(price.id); //we're setting the priceIdLoading to the price.id


if (!user){
setPriceIdLoading(undefined);
return toast.error("Must be Logged in to subscribe");
}

if (subscription){
setPriceIdLoading(undefined);
return toast.error("Already subscribed");
}

try{
const { sessionId } = await postData({
url: '/api/create-checkout-session',
data: { price }
});

const stripe = await getStripe();
stripe?.redirectToCheckout({ sessionId });
} catch (error){
return toast.error((error as Error)?.message);
} finally{
setPriceIdLoading(undefined);
}
};

let content = (
<div className="text-center">
No products available
Expand All @@ -42,7 +88,12 @@ const SubscribeModal: React.FC<SubscribeModalProps> = ({
);
}
return product.prices.map((price) => (
<Button key={price.id}>
<Button
key={price.id}
onClick={() => handleCheckout(price)}
disabled={isLoading || price.id === priceIdLoading}
className="mb-4"
>
{`Subscribe for ${formatPrice(price)} a ${price.interval}`}
</Button>
))
Expand All @@ -51,12 +102,21 @@ const SubscribeModal: React.FC<SubscribeModalProps> = ({
)
}

//Adding a modal for user is already subscribed
if (subscription){
content = (
<div className="text-center">
Already subscribed
</div>
)
}

return (
<Modal
title="Only for Premium Users"
description="Listen to music with Vocalshare Premium"
isOpen
onChange={() => {}}
isOpen={subscribeModal.isOpen}
onChange={onChange}
>
{content}
</Modal>
Expand Down
Loading

0 comments on commit 835d798

Please sign in to comment.