mirror of
https://github.com/fergalmoran/supanextail.git
synced 2025-12-22 09:17:54 +00:00
Continue typescript integration
This commit is contained in:
@@ -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
|
||||
/>
|
||||
</div>
|
||||
|
||||
129
package-lock.json
generated
129
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
<AuthProvider>
|
||||
@@ -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<DocumentInitialProps> {
|
||||
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() {
|
||||
@@ -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<void> {
|
||||
supabase.auth.api.setAuthCookie(req, res);
|
||||
}
|
||||
@@ -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;
|
||||
21
pages/api/getUser.ts
Normal file
21
pages/api/getUser.ts
Normal file
@@ -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<void> => {
|
||||
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;
|
||||
@@ -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<void> => {
|
||||
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;
|
||||
@@ -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<void> {
|
||||
await cors(req, res);
|
||||
await limiter(req, res);
|
||||
if (req.method === 'POST') {
|
||||
@@ -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<void> {
|
||||
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,
|
||||
@@ -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 => (
|
||||
<>
|
||||
<NextSeo
|
||||
title={`${process.env.NEXT_PUBLIC_TITLE} | Contact`}
|
||||
@@ -1,18 +1,31 @@
|
||||
import * as Stripe from 'stripe';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import Dashboard from '../components/Dashboard';
|
||||
import Head from 'next/head';
|
||||
import Layout from 'components/Layout';
|
||||
import type { NextPageContext } from 'next';
|
||||
import Stripe from 'stripe';
|
||||
import { createClient } from '@supabase/supabase-js';
|
||||
import { supabase } from '../utils/supabaseClient';
|
||||
import { useRouter } from 'next/router';
|
||||
|
||||
const DashboardPage = ({ user, plan, profile }) => {
|
||||
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 }) => {
|
||||
<div className="text-center">You are not logged in</div>
|
||||
) : (
|
||||
<>
|
||||
<Dashboard key={user.id} session={session} plan={plan} profile={profile} />
|
||||
{session && (
|
||||
<Dashboard
|
||||
key={user.id || undefined}
|
||||
session={session}
|
||||
plan={plan}
|
||||
profile={profile}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Layout>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
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;
|
||||
@@ -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 => (
|
||||
<>
|
||||
<Head>
|
||||
<title>{`Welcome to ${process.env.NEXT_PUBLIC_TITLE} 👋`}</title>
|
||||
@@ -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 (
|
||||
<>
|
||||
<NextSeo
|
||||
@@ -20,7 +20,7 @@ const LoginPage = () => {
|
||||
|
||||
<Layout>
|
||||
<div className="flex flex-wrap justify-evenly w-full mt-20">
|
||||
<Login signUp={signUp} signIn={signIn} signOut={signOut} resetPassword={resetPassword} />
|
||||
<Login signIn={signIn} resetPassword={resetPassword} />
|
||||
</div>
|
||||
</Layout>
|
||||
</>
|
||||
@@ -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 => (
|
||||
<>
|
||||
<NextSeo
|
||||
title={`${process.env.NEXT_PUBLIC_TITLE} | Pricing`}
|
||||
@@ -4,7 +4,7 @@ import Layout from 'components/Layout';
|
||||
import { NextSeo } from 'next-seo';
|
||||
import PrivacyPolicy from 'components/PrivacyPolicy';
|
||||
|
||||
const PrivacyPage = () => (
|
||||
const PrivacyPage = (): JSX.Element => (
|
||||
<>
|
||||
<NextSeo
|
||||
title={`${process.env.NEXT_PUBLIC_TITLE} | Privacy Policy`}
|
||||
@@ -9,7 +9,7 @@ import AuthText from 'components/AuthText';
|
||||
import Layout from 'components/Layout';
|
||||
import { NextSeo } from 'next-seo';
|
||||
|
||||
const SignUpPage = () => (
|
||||
const SignUpPage = (): JSX.Element => (
|
||||
<>
|
||||
<NextSeo
|
||||
title={`${process.env.NEXT_PUBLIC_TITLE} | Auth`}
|
||||
@@ -4,7 +4,7 @@ import Layout from 'components/Layout';
|
||||
import { NextSeo } from 'next-seo';
|
||||
import Terms from 'components/Terms';
|
||||
|
||||
const TermsPage = () => (
|
||||
const TermsPage = (): JSX.Element => (
|
||||
<>
|
||||
<NextSeo
|
||||
title={`${process.env.NEXT_PUBLIC_TITLE} | Terms and conditions`}
|
||||
@@ -1,9 +1,12 @@
|
||||
// Helper method to wait for a middleware to execute before continuing
|
||||
// And to throw an error when an error happens in a middleware
|
||||
export default function initMiddleware(middleware) {
|
||||
return (req, res) =>
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -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<Stripe | null>;
|
||||
const getStripe = (): Promise<Stripe | null> => {
|
||||
if (!stripePromise) {
|
||||
stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLIC_KEY);
|
||||
stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLIC_KEY || '');
|
||||
}
|
||||
return stripePromise;
|
||||
};
|
||||
@@ -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();
|
||||
Reference in New Issue
Block a user