From 0a6d09111d27e272da598b159e8a612d27aaf2b4 Mon Sep 17 00:00:00 2001 From: shye <113232835+justshye@users.noreply.github.com> Date: Thu, 23 May 2024 14:00:03 -0700 Subject: [PATCH 01/21] Add shopping cart screen --- src/app/payment/page.js | 139 ++++++++++++++++++++++++++++++---------- 1 file changed, 105 insertions(+), 34 deletions(-) diff --git a/src/app/payment/page.js b/src/app/payment/page.js index c6383a7..d808b1a 100644 --- a/src/app/payment/page.js +++ b/src/app/payment/page.js @@ -2,6 +2,9 @@ import { loadStripe } from '@stripe/stripe-js'; import { useSearchParams } from 'next/navigation'; +import { useState } from 'react'; +import Navbar from '@/components/ui/navbar'; +import Footer from '@/components/ui/footer'; // Make sure to call `loadStripe` outside of a component’s render to avoid // recreating the `Stripe` object on every render. @@ -9,6 +12,14 @@ loadStripe(process.env.STRIPE_PUBLISHABLE_KEY); // TODO: Needs styling export default function PreviewPage() { + const [quantity, setQuantity] = useState(1); + + const links = [ + { text: 'Home', url: '/' }, + { text: 'About', url: '/wip' }, + { text: 'Login / Signup', url: '/api/auth/login' }, + ]; + const searchParams = useSearchParams(); const paymentStatus = searchParams.get('paymentStatus'); @@ -22,40 +33,100 @@ export default function PreviewPage() { ); } + const increaseQuantity = () => { + setQuantity(quantity + 1); + }; + + const decreaseQuantity = () => { + if (quantity > 1) { + setQuantity(quantity - 1); + } + }; + return ( -
-
- -
- -
+
+ +
+
+
+

Shopping Cart

+
1 item
+
+
+
+
+
+
+

Job Posting

+

+ 6 Months, Cross-posting enabled +

