diff --git a/components/Dashboard.tsx b/components/Dashboard.tsx index 41ae031..322ca99 100644 --- a/components/Dashboard.tsx +++ b/components/Dashboard.tsx @@ -12,13 +12,14 @@ import Image from 'next/image'; import PaymentModal from './PaymentModal'; import Plan from 'public/plan.svg'; import { PriceIds } from 'utils/priceList'; +import { Session } from '@supabase/gotrue-js'; import { supabase } from '../utils/supabaseClient'; import { toast } from 'react-toastify'; import { useRouter } from 'next/router'; type DashboardProps = { profile: { username: string; website: string; avatar_url: string }; - session: { user: { email: string } }; + session: Session; plan: string; }; @@ -92,7 +93,7 @@ const Dashboard = ({ profile, session, plan }: DashboardProps): JSX.Element => { className="input input-primary input-bordered input-sm flex-1 text-base-100" id="email" type="text" - value={session.user.email} + value={session.user?.email} disabled /> diff --git a/package-lock.json b/package-lock.json index e6bed76..8fd94ae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,6 +34,7 @@ "@next/eslint-plugin-next": "^11.0.1", "@types/cors": "^2.8.12", "@types/express-rate-limit": "^5.1.3", + "@types/micro": "^7.3.6", "@typescript-eslint/eslint-plugin": "^4.29.1", "autoprefixer": "^10.3.1", "cypress": "^8.2.0", @@ -988,6 +989,12 @@ "@types/node": "*" } }, + "node_modules/@types/component-emitter": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.10.tgz", + "integrity": "sha512-bsjleuRKWmGqajMerkzox19aGbscQX5rmmvvXl3wlIp5gMG1HgkiwPxsN5p070fBDKTNSPgojVbuY1+HWMbFhg==", + "dev": true + }, "node_modules/@types/connect": { "version": "3.4.35", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", @@ -1008,6 +1015,15 @@ "resolved": "https://registry.npmjs.org/@types/debounce-promise/-/debounce-promise-3.1.4.tgz", "integrity": "sha512-9SEVY3nsz+uMN2DwDocftB5TAgZe7D0cOzxxRhpotWs6T4QFqRaTXpXbOSzbk31/7iYcfCkJJPwWGzTxyuGhCg==" }, + "node_modules/@types/engine.io": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/engine.io/-/engine.io-3.1.7.tgz", + "integrity": "sha512-qNjVXcrp+1sS8YpRUa714r0pgzOwESdW5UjHL7D/2ZFdBX0BXUXtg1LUrp+ylvqbvMcMWUy73YpRoxPN2VoKAQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/express": { "version": "4.17.13", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", @@ -1046,6 +1062,16 @@ "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", "dev": true }, + "node_modules/@types/micro": { + "version": "7.3.6", + "resolved": "https://registry.npmjs.org/@types/micro/-/micro-7.3.6.tgz", + "integrity": "sha512-rZHvZ3+Ev3cGJJSy/wtSiXZmafU8guI07PHXf4ku9sQLfDuFALHMCiV+LuH4VOaeMMMnRs8nqxU392gxfn661g==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/socket.io": "2.1.13" + } + }, "node_modules/@types/mime": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", @@ -1125,6 +1151,27 @@ "integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==", "dev": true }, + "node_modules/@types/socket.io": { + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/@types/socket.io/-/socket.io-2.1.13.tgz", + "integrity": "sha512-JRgH3nCgsWel4OPANkhH8TelpXvacAJ9VeryjuqCDiaVDMpLysd6sbt0dr6Z15pqH3p2YpOT3T1C5vQ+O/7uyg==", + "dev": true, + "dependencies": { + "@types/engine.io": "*", + "@types/node": "*", + "@types/socket.io-parser": "*" + } + }, + "node_modules/@types/socket.io-parser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/socket.io-parser/-/socket.io-parser-3.0.0.tgz", + "integrity": "sha512-Ry/rbTE6HQNL9eu3LpL1Ocup5VexXu1bSSGlSho/IR5LuRc8YvxwSNJ3JxqTltVJEATLbZkMQETSbxfKNgp4Ew==", + "deprecated": "This is a stub types definition. socket.io-parser provides its own type definitions, so you do not need this installed.", + "dev": true, + "dependencies": { + "socket.io-parser": "*" + } + }, "node_modules/@types/websocket": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@types/websocket/-/websocket-1.0.4.tgz", @@ -2281,6 +2328,12 @@ "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=" }, + "node_modules/component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "dev": true + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -7331,6 +7384,20 @@ "node": ">=8" } }, + "node_modules/socket.io-parser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.4.tgz", + "integrity": "sha512-t+b0SS+IxG7Rxzda2EVvyBZbvFPBCjJoyHuE0P//7OAsN23GItzDRdWa6ALxZI/8R5ygK7jAR6t028/z+7295g==", + "dev": true, + "dependencies": { + "@types/component-emitter": "^1.2.10", + "component-emitter": "~1.3.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/source-map": { "version": "0.8.0-beta.0", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", @@ -9198,6 +9265,12 @@ "@types/node": "*" } }, + "@types/component-emitter": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.10.tgz", + "integrity": "sha512-bsjleuRKWmGqajMerkzox19aGbscQX5rmmvvXl3wlIp5gMG1HgkiwPxsN5p070fBDKTNSPgojVbuY1+HWMbFhg==", + "dev": true + }, "@types/connect": { "version": "3.4.35", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", @@ -9218,6 +9291,15 @@ "resolved": "https://registry.npmjs.org/@types/debounce-promise/-/debounce-promise-3.1.4.tgz", "integrity": "sha512-9SEVY3nsz+uMN2DwDocftB5TAgZe7D0cOzxxRhpotWs6T4QFqRaTXpXbOSzbk31/7iYcfCkJJPwWGzTxyuGhCg==" }, + "@types/engine.io": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/engine.io/-/engine.io-3.1.7.tgz", + "integrity": "sha512-qNjVXcrp+1sS8YpRUa714r0pgzOwESdW5UjHL7D/2ZFdBX0BXUXtg1LUrp+ylvqbvMcMWUy73YpRoxPN2VoKAQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/express": { "version": "4.17.13", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", @@ -9256,6 +9338,16 @@ "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", "dev": true }, + "@types/micro": { + "version": "7.3.6", + "resolved": "https://registry.npmjs.org/@types/micro/-/micro-7.3.6.tgz", + "integrity": "sha512-rZHvZ3+Ev3cGJJSy/wtSiXZmafU8guI07PHXf4ku9sQLfDuFALHMCiV+LuH4VOaeMMMnRs8nqxU392gxfn661g==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/socket.io": "2.1.13" + } + }, "@types/mime": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", @@ -9335,6 +9427,26 @@ "integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==", "dev": true }, + "@types/socket.io": { + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/@types/socket.io/-/socket.io-2.1.13.tgz", + "integrity": "sha512-JRgH3nCgsWel4OPANkhH8TelpXvacAJ9VeryjuqCDiaVDMpLysd6sbt0dr6Z15pqH3p2YpOT3T1C5vQ+O/7uyg==", + "dev": true, + "requires": { + "@types/engine.io": "*", + "@types/node": "*", + "@types/socket.io-parser": "*" + } + }, + "@types/socket.io-parser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/socket.io-parser/-/socket.io-parser-3.0.0.tgz", + "integrity": "sha512-Ry/rbTE6HQNL9eu3LpL1Ocup5VexXu1bSSGlSho/IR5LuRc8YvxwSNJ3JxqTltVJEATLbZkMQETSbxfKNgp4Ew==", + "dev": true, + "requires": { + "socket.io-parser": "*" + } + }, "@types/websocket": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@types/websocket/-/websocket-1.0.4.tgz", @@ -10182,6 +10294,12 @@ "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=" }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "dev": true + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -14023,6 +14141,17 @@ "is-fullwidth-code-point": "^3.0.0" } }, + "socket.io-parser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.4.tgz", + "integrity": "sha512-t+b0SS+IxG7Rxzda2EVvyBZbvFPBCjJoyHuE0P//7OAsN23GItzDRdWa6ALxZI/8R5ygK7jAR6t028/z+7295g==", + "dev": true, + "requires": { + "@types/component-emitter": "^1.2.10", + "component-emitter": "~1.3.0", + "debug": "~4.3.1" + } + }, "source-map": { "version": "0.8.0-beta.0", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", diff --git a/package.json b/package.json index b20d651..880d200 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "@next/eslint-plugin-next": "^11.0.1", "@types/cors": "^2.8.12", "@types/express-rate-limit": "^5.1.3", + "@types/micro": "^7.3.6", "@typescript-eslint/eslint-plugin": "^4.29.1", "autoprefixer": "^10.3.1", "cypress": "^8.2.0", @@ -57,4 +58,4 @@ "prettier": "^2.3.2", "tailwindcss": "^2.2.7" } -} \ No newline at end of file +} diff --git a/pages/_app.js b/pages/_app.tsx similarity index 85% rename from pages/_app.js rename to pages/_app.tsx index 4ecdd35..3b896c0 100644 --- a/pages/_app.js +++ b/pages/_app.tsx @@ -1,5 +1,6 @@ import './global.css'; +import { AppProps } from 'next/app'; import { AuthProvider } from 'utils/AuthContext'; import { DefaultSeo } from 'next-seo'; @@ -8,7 +9,7 @@ Next-seo is integrated by default, if you want more information and how to setup more elements, visit their Github page https://github.com/garmeeh/next-seo */ -function MyApp({ Component, pageProps }) { +function MyApp({ Component, pageProps }: AppProps): JSX.Element { return ( <> diff --git a/pages/_document.js b/pages/_document.tsx similarity index 74% rename from pages/_document.js rename to pages/_document.tsx index 08730ad..19fb89a 100644 --- a/pages/_document.js +++ b/pages/_document.tsx @@ -1,12 +1,20 @@ -import Document, { Head, Html, Main, NextScript } from 'next/document'; +import Document, { + DocumentContext, + DocumentInitialProps, + Head, + Html, + Main, + NextScript, +} from 'next/document'; class MyDocument extends Document { - static async getInitialProps(ctx) { + static async getInitialProps(ctx: DocumentContext): Promise { const initialProps = await Document.getInitialProps(ctx); - return { ...initialProps }; + + return initialProps; } - render() { + render(): JSX.Element { // This will set the initial theme, saved in localstorage const setInitialTheme = ` function getUserPreference() { diff --git a/pages/api/auth.js b/pages/api/auth.ts similarity index 61% rename from pages/api/auth.js rename to pages/api/auth.ts index 32e120a..81dcd14 100644 --- a/pages/api/auth.js +++ b/pages/api/auth.ts @@ -3,8 +3,10 @@ * With SupaNexTail, we use SSR with the Dashboard page (pages/dashboard.js) */ +import type { NextApiRequest, NextApiResponse } from 'next'; + import { supabase } from 'utils/supabaseClient'; -export default function handler(req, res) { +export default async function handler(req: NextApiRequest, res: NextApiResponse): Promise { supabase.auth.api.setAuthCookie(req, res); } diff --git a/pages/api/getUser.js b/pages/api/getUser.js deleted file mode 100644 index b329858..0000000 --- a/pages/api/getUser.js +++ /dev/null @@ -1,13 +0,0 @@ -import { supabase } from 'utils/supabaseClient'; - -// Example of how to verify and get user data server-side. -const getUser = async (req, res) => { - const { token } = req.headers; - - const { data: user, error } = await supabase.auth.api.getUser(token); - - if (error) return res.status(401).json({ error: error.message }); - return res.status(200).json(user); -}; - -export default getUser; diff --git a/pages/api/getUser.ts b/pages/api/getUser.ts new file mode 100644 index 0000000..f9d3928 --- /dev/null +++ b/pages/api/getUser.ts @@ -0,0 +1,21 @@ +import type { NextApiRequest, NextApiResponse } from 'next'; + +import { supabase } from 'utils/supabaseClient'; + +// Example of how to verify and get user data server-side. +const getUser = async (req: NextApiRequest, res: NextApiResponse): Promise => { + const token = req.headers.token; + + if (typeof token !== 'string') { + return res.status(401).json({ error: 'Missing auth token.' }); + } + + if (token) { + const { data: user, error } = await supabase.auth.api.getUser(token); + + if (error) return res.status(401).json({ error: error.message }); + return res.status(200).json(user); + } +}; + +export default getUser; diff --git a/pages/api/sendgrid.js b/pages/api/sendgrid.ts similarity index 59% rename from pages/api/sendgrid.js rename to pages/api/sendgrid.ts index 127116f..1391117 100644 --- a/pages/api/sendgrid.js +++ b/pages/api/sendgrid.ts @@ -2,19 +2,22 @@ This is a simple contact form for SupaNexTail Using Sendgrid. */ +import type { NextApiRequest, NextApiResponse } from 'next'; import sgMail from '@sendgrid/mail'; -export default async function handler(req, res) { +const sendGrid = async (req: NextApiRequest, res: NextApiResponse): Promise => { if (req.method === 'POST') { - sgMail.setApiKey(process.env.SENDGRID_SECRET); + sgMail.setApiKey(process.env.SENDGRID_SECRET || ''); + const msg = { - to: process.env.SENDGRID_MAILTO, // Change to your recipient - from: process.env.SENDGRID_MAILFROM, // Change to your verified sender + to: process.env.SENDGRID_MAILTO || '', // Change to your recipient + from: process.env.SENDGRID_MAILFROM || '', // Change to your verified sender subject: `[${process.env.NEXT_PUBLIC_TITLE}] New message from ${req.body.name}`, text: req.body.message, reply_to: req.body.email, }; + sgMail .send(msg) .then(() => { @@ -24,8 +27,9 @@ export default async function handler(req, res) { console.error(error); res.status(500).send({ message: 'There was an issue with your email... please retry', - err, + error, }); }); } -} +}; +export default sendGrid; diff --git a/pages/api/stripe/customer-portal.js b/pages/api/stripe/customer-portal.ts similarity index 77% rename from pages/api/stripe/customer-portal.js rename to pages/api/stripe/customer-portal.ts index 5fd28c8..32d6618 100644 --- a/pages/api/stripe/customer-portal.js +++ b/pages/api/stripe/customer-portal.ts @@ -1,9 +1,9 @@ /* Dont forget to create your customer portal on Stripe https://dashboard.stripe.com/test/settings/billing/portal */ - -import * as Stripe from 'stripe'; +import type { NextApiRequest, NextApiResponse } from 'next'; import Cors from 'cors'; +import Stripe from 'stripe'; import initMiddleware from 'utils/init-middleware'; import rateLimit from 'express-rate-limit'; @@ -21,9 +21,11 @@ const limiter = initMiddleware( ); // Set your secret key. Remember to switch to your live secret key in production. // See your keys here: https://dashboard.stripe.com/apikeys -const stripe = new Stripe(process.env.STRIPE_SECRET); +const stripe = new Stripe(process.env.STRIPE_SECRET || '', { + apiVersion: '2020-08-27', +}); -export default async function handler(req, res) { +export default async function handler(req: NextApiRequest, res: NextApiResponse): Promise { await cors(req, res); await limiter(req, res); if (req.method === 'POST') { diff --git a/pages/api/stripe/stripe-webhook.js b/pages/api/stripe/stripe-webhook.ts similarity index 75% rename from pages/api/stripe/stripe-webhook.js rename to pages/api/stripe/stripe-webhook.ts index 5071880..63b1092 100644 --- a/pages/api/stripe/stripe-webhook.js +++ b/pages/api/stripe/stripe-webhook.ts @@ -6,9 +6,10 @@ If you want to test it locally, you'll need the stripe CLI and use this command stripe listen --forward-to localhost:3000/api/stripe/stripe-webhook */ -import * as Stripe from 'stripe'; +import type { NextApiRequest, NextApiResponse } from 'next'; import Cors from 'cors'; +import Stripe from 'stripe'; import { buffer } from 'micro'; import { createClient } from '@supabase/supabase-js'; import initMiddleware from 'utils/init-middleware'; @@ -29,7 +30,10 @@ const cors = initMiddleware( // Init Supabase Admin -const supabase = createClient(process.env.NEXT_PUBLIC_SUPABASE_URL, process.env.SUPABASE_ADMIN_KEY); +const supabase = createClient( + process.env.NEXT_PUBLIC_SUPABASE_URL || '', + process.env.SUPABASE_ADMIN_KEY || '' +); // Rate limiter : The user can only create one list every 20 seconds (avoid spam) @@ -42,24 +46,24 @@ const limiter = initMiddleware( // Set your secret key. Remember to switch to your live secret key in production. // See your keys here: https://dashboard.stripe.com/apikeys -const stripe = new Stripe(process.env.STRIPE_SECRET); +const stripe = new Stripe(process.env.STRIPE_SECRET || '', { + apiVersion: '2020-08-27', + maxNetworkRetries: 2, +}); -export default async function handler(req, res) { +export default async function handler(req: NextApiRequest, res: NextApiResponse): Promise { await cors(req, res); await limiter(req, res); - stripe.setMaxNetworkRetries(2); if (req.method === 'POST') { // Retrieve the event by verifying the signature using the raw body and secret. - let event; + let event: Stripe.Event; const buf = await buffer(req); + const sig = req.headers['stripe-signature'] as string; + try { - event = stripe.webhooks.constructEvent( - buf, - req.headers['stripe-signature'], - process.env.STRIPE_WEBHOOK - ); + event = stripe.webhooks.constructEvent(buf, sig, process.env.STRIPE_WEBHOOK || ''); } catch (err) { console.log(err); console.log(`⚠️ Webhook signature verification failed.`); @@ -67,7 +71,14 @@ export default async function handler(req, res) { return res.send(400); } // Extract the object from the event. - const dataObject = event.data.object; + const dataObject = event.data.object as { + client_reference_id: string; + customer: string; + metadata: { + priceId: string; + }; + subscription: string; + }; // Handle the event // Review important events for Billing webhooks @@ -75,18 +86,16 @@ export default async function handler(req, res) { // Remove comment to see the various objects sent for this sample switch (event.type) { case 'checkout.session.completed': - const { data: subscriptions, error } = await supabase + const { data: subscriptions } = await supabase .from('subscriptions') .select('*') .eq('id', dataObject.client_reference_id); - console.log(dataObject); - if (subscriptions.length == 0) { - const { data, error } = await supabase + if (subscriptions?.length == 0) { + await supabase .from('profiles') .update({ customerId: dataObject.customer }) .eq('id', dataObject.client_reference_id); - if (error) console.log(error); await supabase .from('subscriptions') @@ -100,8 +109,8 @@ export default async function handler(req, res) { }, ]) .then() - .catch((err) => console.log(err)); - } else if (subscriptions.length > 0) { + .then(null, (err) => console.log('err: ', err)); // catch + } else if (subscriptions?.length && subscriptions?.length > 0) { await supabase .from('subscriptions') .update({ @@ -112,7 +121,7 @@ export default async function handler(req, res) { }) .eq('id', dataObject.client_reference_id) .then() - .catch((err) => console.log(err)); + .then(null, (err) => console.log('err: ', err)); // catch } break; case 'customer.subscription.deleted': @@ -121,7 +130,7 @@ export default async function handler(req, res) { .update({ paid_user: false }) .eq('customer_id', dataObject.customer) .then() - .catch((err) => console.log(err)); + .then(null, (err) => console.log('err: ', err)); // catch break; case 'invoice.payment_failed': // If the payment fails or the customer does not have a valid payment method, diff --git a/pages/contact.js b/pages/contact.tsx similarity index 89% rename from pages/contact.js rename to pages/contact.tsx index abce3d6..c86e062 100644 --- a/pages/contact.js +++ b/pages/contact.tsx @@ -2,7 +2,7 @@ import Contact from 'components/Contact'; import Layout from 'components/Layout'; import { NextSeo } from 'next-seo'; -const ContactPage = () => ( +const ContactPage = (): JSX.Element => ( <> { - const [session, setSession] = useState(null); +const DashboardPage = ({ + user, + plan, + profile, +}: { + user: { + id: string; + }; + plan: string; + profile: { + username: string; + website: string; + avatar_url: string; + }; +}): JSX.Element => { + const [session, setSession] = useState(supabase.auth.session()); const router = useRouter(); - useEffect(() => { // If a user is not logged in, return to the homepage if (!user) { @@ -39,24 +52,35 @@ const DashboardPage = ({ user, plan, profile }) => {
You are not logged in
) : ( <> - + {session && ( + + )} )} ); }; -export async function getServerSideProps({ req }) { +export async function getServerSideProps(context: NextPageContext) { const supabaseAdmin = createClient( - process.env.NEXT_PUBLIC_SUPABASE_URL, - process.env.SUPABASE_ADMIN_KEY + process.env.NEXT_PUBLIC_SUPABASE_URL || '', + process.env.SUPABASE_ADMIN_KEY || '' ); - const { user } = await supabaseAdmin.auth.api.getUserByCookie(req); - const stripe = new Stripe(process.env.STRIPE_SECRET); + const { user } = await supabaseAdmin.auth.api.getUserByCookie(context.req); + + const stripe = new Stripe(process.env.STRIPE_SECRET || '', { + apiVersion: '2020-08-27', + maxNetworkRetries: 2, + }); // If the user exist, you will retrieve the user profile and if he/she's a paid user if (user) { - const { data: plan, error } = await supabaseAdmin + const { data: plan } = await supabaseAdmin .from('subscriptions') .select('plan') .eq('id', user.id) @@ -65,7 +89,7 @@ export async function getServerSideProps({ req }) { // Check the subscription plan. If it doesnt exist, return null const subscription = plan?.plan ? await stripe.subscriptions.retrieve(plan.plan) : null; - const { data: profile, errorProfile } = await supabaseAdmin + const { data: profile } = await supabaseAdmin .from('profiles') .select(`username, website, avatar_url`) .eq('id', user.id) @@ -74,7 +98,7 @@ export async function getServerSideProps({ req }) { return { props: { user, - plan: subscription?.plan?.id ? subscription.plan.id : null, + plan: subscription?.items.data[0].price.id ? subscription?.items.data[0].price.id : null, profile, }, }; @@ -86,5 +110,6 @@ export async function getServerSideProps({ req }) { } // If there is a user, return it. + return null; } export default DashboardPage; diff --git a/pages/index.js b/pages/index.tsx similarity index 97% rename from pages/index.js rename to pages/index.tsx index b82dc25..b3f8c09 100644 --- a/pages/index.js +++ b/pages/index.tsx @@ -7,7 +7,7 @@ import Head from 'next/head'; import Landing from 'components/Landing'; import Layout from 'components/Layout'; -const Home = () => ( +const Home = (): JSX.Element => ( <> {`Welcome to ${process.env.NEXT_PUBLIC_TITLE} 👋`} diff --git a/pages/login.js b/pages/login.tsx similarity index 78% rename from pages/login.js rename to pages/login.tsx index cf37d29..9eacdb1 100644 --- a/pages/login.js +++ b/pages/login.tsx @@ -9,8 +9,8 @@ import Login from 'components/UI/Login'; import { NextSeo } from 'next-seo'; import { useAuth } from 'utils/AuthContext'; -const LoginPage = () => { - const { signUp, signIn, signOut, resetPassword } = useAuth(); +const LoginPage = (): JSX.Element => { + const { signIn, resetPassword } = useAuth(); return ( <> {
- +
diff --git a/pages/pricing.js b/pages/pricing.tsx similarity index 91% rename from pages/pricing.js rename to pages/pricing.tsx index 19a0703..385caba 100644 --- a/pages/pricing.js +++ b/pages/pricing.tsx @@ -4,7 +4,7 @@ import Layout from 'components/Layout'; import { NextSeo } from 'next-seo'; import Pricing from 'components/Pricing'; -const PricingPage = () => ( +const PricingPage = (): JSX.Element => ( <> ( +const PrivacyPage = (): JSX.Element => ( <> ( +const SignUpPage = (): JSX.Element => ( <> ( +const TermsPage = (): JSX.Element => ( <> + +import type { NextApiRequest, NextApiResponse } from 'next'; + +export default function initMiddleware(middleware: any) { + return (req: NextApiRequest, res: NextApiResponse) => new Promise((resolve, reject) => { - middleware(req, res, (result) => { + middleware(req, res, (result: any) => { if (result instanceof Error) { return reject(result); } diff --git a/utils/stripe.js b/utils/stripe.tsx similarity index 57% rename from utils/stripe.js rename to utils/stripe.tsx index 92a07df..15960ed 100644 --- a/utils/stripe.js +++ b/utils/stripe.tsx @@ -1,12 +1,12 @@ /** * This is a singleton to ensure we only instantiate Stripe once. */ -import { loadStripe } from '@stripe/stripe-js'; +import { Stripe, loadStripe } from '@stripe/stripe-js'; -let stripePromise = null; -const getStripe = () => { +let stripePromise: Promise; +const getStripe = (): Promise => { if (!stripePromise) { - stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLIC_KEY); + stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLIC_KEY || ''); } return stripePromise; }; diff --git a/utils/supabaseClient.js b/utils/supabaseClient.tsx similarity index 74% rename from utils/supabaseClient.js rename to utils/supabaseClient.tsx index 7008c47..6a7b892 100644 --- a/utils/supabaseClient.js +++ b/utils/supabaseClient.tsx @@ -3,11 +3,11 @@ import { createClient } from '@supabase/supabase-js'; const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL; const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY; -export const supabase = createClient(supabaseUrl, supabaseAnonKey); +export const supabase = createClient(supabaseUrl || '', supabaseAnonKey || ''); // Check if a user has a paid plan export const getSub = async () => { - const { data: subscriptions, error } = await supabase + const { data: subscriptions } = await supabase .from('subscriptions') .select('paid_user, plan') .single();