+
+
+
+
+ +
{quantity}
+ +
+
+
$10
+
+
+
+
Subtotal
+
${quantity * 10}
+
+
+ {/* */} +
+
+ +
+ +
+
+
+
+
); } From b9091de879952b65fe2955dcdfd7d45a4c600df4 Mon Sep 17 00:00:00 2001 From: shye <113232835+justshye@users.noreply.github.com> Date: Thu, 23 May 2024 14:23:31 -0700 Subject: [PATCH 02/21] Change navbar to logout if user is logged in --- src/app/pricing/page.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/app/pricing/page.js b/src/app/pricing/page.js index 1fcfb34..a745798 100644 --- a/src/app/pricing/page.js +++ b/src/app/pricing/page.js @@ -8,14 +8,24 @@ import Link from 'next/link'; import Navbar from '@/components/ui/navbar'; import Footer from '@/components/ui/footer'; +import { useUser } from '@auth0/nextjs-auth0/client'; export default function Component() { + const { user, error } = useUser(); + + if (error) return
{error.message}
; + const links = [ { text: 'Home', url: '/' }, { text: 'About', url: '/wip' }, - { text: 'Login / Signup', url: '/api/auth/login' }, + { + text: user ? 'Logout' : 'Login / Signup', + url: user ? '/api/auth/logout' : '/api/auth/login', + }, ]; + const linkHref = user ? '/payment' : '/api/auth/login?returnTo=/payment'; + return (
@@ -43,7 +53,7 @@ export default function Component() { /position Get Started From 4d62489323df43fdb71a14429b37b6c0838285dc Mon Sep 17 00:00:00 2001 From: shye <113232835+justshye@users.noreply.github.com> Date: Thu, 23 May 2024 14:25:09 -0700 Subject: [PATCH 03/21] Change to logout button --- src/app/payment/page.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/payment/page.js b/src/app/payment/page.js index d808b1a..7aca125 100644 --- a/src/app/payment/page.js +++ b/src/app/payment/page.js @@ -17,7 +17,7 @@ export default function PreviewPage() { const links = [ { text: 'Home', url: '/' }, { text: 'About', url: '/wip' }, - { text: 'Login / Signup', url: '/api/auth/login' }, + { text: 'Logout', url: '/api/auth/logout' }, ]; const searchParams = useSearchParams(); From 25f0c90e3e89f4ac2efb97e939c4a21b8daf0c32 Mon Sep 17 00:00:00 2001 From: shye <113232835+justshye@users.noreply.github.com> Date: Thu, 23 May 2024 14:25:26 -0700 Subject: [PATCH 04/21] Change to logout button if user is logged in --- src/app/page.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/app/page.js b/src/app/page.js index c4b8cc6..ea830c6 100644 --- a/src/app/page.js +++ b/src/app/page.js @@ -32,7 +32,10 @@ export default function Component() { const links = [ { text: 'Pricing', url: '/pricing' }, { text: 'About', url: '/wip' }, - { text: 'Login / Signup', url: '/api/auth/login' }, + { + text: user ? 'Logout' : 'Login / Signup', + url: user ? '/api/auth/logout' : '/api/auth/login', + }, ]; return ( From c529d2b4bdf17e3a6fb89f1bfd00dc0722da9df9 Mon Sep 17 00:00:00 2001 From: shye <113232835+justshye@users.noreply.github.com> Date: Thu, 23 May 2024 14:29:56 -0700 Subject: [PATCH 05/21] Return user to homepage if not logged in --- src/app/payment/page.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/app/payment/page.js b/src/app/payment/page.js index 7aca125..4d34b34 100644 --- a/src/app/payment/page.js +++ b/src/app/payment/page.js @@ -5,14 +5,23 @@ import { useSearchParams } from 'next/navigation'; import { useState } from 'react'; import Navbar from '@/components/ui/navbar'; import Footer from '@/components/ui/footer'; +import { redirect } from 'next/navigation'; +import { useUser } from '@auth0/nextjs-auth0/client'; // Make sure to call `loadStripe` outside of a component’s render to avoid // recreating the `Stripe` object on every render. loadStripe(process.env.STRIPE_PUBLISHABLE_KEY); -// TODO: Needs styling export default function PreviewPage() { + const { user, error } = useUser(); const [quantity, setQuantity] = useState(1); + const searchParams = useSearchParams(); + + if (!user) { + redirect('/admin-panel/home'); + } + + if (error) return
{error.message}
; const links = [ { text: 'Home', url: '/' }, @@ -20,8 +29,6 @@ export default function PreviewPage() { { text: 'Logout', url: '/api/auth/logout' }, ]; - const searchParams = useSearchParams(); - const paymentStatus = searchParams.get('paymentStatus'); if (paymentStatus) { From 51aafc5003bf52e3acccca895d9b695549f21310 Mon Sep 17 00:00:00 2001 From: shye <113232835+justshye@users.noreply.github.com> Date: Fri, 24 May 2024 14:31:25 -0700 Subject: [PATCH 06/21] Reflect Shopping cart to show how many posts the user has --- src/app/payment/page.js | 95 ++++++++++++++++++++++++++++++++--------- 1 file changed, 74 insertions(+), 21 deletions(-) diff --git a/src/app/payment/page.js b/src/app/payment/page.js index 4d34b34..cf7fa2d 100644 --- a/src/app/payment/page.js +++ b/src/app/payment/page.js @@ -5,23 +5,19 @@ import { useSearchParams } from 'next/navigation'; import { useState } from 'react'; import Navbar from '@/components/ui/navbar'; import Footer from '@/components/ui/footer'; -import { redirect } from 'next/navigation'; -import { useUser } from '@auth0/nextjs-auth0/client'; +import { useCallback } from 'react'; +import { useEffect } from 'react'; // Make sure to call `loadStripe` outside of a component’s render to avoid // recreating the `Stripe` object on every render. loadStripe(process.env.STRIPE_PUBLISHABLE_KEY); export default function PreviewPage() { - const { user, error } = useUser(); + const [user, setUser] = useState(null); + const [setJobPostings] = useState([]); const [quantity, setQuantity] = useState(1); const searchParams = useSearchParams(); - - if (!user) { - redirect('/admin-panel/home'); - } - - if (error) return
{error.message}
; + const [setLoading] = useState(true); const links = [ { text: 'Home', url: '/' }, @@ -29,6 +25,63 @@ export default function PreviewPage() { { text: 'Logout', url: '/api/auth/logout' }, ]; + const JOB_POSTING_API_URL = process.env.NEXT_PUBLIC_JOB_POSTING_API_URL; + + const fetchUser = useCallback(async () => { + try { + const response = await fetch('/api/auth/me'); + if (response.ok) { + const userData = await response.json(); + setUser(userData); + } else { + console.error('Failed to fetch user:', response.statusText); + window.location.href = '/'; + } + } catch (error) { + console.error('Error fetching user:', error); + //redirect to login page + window.location.href = '/'; + } + }, []); + + // Function to fetch job postings from the API + const fetchJobPostings = useCallback(async () => { + try { + setLoading(true); + const sortCriteria = JSON.stringify({ _id: -1 }); + const apiURL = `${JOB_POSTING_API_URL}?email=${user.email}&sort=${sortCriteria}`; + const response = await fetch(apiURL, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }); + + if (response.ok) { + const res = await response.json(); + setJobPostings(res.jobPostings); + // Set the quantity to the number of job postings + setQuantity(res.jobPostings.length); + } else { + console.error('Failed to fetch job postings:', response.statusText); + } + } catch (error) { + console.error('Error fetching job postings:', error); + } finally { + setLoading(false); + } + }, [user]); + + useEffect(() => { + fetchUser(); + }, []); // Fetch job postings when the component mounts + + useEffect(() => { + if (user) { + fetchJobPostings(); + } + }, [user]); + const paymentStatus = searchParams.get('paymentStatus'); if (paymentStatus) { @@ -40,15 +93,15 @@ export default function PreviewPage() { ); } - const increaseQuantity = () => { - setQuantity(quantity + 1); - }; + // const increaseQuantity = () => { + // setQuantity(quantity + 1); + // }; - const decreaseQuantity = () => { - if (quantity > 1) { - setQuantity(quantity - 1); - } - }; + // const decreaseQuantity = () => { + // if (quantity > 1) { + // setQuantity(quantity - 1); + // } + // }; return (
@@ -72,17 +125,17 @@ export default function PreviewPage() {
- + */}
{quantity}
- + */}
$10
From a15cbcb17aa3cff49b56d39157644b6d09d525d7 Mon Sep 17 00:00:00 2001 From: shye <113232835+justshye@users.noreply.github.com> Date: Fri, 24 May 2024 15:17:07 -0700 Subject: [PATCH 07/21] Fix cart page displaying the wrong value --- src/app/payment/page.js | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/app/payment/page.js b/src/app/payment/page.js index cf7fa2d..571a3b0 100644 --- a/src/app/payment/page.js +++ b/src/app/payment/page.js @@ -14,10 +14,8 @@ loadStripe(process.env.STRIPE_PUBLISHABLE_KEY); export default function PreviewPage() { const [user, setUser] = useState(null); - const [setJobPostings] = useState([]); - const [quantity, setQuantity] = useState(1); + const [jobPostings, setJobPostings] = useState([]); const searchParams = useSearchParams(); - const [setLoading] = useState(true); const links = [ { text: 'Home', url: '/' }, @@ -47,7 +45,6 @@ export default function PreviewPage() { // Function to fetch job postings from the API const fetchJobPostings = useCallback(async () => { try { - setLoading(true); const sortCriteria = JSON.stringify({ _id: -1 }); const apiURL = `${JOB_POSTING_API_URL}?email=${user.email}&sort=${sortCriteria}`; const response = await fetch(apiURL, { @@ -60,15 +57,11 @@ export default function PreviewPage() { if (response.ok) { const res = await response.json(); setJobPostings(res.jobPostings); - // Set the quantity to the number of job postings - setQuantity(res.jobPostings.length); } else { console.error('Failed to fetch job postings:', response.statusText); } } catch (error) { console.error('Error fetching job postings:', error); - } finally { - setLoading(false); } }, [user]); @@ -130,7 +123,7 @@ export default function PreviewPage() { onClick={decreaseQuantity}> - */} -
{quantity}
+
{jobPostings.length}
{/* */} -
{jobPostings.length}
+
{loading ? 'Loading...' : jobPostings.length}
{/* */}
+
*/} - +
*/} -
{loading ? 'Loading...' : jobPostings.length}
- {/* */} + +
$10
-
$10
- -
-
Subtotal
-
- {loading ? 'Loading...' : `$${jobPostings.length * 10}`} +
+
Subtotal
+
+ {loading ? 'Loading...' : `$${jobPostings.length * 10}`} +
-
-
- {/* */} - - -
- -
- - +
+ +
+ +
+ +
+
- -
-
+
+
+ ); } From 0ebb04c6c437d7e76d9cbcf9b79c16681de8d9cf Mon Sep 17 00:00:00 2001 From: shye <113232835+justshye@users.noreply.github.com> Date: Tue, 28 May 2024 12:26:43 -0700 Subject: [PATCH 17/21] Move location of searchParams declaration --- src/app/payment/page.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/app/payment/page.js b/src/app/payment/page.js index 46fcb8c..160851b 100644 --- a/src/app/payment/page.js +++ b/src/app/payment/page.js @@ -16,7 +16,6 @@ loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY); export default function PreviewPage() { const [user, setUser] = useState(null); const [jobPostings, setJobPostings] = useState([]); - const searchParams = useSearchParams(); const [loading, setLoading] = useState(true); const links = [ @@ -80,15 +79,18 @@ export default function PreviewPage() { fetchJobPostings(); } }, [user]); + const searchParams = useSearchParams(); const paymentStatus = searchParams.get('paymentStatus'); if (paymentStatus) { return ( - <> -

Payment Status

-

Payment was successful: {paymentStatus}

- + Loading...}> + <> +

Payment Status

+

Payment was successful: {paymentStatus}

+ +
); } From 8d5aa71f0def858d2a437dfb84560fee52913ff5 Mon Sep 17 00:00:00 2001 From: shye <113232835+justshye@users.noreply.github.com> Date: Tue, 28 May 2024 12:36:53 -0700 Subject: [PATCH 18/21] Wrap entire function in Suspense --- src/app/payment/page.js | 162 ++++++++++++++++++++-------------------- 1 file changed, 83 insertions(+), 79 deletions(-) diff --git a/src/app/payment/page.js b/src/app/payment/page.js index 160851b..e054d0d 100644 --- a/src/app/payment/page.js +++ b/src/app/payment/page.js @@ -13,7 +13,7 @@ import { Suspense } from 'react'; // recreating the `Stripe` object on every render. loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY); -export default function PreviewPage() { +function Cart() { const [user, setUser] = useState(null); const [jobPostings, setJobPostings] = useState([]); const [loading, setLoading] = useState(true); @@ -79,18 +79,16 @@ export default function PreviewPage() { fetchJobPostings(); } }, [user]); - const searchParams = useSearchParams(); + const searchParams = useSearchParams(); const paymentStatus = searchParams.get('paymentStatus'); if (paymentStatus) { return ( - Loading...}> - <> -

Payment Status

-

Payment was successful: {paymentStatus}

- -
+ <> +

Payment Status

+

Payment was successful: {paymentStatus}

+ ); } @@ -105,94 +103,100 @@ export default function PreviewPage() { // }; return ( - Loading...}> -
- -
-
-
-

Shopping Cart

-
1 item
-
-
-
-
-
-
-

Job Posting

-

- 6 Months, Cross-posting enabled -

-
+
+ +
+
+
+

Shopping Cart

+
1 item
+
+
+
+
+
+
+

Job Posting

+

+ 6 Months, Cross-posting enabled +

-
-
- {/* */} -
{loading ? 'Loading...' : jobPostings.length}
- {/*
+ {/* */} -
-
$10
+
$10
-
-
Subtotal
-
- {loading ? 'Loading...' : `$${jobPostings.length * 10}`} -
+
+
+
Subtotal
+
+ {loading ? 'Loading...' : `$${jobPostings.length * 10}`}
-
- {/*
+
+ {/* */} -
- -
- -
- -
-
+
+ +
+ +
+ +
-
-
+
+
+
+ ); +} + +export default function PreviewPage() { + return ( + + ); } From 125d0c45c88712a4c6f5150d5bb7aea5ce48325a Mon Sep 17 00:00:00 2001 From: shye <113232835+justshye@users.noreply.github.com> Date: Tue, 28 May 2024 13:03:40 -0700 Subject: [PATCH 19/21] Update paid status of listing after payment --- src/app/payment/page.js | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/app/payment/page.js b/src/app/payment/page.js index e054d0d..e281f36 100644 --- a/src/app/payment/page.js +++ b/src/app/payment/page.js @@ -60,6 +60,39 @@ function Cart() { const res = await response.json(); const unpaidPostings = res.jobPostings.filter(posting => !posting.paid); setJobPostings(unpaidPostings); + + if (paymentStatus === 'true') { + const promises = unpaidPostings.map(async posting => { + const validThrough = new Date(); + validThrough.setMonth(validThrough.getMonth() + 6); + + const patchData = { + paid: true, + validThrough: validThrough.toISOString(), + }; + + const patchResponse = await fetch( + `/api/job-posting?job-posting-id=${posting._id}`, + { + method: 'PATCH', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(patchData), + } + ); + + if (patchResponse.ok) { + console.log(`Job posting ${posting._id} updated successfully`); + } else { + console.error( + `Failed to update job posting ${posting._id}:`, + patchResponse.statusText + ); + } + }); + await Promise.all(promises); + } } else { console.error('Failed to fetch job postings:', response.statusText); } From 4dd4c0abb204cf0bfbbb503f9af2d400dc4dd79d Mon Sep 17 00:00:00 2001 From: shye <113232835+justshye@users.noreply.github.com> Date: Tue, 28 May 2024 13:47:49 -0700 Subject: [PATCH 20/21] Redirect to payment successful after paying --- src/app/payment/page.js | 51 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 46 insertions(+), 5 deletions(-) diff --git a/src/app/payment/page.js b/src/app/payment/page.js index e281f36..83a2941 100644 --- a/src/app/payment/page.js +++ b/src/app/payment/page.js @@ -8,6 +8,7 @@ import Footer from '@/components/ui/footer'; import { useCallback } from 'react'; import { useEffect } from 'react'; import { Suspense } from 'react'; +import Link from 'next/link'; // Make sure to call `loadStripe` outside of a component’s render to avoid // recreating the `Stripe` object on every render. @@ -116,12 +117,52 @@ function Cart() { const searchParams = useSearchParams(); const paymentStatus = searchParams.get('paymentStatus'); - if (paymentStatus) { + function CircleCheckIcon(props) { return ( - <> -

Payment Status

-

Payment was successful: {paymentStatus}

- + + + + + ); + } + + if (paymentStatus && paymentStatus === 'true') { + const links = [ + { text: 'Home', url: '/admin-panel/home' }, + { text: 'About', url: '/wip' }, + { text: 'Logout', url: '/api/auth/logout' }, + ]; + return ( +
+ +
+
+
+ +

Payment Successful

+

+ Your payment was processed successfully. +

+
+ + Return to Home + +
+
+
); } From d5407ecd8567eb33252d1e96ab6d9318fc9446dc Mon Sep 17 00:00:00 2001 From: shye <113232835+justshye@users.noreply.github.com> Date: Tue, 28 May 2024 13:49:26 -0700 Subject: [PATCH 21/21] Add comment to warn about vulnerability --- src/app/api/stripe/route.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/api/stripe/route.js b/src/app/api/stripe/route.js index dd22811..1153280 100644 --- a/src/app/api/stripe/route.js +++ b/src/app/api/stripe/route.js @@ -20,7 +20,7 @@ export async function POST(req) { }, ], mode: 'payment', - success_url: `${baseURL}/?paymentStatus=true`, + success_url: `${baseURL}/?paymentStatus=true`, // This needs to be changed because right now you can just change the URL to get your postings marked as active cancel_url: `${baseURL}/?paymentStatus=false`, });