Clean V2 - Add sonarjs and unicorn js

This commit is contained in:
Michael
2022-01-14 18:05:18 +01:00
parent 8579861f3b
commit e973cc5cd8
30 changed files with 590 additions and 310 deletions

View File

@@ -8,20 +8,22 @@
"extends": [
"next",
"next/core-web-vitals",
"plugin:@typescript-eslint/recommended-requiring-type-checking"
"plugin:@typescript-eslint/recommended-requiring-type-checking",
"plugin:sonarjs/recommended",
"plugin:unicorn/recommended"
],
"parserOptions": {
"ecmaFeatures": {
"jsx": true
},
"ecmaVersion": 12,
"sourceType": "module"
"project": [
"./tsconfig.json"
]
},
"parser": "@typescript-eslint/parser",
"plugins": [
"@typescript-eslint",
"cypress",
"simple-import-sort",
"prettier"
"prettier",
"sonarjs"
],
"rules": {
"no-console": "off",
@@ -61,6 +63,15 @@
"allowHigherOrderFunctions": true,
"allowConciseArrowFunctionExpressionsStartingWithVoid": true
}
],
"unicorn/filename-case": [
"error",
{
"cases": {
"camelCase": true,
"pascalCase": true
}
}
]
},
"settings": {

View File

@@ -9,25 +9,25 @@ import React, { useEffect, useState } from 'react';
import Image from 'next/image';
import { supabase } from 'utils/supabaseClient';
type AvatarProps = {
type AvatarProperties = {
url: string;
size: number;
onUpload: (filePath: string) => void;
};
const Avatar = ({ url, size, onUpload }: AvatarProps): JSX.Element => {
const customImgLoader = ({ src }: { src: string }): string => {
return `${src}`;
};
const Avatar = ({ url, size, onUpload }: AvatarProperties): JSX.Element => {
const [avatarUrl, setAvatarUrl] = useState('');
const [uploading, setUploading] = useState(false);
const customImgLoader = ({ src }: { src: string }) => {
return `${src}`;
};
useEffect(() => {
if (url) downloadImage(url);
if (url) void downloadImage(url);
}, [url]);
async function downloadImage(path: string) {
async function downloadImage(path: string): Promise<void> {
try {
const { data, error } = await supabase.storage
.from('avatars')
@@ -41,12 +41,14 @@ const Avatar = ({ url, size, onUpload }: AvatarProps): JSX.Element => {
}
} catch (error: unknown) {
if (error instanceof Error) {
console.log('Error downloading image: ', error.message);
console.log('Error downloading image:', error.message);
}
}
}
async function uploadAvatar(event: React.ChangeEvent<HTMLInputElement>) {
async function uploadAvatar(
event: React.ChangeEvent<HTMLInputElement>
): Promise<void> {
try {
setUploading(true);
@@ -55,11 +57,11 @@ const Avatar = ({ url, size, onUpload }: AvatarProps): JSX.Element => {
}
const file = event.target.files[0];
const fileExt = file.name.split('.').pop();
const fileName = `${Math.random()}.${fileExt}`;
const fileExtension = file.name.split('.').pop();
const fileName = fileExtension ? `${Math.random()}.${fileExtension}` : '';
const filePath = `${fileName}`;
if (event.target.files[0].size > 150000) {
if (event.target.files[0].size > 150_000) {
alert('File is too big!');
event.target.value = '';
setUploading(false);

View File

@@ -9,39 +9,47 @@ the axios.post here, line 18.
import axios from 'axios';
import { toast } from 'react-toastify';
const Contact = (): JSX.Element => {
const sendEmail = () => {
const name = (document.getElementById('name') as HTMLInputElement).value;
const email = (document.getElementById('email') as HTMLInputElement).value;
const message = (document.getElementById('message') as HTMLInputElement)
.value;
const sendEmail = (): void => {
const name = (document.querySelector('#name') as HTMLInputElement).value;
const email = (document.querySelector('#email') as HTMLInputElement).value;
const message = (document.querySelector('#message') as HTMLInputElement)
.value;
if (name && email && message) {
axios
.post('/api/sendgrid', { email, name, message })
.then((result) => {
if (name && email && message) {
axios
.post('/api/sendgrid', { email, name, message })
.then(
(result: {
data: {
success: boolean;
message: string;
};
}) => {
if (result.data.success === true) {
toast.success(result.data.message);
(document.getElementById('name') as HTMLInputElement).value = '';
(document.getElementById('email') as HTMLInputElement).value = '';
(document.getElementById('message') as HTMLInputElement).value = '';
(document.querySelector('#name') as HTMLInputElement).value = '';
(document.querySelector('#email') as HTMLInputElement).value = '';
(document.querySelector('#message') as HTMLInputElement).value = '';
}
})
.catch((err) => {
console.log(err);
});
} else {
toast.info('Please fill all the fields ', {
position: 'top-center',
autoClose: 2000,
hideProgressBar: true,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: undefined,
}
)
.catch((error) => {
console.log(error);
});
}
};
} else {
toast.info('Please fill all the fields ', {
position: 'top-center',
autoClose: 2000,
hideProgressBar: true,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: undefined,
});
}
};
const Contact = (): JSX.Element => {
return (
<div className="max-w-xl px-5 py-10 m-auto">
<div>
@@ -87,8 +95,8 @@ const Contact = (): JSX.Element => {
<button
type="button"
className="btn btn-primary btn-sm"
onClick={(e) => {
e.preventDefault();
onClick={(event) => {
event.preventDefault();
sendEmail();
}}>
Submit{' '}

View File

@@ -16,7 +16,7 @@ import { supabase } from '../utils/supabaseClient';
import { toast } from 'react-toastify';
import { useRouter } from 'next/router';
type DashboardProps = {
type DashboardProperties = {
profile: { username: string; website: string; avatar_url: string };
session: Session;
planName: string;
@@ -26,7 +26,7 @@ const Dashboard = ({
profile,
session,
planName,
}: DashboardProps): JSX.Element => {
}: DashboardProperties): JSX.Element => {
const router = useRouter();
const [loading, setLoading] = useState(false);
const [username, setUsername] = useState(profile?.username || '');
@@ -48,7 +48,7 @@ const Dashboard = ({
username: string;
website: string;
avatar_url: string;
}) {
}): Promise<void> {
try {
setLoading(true);
const user = supabase.auth.user();
@@ -89,7 +89,7 @@ const Dashboard = ({
size={150}
onUpload={(url) => {
setAvatarUrl(url);
updateProfile({ username, website, avatar_url: url });
void updateProfile({ username, website, avatar_url: url });
}}
/>
<div className="flex flex-col mb-5">
@@ -125,7 +125,7 @@ const Dashboard = ({
id="website"
type="website"
value={website || ''}
onChange={(e) => setWebsite(e.target.value)}
onChange={(event) => setWebsite(event.target.value)}
/>
</div>

View File

@@ -18,11 +18,11 @@ import Nav from './Nav';
import { ToastContainer } from 'react-toastify';
import { useAuth } from 'utils/AuthContext';
type LayoutProps = {
type LayoutProperties = {
children: JSX.Element;
};
const Layout = ({ children }: LayoutProps): JSX.Element => {
const Layout = ({ children }: LayoutProperties): JSX.Element => {
const { user, signOut } = useAuth();
return (

View File

@@ -17,7 +17,7 @@ const MailingList = (): JSX.Element => {
const validateEmail = () => {
// Regex patern for email validation
const regex =
/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
/^(([^\s"(),.:;<>@[\\\]]+(\.[^\s"(),.:;<>@[\\\]]+)*)|(".+"))@((\[(?:\d{1,3}\.){3}\d{1,3}])|(([\dA-Za-z\-]+\.)+[A-Za-z]{2,}))$/;
if (regex.test(mail)) {
// this is a valid email address
@@ -42,8 +42,8 @@ const MailingList = (): JSX.Element => {
setLoading(false);
}
})
.catch((err) => {
console.log(err);
.catch((error) => {
console.log(error);
setLoading(false);
});
};

View File

@@ -8,12 +8,12 @@ import Image from 'next/image';
import Link from 'next/link';
import Logo from 'public/logo.svg';
type NavProps = {
type NavProperties = {
user: Record<string, unknown>;
signOut: () => void;
};
const Nav = ({ user, signOut }: NavProps): JSX.Element => {
const Nav = ({ user, signOut }: NavProperties): JSX.Element => {
// Modify you menu directly here
const NavMenu = (
<>

View File

@@ -2,12 +2,15 @@ import { Dialog, Transition } from '@headlessui/react';
import { Fragment } from 'react';
type PaymentModalProps = {
type PaymentModalProperties = {
open: boolean;
setPayment: (arg0: boolean) => void;
setPayment: (argument0: boolean) => void;
};
const PaymentModal = ({ open, setPayment }: PaymentModalProps): JSX.Element => {
const PaymentModal = ({
open,
setPayment,
}: PaymentModalProperties): JSX.Element => {
function closeModal() {
setPayment(false);
}

View File

@@ -12,12 +12,12 @@ import { SupabaseClient } from '@supabase/supabase-js';
import { supabase } from 'utils/supabaseClient';
import { useAuth } from 'utils/AuthContext';
type ContainerProps = {
type ContainerProperties = {
children: JSX.Element;
supabaseClient: SupabaseClient;
};
const Container = ({ children }: ContainerProps): JSX.Element => {
const Container = ({ children }: ContainerProperties): JSX.Element => {
const { user, signOut } = useAuth();
if (user)
return (

View File

@@ -4,13 +4,17 @@ This card is used on the landing page
import Image from 'next/image';
type CardLandingProps = {
type CardLandingProperties = {
image: string;
title: string;
text: string;
};
const CardLanding = ({ image, title, text }: CardLandingProps): JSX.Element => {
const CardLanding = ({
image,
title,
text,
}: CardLandingProperties): JSX.Element => {
return (
<div className="flex h-48 p-5 mb-5 w-80 sm:ml-5 bg-base-100">
<div>

View File

@@ -4,11 +4,11 @@ This card is used on the landing page
import { FiStar } from 'react-icons/fi';
type KeyFeatureProps = {
type KeyFeatureProperties = {
children: JSX.Element;
};
const KeyFeature = ({ children }: KeyFeatureProps): JSX.Element => (
const KeyFeature = ({ children }: KeyFeatureProperties): JSX.Element => (
<div className="flex p-5 mb-5 italic shadow-sm bg-base-100">
<div className="flex w-12 h-12 p-2 my-auto text-white rounded-sm bg-accent-focus">
<FiStar className="m-auto text-2xl" />

View File

@@ -3,7 +3,7 @@ import router from 'next/router';
import { toast } from 'react-toastify';
import { useState } from 'react';
type LoginProps = {
type LoginProperties = {
resetPassword: (email: string) => Promise<{ error: { message: string } }>;
signIn: ({}) => Promise<{
data: Record<string, unknown>;
@@ -11,24 +11,28 @@ type LoginProps = {
}>;
};
const Login = ({ resetPassword, signIn }: LoginProps): JSX.Element => {
const Login = ({ resetPassword, signIn }: LoginProperties): JSX.Element => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [forgot, setForgot] = useState(false);
const resetPasswordLogin = () => {
resetPassword(email).then((result: { error: { message: string } }) => {
if (result.error) {
toast.error(result.error.message);
} else toast.success('Check your email to reset your password!');
});
const resetPasswordLogin = async (): Promise<void> => {
await resetPassword(email).then(
(result: { error: { message: string } }) => {
if (result.error) {
toast.error(result.error.message);
} else toast.success('Check your email to reset your password!');
}
);
};
const login = (e: React.SyntheticEvent<HTMLButtonElement>) => {
e.preventDefault();
const login = async (
event: React.SyntheticEvent<HTMLButtonElement>
): Promise<void> => {
event.preventDefault();
// Handle the login. Go to the homepage if success or display an error.
signIn({
await signIn({
email,
password,
}).then(
@@ -37,7 +41,7 @@ const Login = ({ resetPassword, signIn }: LoginProps): JSX.Element => {
error: { message: string };
}) => {
if (result.data) {
router.push('/');
void router.push('/');
}
if (result.error) {
toast.error(result.error.message);
@@ -97,7 +101,7 @@ const Login = ({ resetPassword, signIn }: LoginProps): JSX.Element => {
<button
className="w-full btn btn-primary"
onClick={(event) => {
login(event);
void login(event);
}}>
Log in
</button>
@@ -113,7 +117,7 @@ const Login = ({ resetPassword, signIn }: LoginProps): JSX.Element => {
className="flex items-center justify-center px-4 py-2 space-x-2 transition-colors duration-300 border rounded-md focus:outline-none border-base-200 group hover:bg-base-300"
onClick={(event) => {
event.preventDefault();
signIn({ provider: 'google' });
void signIn({ provider: 'google' });
}}>
<div className="text-base-content">
<IoLogoGoogle />
@@ -154,7 +158,7 @@ const Login = ({ resetPassword, signIn }: LoginProps): JSX.Element => {
className="w-full btn btn-primary btn-sm"
onClick={(event) => {
event.preventDefault();
resetPasswordLogin();
void resetPasswordLogin();
}}>
Recover my password
</button>

View File

@@ -3,7 +3,7 @@ import router from 'next/router';
import { toast } from 'react-toastify';
import { useState } from 'react';
type SignUpPanelProps = {
type SignUpPanelProperties = {
signIn: ({}) => Promise<{
data: Record<string, unknown>;
error: { message: string };
@@ -14,19 +14,23 @@ type SignUpPanelProps = {
}>;
};
const SignUpPanel = ({ signIn, signUp }: SignUpPanelProps): JSX.Element => {
const SignUpPanel = ({
signIn,
signUp,
}: SignUpPanelProperties): JSX.Element => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const signup = (e: React.SyntheticEvent<HTMLButtonElement>) => {
e.preventDefault();
const signup = async (
event: React.SyntheticEvent<HTMLButtonElement>
): Promise<void> => {
event.preventDefault();
// Handle the login. Go to the homepage if success or display an error.
signUp({
await signUp({
email,
password,
}).then((result) => {
console.log(result);
if (result.error) {
toast.error(result.error.message);
} else if (result.data?.confirmation_sent_at) {
@@ -35,7 +39,7 @@ const SignUpPanel = ({ signIn, signUp }: SignUpPanelProps): JSX.Element => {
'A confirmation email has been sent to you, watch your mailbox!'
);
} else if (result.data) {
router.push('/');
void router.push('/');
}
});
};
@@ -78,7 +82,7 @@ const SignUpPanel = ({ signIn, signUp }: SignUpPanelProps): JSX.Element => {
id="loginBtn"
className="w-full btn btn-primary"
onClick={(event) => {
signup(event);
void signup(event);
}}>
Sign Up
</button>
@@ -94,7 +98,7 @@ const SignUpPanel = ({ signIn, signUp }: SignUpPanelProps): JSX.Element => {
className="flex items-center justify-center px-4 py-2 space-x-2 transition-colors duration-300 border rounded-md focus:outline-none border-base-200 group hover:bg-base-300"
onClick={(event) => {
event.preventDefault();
signIn({ provider: 'google' });
void signIn({ provider: 'google' });
}}>
<div className="text-base-content">
<IoLogoGoogle />

View File

@@ -7,7 +7,8 @@
"build": "next build",
"start": "next start",
"eslint": "eslint . --fix",
"test": "playwright test"
"test": "playwright test",
"update-types": "npx openapi-typescript https://vwkxoczhqygxvqzbhkmj.supabase.co/rest/v1/?apikey=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlhdCI6MTYzMDQwNzE1NCwiZXhwIjoxOTQ1OTgzMTU0fQ.VsWbay8ZrhDe3-elrlh7EGtJfW1INXlUywjeh6h2xgc --output types/database/index.ts"
},
"dependencies": {
"@headlessui/react": "^1.4.2",
@@ -16,7 +17,6 @@
"@supabase/gotrue-js": "^1.21.7",
"@supabase/supabase-js": "^1.29.1",
"@types/node": "^17.0.4",
"@types/react": "^17.0.38",
"@types/react-dom": "^17.0.11",
"axios": "^0.24.0",
"cors": "^2.8.5",
@@ -30,8 +30,7 @@
"react-feather": "^2.0.9",
"react-icons": "^4.3.1",
"react-toastify": "^8.1.0",
"stripe": "^8.195.0",
"typescript": "^4.5.4"
"stripe": "^8.195.0"
},
"devDependencies": {
"@next/eslint-plugin-next": "^12.0.7",
@@ -39,6 +38,7 @@
"@types/cors": "^2.8.12",
"@types/express-rate-limit": "^5.1.3",
"@types/micro": "^7.3.6",
"@types/react": "^17.0.38",
"@typescript-eslint/eslint-plugin": "^5.8.0",
"autoprefixer": "^10.4.0",
"eslint": "^8.5.0",
@@ -53,8 +53,11 @@
"eslint-plugin-react": "^7.28.0",
"eslint-plugin-react-hooks": "^4.3.0",
"eslint-plugin-simple-import-sort": "^7.0.0",
"eslint-plugin-sonarjs": "^0.11.0",
"eslint-plugin-unicorn": "^40.0.0",
"postcss": "^8.4.5",
"prettier": "^2.5.1",
"tailwindcss": "^3.0.7"
"tailwindcss": "^3.0.7",
"typescript": "^4.5.4"
}
}

View File

@@ -9,11 +9,9 @@ import Document, {
class MyDocument extends Document {
static async getInitialProps(
ctx: DocumentContext
context_: DocumentContext
): Promise<DocumentInitialProps> {
const initialProps = await Document.getInitialProps(ctx);
return initialProps;
return await Document.getInitialProps(context_);
}
render(): JSX.Element {

View File

@@ -8,8 +8,8 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import { supabase } from 'utils/supabaseClient';
export default async function handler(
req: NextApiRequest,
request: NextApiRequest,
res: NextApiResponse
): Promise<void> {
supabase.auth.api.setAuthCookie(req, res);
supabase.auth.api.setAuthCookie(request, res);
}

View File

@@ -4,10 +4,10 @@ import { supabase } from 'utils/supabaseClient';
// Example of how to verify and get user data server-side.
const getUser = async (
req: NextApiRequest,
request: NextApiRequest,
res: NextApiResponse
): Promise<void> => {
const token = req.headers.token;
const token = request.headers.token;
if (typeof token !== 'string') {
return res.status(401).json({ error: 'Missing auth token.' });

View File

@@ -19,23 +19,23 @@ const cors = initMiddleware(
const limiter = initMiddleware(
rateLimit({
windowMs: 30000, // 30sec
windowMs: 30_000, // 30sec
max: 2, // Max 2 request per 30 sec
})
);
export default async function handler(
req: NextApiRequest,
request: NextApiRequest,
res: NextApiResponse
): Promise<void> {
await cors(req, res);
await limiter(req, res);
if (req.method === 'PUT') {
await cors(request, res);
await limiter(request, res);
if (request.method === 'PUT') {
axios
.put(
'https://api.sendgrid.com/v3/marketing/contacts',
{
contacts: [{ email: `${req.body.mail}` }],
contacts: [{ email: `${request.body.mail}` }],
list_ids: [process.env.SENDGRID_MAILING_ID],
},
{
@@ -52,11 +52,11 @@ export default async function handler(
'Your email has been succesfully added to the mailing list. Welcome 👋',
});
})
.catch((err) => {
.catch((error) => {
res.status(500).send({
message:
'Oups, there was a problem with your subscription, please try again or contact us',
error: err,
error: error,
});
});
}

View File

@@ -8,22 +8,22 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import sgMail from '@sendgrid/mail';
const sendGrid = async (
req: NextApiRequest,
request: NextApiRequest,
res: NextApiResponse
): Promise<void> => {
if (req.method === 'POST') {
if (request.method === 'POST') {
sgMail.setApiKey(process.env.SENDGRID_SECRET || '');
const msg = {
const message = {
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,
subject: `[${process.env.NEXT_PUBLIC_TITLE}] New message from ${request.body.name}`,
text: request.body.message,
reply_to: request.body.email,
};
sgMail
.send(msg)
.send(message)
.then(() => {
res
.status(200)

View File

@@ -13,7 +13,7 @@ const cors = initMiddleware(
const limiter = initMiddleware(
rateLimit({
windowMs: 30000, // 30sec
windowMs: 30_000, // 30sec
max: 4, // Max 4 request per 30 sec
})
);
@@ -24,26 +24,26 @@ const stripe = new Stripe(process.env.STRIPE_SECRET || '', {
});
export default async function handler(
req: NextApiRequest,
request: NextApiRequest,
res: NextApiResponse
): Promise<void> {
await cors(req, res);
await limiter(req, res);
if (req.method === 'POST') {
const { priceId } = req.body;
await cors(request, res);
await limiter(request, res);
if (request.method === 'POST') {
const { priceId } = request.body;
// See https://stripe.com/docs/api/checkout/sessions/create
// for additional parameters to pass.
try {
const session = req.body.customerId
const session = request.body.customerId
? await stripe.checkout.sessions.create({
mode: req.body.pay_mode,
mode: request.body.pay_mode,
payment_method_types: ['card'],
client_reference_id: req.body.userId,
client_reference_id: request.body.userId,
metadata: {
priceId: req.body.priceId,
priceId: request.body.priceId,
},
customer: req.body.customerId,
customer: request.body.customerId,
line_items: [
{
price: priceId,
@@ -54,16 +54,16 @@ export default async function handler(
// {CHECKOUT_SESSION_ID} is a string literal; do not change it!
// the actual Session ID is returned in the query parameter when your customer
// is redirected to the success page.
success_url: `${req.headers.origin}/dashboard?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${req.headers.origin}/pricing`,
success_url: `${request.headers.origin}/dashboard?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${request.headers.origin}/pricing`,
})
: await stripe.checkout.sessions.create({
mode: 'subscription',
payment_method_types: ['card'],
customer_email: req.body.email,
client_reference_id: req.body.userId,
customer_email: request.body.email,
client_reference_id: request.body.userId,
metadata: {
priceId: req.body.priceId,
priceId: request.body.priceId,
},
line_items: [
{
@@ -75,16 +75,16 @@ export default async function handler(
// {CHECKOUT_SESSION_ID} is a string literal; do not change it!
// the actual Session ID is returned in the query parameter when your customer
// is redirected to the success page.
success_url: `${req.headers.origin}/dashboard?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${req.headers.origin}/pricing`,
success_url: `${request.headers.origin}/dashboard?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${request.headers.origin}/pricing`,
});
res.status(200).send({ url: session.url });
} catch (e: unknown) {
} catch (error: unknown) {
res.status(400);
if (e instanceof Error) {
if (error instanceof Error) {
return res.send({
error: {
message: e.message,
message: error.message,
},
});
}

View File

@@ -16,7 +16,7 @@ const cors = initMiddleware(
const limiter = initMiddleware(
rateLimit({
windowMs: 30000, // 30sec
windowMs: 30_000, // 30sec
max: 150, // Max 4 request per 30 sec
})
);
@@ -27,16 +27,16 @@ const stripe = new Stripe(process.env.STRIPE_SECRET || '', {
});
export default async function handler(
req: NextApiRequest,
request: NextApiRequest,
res: NextApiResponse
): Promise<void> {
await cors(req, res);
await limiter(req, res);
if (req.method === 'POST') {
const returnUrl = `${req.headers.origin}/dashboard`; // Stripe will return to the dashboard, you can change it
await cors(request, res);
await limiter(request, res);
if (request.method === 'POST') {
const returnUrl = `${request.headers.origin}/dashboard`; // Stripe will return to the dashboard, you can change it
const portalsession = await stripe.billingPortal.sessions.create({
customer: req.body.customerId,
customer: request.body.customerId,
return_url: returnUrl,
});
res.status(200).send({ url: portalsession.url });

View File

@@ -39,7 +39,7 @@ const supabase = createClient(
const limiter = initMiddleware(
rateLimit({
windowMs: 30000, // 30sec
windowMs: 30_000, // 30sec
max: 150, // Max 150 request per 30 sec
})
);
@@ -52,18 +52,18 @@ const stripe = new Stripe(process.env.STRIPE_SECRET || '', {
});
export default async function handler(
req: NextApiRequest,
request: NextApiRequest,
res: NextApiResponse
): Promise<void> {
await cors(req, res);
await limiter(req, res);
await cors(request, res);
await limiter(request, res);
if (req.method === 'POST') {
if (request.method === 'POST') {
// Retrieve the event by verifying the signature using the raw body and secret.
let event: Stripe.Event;
const buf = await buffer(req);
const buf = await buffer(request);
const sig = req.headers['stripe-signature'] as string;
const sig = request.headers['stripe-signature'] as string;
try {
event = stripe.webhooks.constructEvent(
@@ -71,8 +71,8 @@ export default async function handler(
sig,
process.env.STRIPE_WEBHOOK || ''
);
} catch (err) {
console.log(err);
} catch (error) {
console.log(error);
console.log(`⚠️ Webhook signature verification failed.`);
console.log(
`⚠️ Check the env file and enter the correct webhook secret.`
@@ -118,7 +118,7 @@ export default async function handler(
},
])
.then()
.then(null, (err) => console.log('err: ', err)); // catch
.then(null, (error) => console.log('err:', error)); // catch
} else if (subscriptions?.length && subscriptions?.length > 0) {
await supabase
.from('subscriptions')
@@ -130,7 +130,7 @@ export default async function handler(
})
.eq('id', dataObject.client_reference_id)
.then()
.then(null, (err) => console.log('err: ', err)); // catch
.then(null, (error) => console.log('err:', error)); // catch
}
break;
case 'customer.subscription.deleted':
@@ -139,7 +139,7 @@ export default async function handler(
.update({ paid_user: false })
.eq('customer_id', dataObject.customer)
.then()
.then(null, (err) => console.log('err: ', err)); // catch
.then(null, (error) => console.log('err:', error)); // catch
break;
case 'invoice.payment_failed':
// If the payment fails or the customer does not have a valid payment method,

192
pnpm-lock.yaml generated
View File

@@ -31,6 +31,8 @@ specifiers:
eslint-plugin-react: ^7.28.0
eslint-plugin-react-hooks: ^4.3.0
eslint-plugin-simple-import-sort: ^7.0.0
eslint-plugin-sonarjs: ^0.11.0
eslint-plugin-unicorn: ^40.0.0
express-rate-limit: ^6.0.1
micro: ^9.3.4
next: '>=12.0.7'
@@ -53,7 +55,6 @@ dependencies:
'@supabase/gotrue-js': 1.21.7
'@supabase/supabase-js': 1.29.1
'@types/node': 17.0.4
'@types/react': 17.0.38
'@types/react-dom': 17.0.11
axios: 0.24.0
cors: 2.8.5
@@ -68,7 +69,6 @@ dependencies:
react-icons: 4.3.1_react@17.0.2
react-toastify: 8.1.0_react-dom@17.0.2+react@17.0.2
stripe: 8.195.0
typescript: 4.5.4
devDependencies:
'@next/eslint-plugin-next': 12.0.7
@@ -76,6 +76,7 @@ devDependencies:
'@types/cors': 2.8.12
'@types/express-rate-limit': 5.1.3
'@types/micro': 7.3.6
'@types/react': 17.0.38
'@typescript-eslint/eslint-plugin': 5.8.0_eslint@8.5.0+typescript@4.5.4
autoprefixer: 10.4.0_postcss@8.4.5
eslint: 8.5.0
@@ -90,9 +91,12 @@ devDependencies:
eslint-plugin-react: 7.28.0_eslint@8.5.0
eslint-plugin-react-hooks: 4.3.0_eslint@8.5.0
eslint-plugin-simple-import-sort: 7.0.0_eslint@8.5.0
eslint-plugin-sonarjs: 0.11.0_eslint@8.5.0
eslint-plugin-unicorn: 40.0.0_eslint@8.5.0
postcss: 8.4.5
prettier: 2.5.1
tailwindcss: 3.0.7_16a290f6d0e3717bf6d2667234aebd30
typescript: 4.5.4
packages:
@@ -1099,13 +1103,16 @@ packages:
/@types/node/17.0.4:
resolution: {integrity: sha512-6xwbrW4JJiJLgF+zNypN5wr2ykM9/jHcL7rQ8fZe2vuftggjzZeRSM4OwRc6Xk8qWjwJ99qVHo/JgOGmomWRog==}
/@types/normalize-package-data/2.4.1:
resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==}
dev: true
/@types/parse-json/4.0.0:
resolution: {integrity: sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==}
dev: true
/@types/prop-types/15.7.4:
resolution: {integrity: sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==}
dev: false
/@types/qs/6.9.7:
resolution: {integrity: sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==}
@@ -1127,11 +1134,9 @@ packages:
'@types/prop-types': 15.7.4
'@types/scheduler': 0.16.2
csstype: 3.0.9
dev: false
/@types/scheduler/0.16.2:
resolution: {integrity: sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==}
dev: false
/@types/serve-static/1.13.10:
resolution: {integrity: sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==}
@@ -1699,6 +1704,11 @@ packages:
node-gyp-build: 4.3.0
dev: false
/builtin-modules/3.2.0:
resolution: {integrity: sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA==}
engines: {node: '>=6'}
dev: true
/builtin-status-codes/3.0.0:
resolution: {integrity: sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=}
dev: false
@@ -1786,6 +1796,10 @@ packages:
fsevents: 2.3.2
dev: true
/ci-info/3.3.0:
resolution: {integrity: sha512-riT/3vI5YpVH6/qomlDnJow6TBee2PBKSEpx3O32EGPYbWGIRsIlGRms3Sm74wYE1JMo8RnO04Hb12+v1J5ICw==}
dev: true
/cipher-base/1.0.4:
resolution: {integrity: sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==}
dependencies:
@@ -1797,6 +1811,13 @@ packages:
resolution: {integrity: sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==}
dev: false
/clean-regexp/1.0.0:
resolution: {integrity: sha1-jffHquUf02h06PjQW5GAvBGj/tc=}
engines: {node: '>=4'}
dependencies:
escape-string-regexp: 1.0.5
dev: true
/clsx/1.1.1:
resolution: {integrity: sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==}
engines: {node: '>=6'}
@@ -1982,7 +2003,6 @@ packages:
/csstype/3.0.9:
resolution: {integrity: sha512-rpw6JPxK6Rfg1zLOYCSwle2GFOOsnjmDYDaBwEcwoOg4qlsIVCN789VkBZDJAGi4T07gI4YSutR43t9Zz4Lzuw==}
dev: false
/d/1.0.1:
resolution: {integrity: sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==}
@@ -2474,6 +2494,38 @@ packages:
eslint: 8.5.0
dev: true
/eslint-plugin-sonarjs/0.11.0_eslint@8.5.0:
resolution: {integrity: sha512-ei/WuZiL0wP+qx2KrxKyZs3+eDbxiGAhFSm3GKCOOAUkg+G2ny6TSXDB2j67tvyqHefi+eoQsAgGQvz+nEtIBw==}
engines: {node: '>=12'}
peerDependencies:
eslint: ^5.0.0 || ^6.0.0 || ^7.0.0|| ^8.0.0
dependencies:
eslint: 8.5.0
dev: true
/eslint-plugin-unicorn/40.0.0_eslint@8.5.0:
resolution: {integrity: sha512-5GRXISfBk8jMmYk1eeNDw8zSRnWTxBjWkzx2Prre6E2/yLu2twozZ3EomLWCBu9nWms/ZE361BItyMQwfnG1qA==}
engines: {node: '>=12'}
peerDependencies:
eslint: '>=7.32.0'
dependencies:
'@babel/helper-validator-identifier': 7.15.7
ci-info: 3.3.0
clean-regexp: 1.0.0
eslint: 8.5.0
eslint-utils: 3.0.0_eslint@8.5.0
esquery: 1.4.0
indent-string: 4.0.0
is-builtin-module: 3.1.0
lodash: 4.17.21
pluralize: 8.0.0
read-pkg-up: 7.0.1
regexp-tree: 0.1.24
safe-regex: 2.1.1
semver: 7.3.5
strip-indent: 3.0.0
dev: true
/eslint-scope/5.1.1:
resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==}
engines: {node: '>=8.0.0'}
@@ -2725,7 +2777,6 @@ packages:
dependencies:
locate-path: 5.0.0
path-exists: 4.0.0
dev: false
/flat-cache/3.0.4:
resolution: {integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==}
@@ -2929,6 +2980,10 @@ packages:
minimalistic-crypto-utils: 1.0.1
dev: false
/hosted-git-info/2.8.9:
resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==}
dev: true
/http-errors/1.6.2:
resolution: {integrity: sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=}
engines: {node: '>= 0.6'}
@@ -3032,6 +3087,11 @@ packages:
engines: {node: '>=0.8.19'}
dev: true
/indent-string/4.0.0:
resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==}
engines: {node: '>=8'}
dev: true
/inflight/1.0.6:
resolution: {integrity: sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=}
dependencies:
@@ -3088,6 +3148,13 @@ packages:
call-bind: 1.0.2
has-tostringtag: 1.0.0
/is-builtin-module/3.1.0:
resolution: {integrity: sha512-OV7JjAgOTfAFJmHZLvpSTb4qi0nIILDV1gWPYDnDJUTNFM5aGlRAhk4QcT8i7TuAleeEV5Fdkqn3t4mS+Q11fg==}
engines: {node: '>=6'}
dependencies:
builtin-modules: 3.2.0
dev: true
/is-callable/1.2.4:
resolution: {integrity: sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==}
engines: {node: '>= 0.4'}
@@ -3364,7 +3431,6 @@ packages:
engines: {node: '>=8'}
dependencies:
p-locate: 4.1.0
dev: false
/lodash.merge/4.6.2:
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
@@ -3374,6 +3440,10 @@ packages:
resolution: {integrity: sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=}
dev: false
/lodash/4.17.21:
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
dev: true
/loose-envify/1.4.0:
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
hasBin: true
@@ -3444,6 +3514,11 @@ packages:
hasBin: true
dev: true
/min-indent/1.0.1:
resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==}
engines: {node: '>=4'}
dev: true
/minimalistic-assert/1.0.1:
resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==}
dev: false
@@ -3612,6 +3687,15 @@ packages:
resolution: {integrity: sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==}
dev: true
/normalize-package-data/2.5.0:
resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==}
dependencies:
hosted-git-info: 2.8.9
resolve: 1.20.0
semver: 5.7.1
validate-npm-package-license: 3.0.4
dev: true
/normalize-path/3.0.0:
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
engines: {node: '>=0.10.0'}
@@ -3731,7 +3815,6 @@ packages:
engines: {node: '>=6'}
dependencies:
p-try: 2.2.0
dev: false
/p-limit/3.1.0:
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
@@ -3752,7 +3835,6 @@ packages:
engines: {node: '>=8'}
dependencies:
p-limit: 2.3.0
dev: false
/p-try/1.0.0:
resolution: {integrity: sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=}
@@ -3762,7 +3844,6 @@ packages:
/p-try/2.2.0:
resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
engines: {node: '>=6'}
dev: false
/pako/1.0.11:
resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==}
@@ -3807,7 +3888,6 @@ packages:
/path-exists/4.0.0:
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
engines: {node: '>=8'}
dev: false
/path-is-absolute/1.0.1:
resolution: {integrity: sha1-F0uSaHNVNP+8es5r9TpanhtcX18=}
@@ -3910,6 +3990,11 @@ packages:
- utf-8-validate
dev: true
/pluralize/8.0.0:
resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==}
engines: {node: '>=4'}
dev: true
/pngjs/4.0.1:
resolution: {integrity: sha512-rf5+2/ioHeQxR6IxuYNYGFytUyG3lma/WW1nsmjeHlWwtb2aByla6dkVc8pmJ9nplzkTA0q2xx7mMWrOTqT4Gg==}
engines: {node: '>=8.0.0'}
@@ -4183,6 +4268,25 @@ packages:
object-assign: 4.1.1
dev: false
/read-pkg-up/7.0.1:
resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==}
engines: {node: '>=8'}
dependencies:
find-up: 4.1.0
read-pkg: 5.2.0
type-fest: 0.8.1
dev: true
/read-pkg/5.2.0:
resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==}
engines: {node: '>=8'}
dependencies:
'@types/normalize-package-data': 2.4.1
normalize-package-data: 2.5.0
parse-json: 5.2.0
type-fest: 0.6.0
dev: true
/readable-stream/3.6.0:
resolution: {integrity: sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==}
engines: {node: '>= 6'}
@@ -4213,6 +4317,11 @@ packages:
/regenerator-runtime/0.13.9:
resolution: {integrity: sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==}
/regexp-tree/0.1.24:
resolution: {integrity: sha512-s2aEVuLhvnVJW6s/iPgEGK6R+/xngd2jNQ+xy4bXNDKxZKJH6jpPHY6kVeVv1IeLCHgswRj+Kl3ELaDjG6V1iw==}
hasBin: true
dev: true
/regexp.prototype.flags/1.3.1:
resolution: {integrity: sha512-JiBdRBq91WlY7uRJ0ds7R+dU02i6LKi8r3BuQhNXn+kmeLN+EfHhfjqMRis1zJxnlu88hq/4dx0P2OP3APRTOA==}
engines: {node: '>= 0.4'}
@@ -4287,6 +4396,12 @@ packages:
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
dev: false
/safe-regex/2.1.1:
resolution: {integrity: sha512-rx+x8AMzKb5Q5lQ95Zoi6ZbJqwCLkqi3XuJXp5P3rT8OEc6sZCJG5AE5dU3lsgRr/F4Bs31jSlVN+j5KrsGu9A==}
dependencies:
regexp-tree: 0.1.24
dev: true
/safer-buffer/2.1.2:
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
dev: false
@@ -4298,6 +4413,11 @@ packages:
object-assign: 4.1.1
dev: false
/semver/5.7.1:
resolution: {integrity: sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==}
hasBin: true
dev: true
/semver/6.3.0:
resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==}
hasBin: true
@@ -4429,6 +4549,28 @@ packages:
whatwg-url: 7.1.0
dev: false
/spdx-correct/3.1.1:
resolution: {integrity: sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==}
dependencies:
spdx-expression-parse: 3.0.1
spdx-license-ids: 3.0.11
dev: true
/spdx-exceptions/2.3.0:
resolution: {integrity: sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==}
dev: true
/spdx-expression-parse/3.0.1:
resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==}
dependencies:
spdx-exceptions: 2.3.0
spdx-license-ids: 3.0.11
dev: true
/spdx-license-ids/3.0.11:
resolution: {integrity: sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g==}
dev: true
/stack-utils/2.0.5:
resolution: {integrity: sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==}
engines: {node: '>=10'}
@@ -4516,6 +4658,13 @@ packages:
engines: {node: '>=4'}
dev: true
/strip-indent/3.0.0:
resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==}
engines: {node: '>=8'}
dependencies:
min-indent: 1.0.1
dev: true
/strip-json-comments/3.1.1:
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
engines: {node: '>=8'}
@@ -4694,11 +4843,21 @@ packages:
engines: {node: '>=10'}
dev: true
/type-fest/0.6.0:
resolution: {integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==}
engines: {node: '>=8'}
dev: true
/type-fest/0.7.1:
resolution: {integrity: sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==}
engines: {node: '>=8'}
dev: false
/type-fest/0.8.1:
resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==}
engines: {node: '>=8'}
dev: true
/type/1.2.0:
resolution: {integrity: sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==}
dev: false
@@ -4717,7 +4876,7 @@ packages:
resolution: {integrity: sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==}
engines: {node: '>=4.2.0'}
hasBin: true
dev: false
dev: true
/unbox-primitive/1.0.1:
resolution: {integrity: sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==}
@@ -4773,6 +4932,13 @@ packages:
resolution: {integrity: sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==}
dev: true
/validate-npm-package-license/3.0.4:
resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==}
dependencies:
spdx-correct: 3.1.1
spdx-expression-parse: 3.0.1
dev: true
/vary/1.1.2:
resolution: {integrity: sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=}
engines: {node: '>= 0.8'}

View File

@@ -1,76 +1,76 @@
module.exports = {
mode: 'jit',
purge: ['./pages/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}'],
darkMode: false, // or 'media' or 'class'
theme: {
fontFamily: {
title: ['Poppins'],
body: ['Inter'],
},
extend: {},
},
variants: {
extend: {},
},
plugins: [require('daisyui')],
daisyui: {
themes: [
{
supaTheme: {
primary: '#00B8F0',
'primary-focus': '#009de0',
'primary-content': '#ffffff',
mode: 'jit',
purge: ['./pages/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}'],
darkMode: false, // or 'media' or 'class'
theme: {
fontFamily: {
title: ['Poppins'],
body: ['Inter'],
},
extend: {},
},
variants: {
extend: {},
},
plugins: [require('daisyui')],
daisyui: {
themes: [
{
supaTheme: {
primary: '#00B8F0',
'primary-focus': '#009de0',
'primary-content': '#ffffff',
secondary: '#f03800',
'secondary-focus': '#e22f00',
'secondary-content': '#ffffff',
secondary: '#f03800',
'secondary-focus': '#e22f00',
'secondary-content': '#ffffff',
accent: '#00f0b0',
'accent-focus': '#00e28a',
'accent-content': '#ffffff',
accent: '#00f0b0',
'accent-focus': '#00e28a',
'accent-content': '#ffffff',
neutral: '#3d4451',
'neutral-focus': '#2a2e37',
'neutral-content': '#ffffff',
neutral: '#3d4451',
'neutral-focus': '#2a2e37',
'neutral-content': '#ffffff',
'base-100': '#ffffff',
'base-200': '#767676',
'base-300': '#d1d5db',
'base-content': '#1f2937',
'base-100': '#ffffff',
'base-200': '#767676',
'base-300': '#d1d5db',
'base-content': '#1f2937',
info: '#2094f3' /* Info */,
success: '#009485' /* Success */,
warning: '#ff9900' /* Warning */,
error: '#ff5724' /* Error */,
},
dark: {
primary: '#00B8F0',
'primary-focus': '#009de0',
'primary-content': '#ffffff',
info: '#2094f3' /* Info */,
success: '#009485' /* Success */,
warning: '#ff9900' /* Warning */,
error: '#ff5724' /* Error */,
},
dark: {
primary: '#00B8F0',
'primary-focus': '#009de0',
'primary-content': '#ffffff',
secondary: '#f03800',
'secondary-focus': '#e22f00',
'secondary-content': '#ffffff',
secondary: '#f03800',
'secondary-focus': '#e22f00',
'secondary-content': '#ffffff',
accent: '#00f0b0',
'accent-focus': '#00e28a',
'accent-content': '#ffffff',
accent: '#00f0b0',
'accent-focus': '#00e28a',
'accent-content': '#ffffff',
neutral: '#3d4451',
'neutral-focus': '#2a2e37',
'neutral-content': '#ffffff',
neutral: '#3d4451',
'neutral-focus': '#2a2e37',
'neutral-content': '#ffffff',
'base-100': '#2A2E37',
'base-200': '#EBECF0',
'base-300': '#16181D',
'base-content': '#EBECF0',
'base-100': '#2A2E37',
'base-200': '#EBECF0',
'base-300': '#16181D',
'base-content': '#EBECF0',
info: '#2094f3',
success: '#009485',
warning: '#ff9900',
error: '#ff5724',
},
},
],
},
info: '#2094f3',
success: '#009485',
warning: '#ff9900',
error: '#ff5724',
},
},
],
},
};

View File

@@ -36,7 +36,7 @@
// "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */
/* Module Resolution Options */
"moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
"baseUrl": "./", /* Base directory to resolve non-absolute module names. */ /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
"baseUrl": "./", /* Base directory to resolve non-absolute module names. */ /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
"allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
@@ -46,7 +46,7 @@
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
"inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
"inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ /* Experimental Options */// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
"inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ /* Experimental Options */ // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
/* Advanced Options */
"skipLibCheck": true, /* Skip type checking of declaration files. */
@@ -65,9 +65,10 @@
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx"
"**/*.tsx",
"utils/AuthContext.tsx"
],
"exclude": [
"node_modules"
]
}
}

File diff suppressed because one or more lines are too long

54
types/supabase.ts Normal file
View File

@@ -0,0 +1,54 @@
/**
* This file was auto-generated by openapi-typescript.
* Do not make direct changes to the file.
*/
export interface paths {
'/': {
get: {
responses: {
/** OK */
200: unknown;
};
};
};
}
export interface parameters {
/**
* @description Preference
* @enum {string}
*/
preferParams: 'params=single-object';
/**
* @description Preference
* @enum {string}
*/
preferReturn: 'return=representation' | 'return=minimal' | 'return=none';
/**
* @description Preference
* @enum {string}
*/
preferCount: 'count=none';
/** @description Filtering Columns */
select: string;
/** @description On Conflict */
on_conflict: string;
/** @description Ordering */
order: string;
/** @description Limiting and Pagination */
range: string;
/**
* @description Limiting and Pagination
* @default items
*/
rangeUnit: string;
/** @description Limiting and Pagination */
offset: string;
/** @description Limiting and Pagination */
limit: string;
}
export interface operations {}
export interface external {}

View File

@@ -1,63 +0,0 @@
import React, { createContext, useContext, useEffect, useState } from 'react';
import { supabase } from 'utils/supabaseClient';
// create a context for authentication
const AuthContext = createContext();
export const AuthProvider = ({ children }) => {
const [user, setUser] = useState();
const [session, setSession] = useState();
const [loading, setLoading] = useState(true);
useEffect(() => {
// Check active sessions and sets the user
const session = supabase.auth.session();
setUser(session?.user ?? null);
setSession(session ?? null);
setLoading(false);
// Listen for changes on auth state (logged in, signed out, etc.)
const { data: listener } = supabase.auth.onAuthStateChange(
async (event, session) => {
if ((event === 'SIGNED_OUT') | (event === 'SIGNED_IN')) {
fetch('/api/auth', {
method: 'POST',
headers: new Headers({ 'Content-Type': 'application/json' }),
credentials: 'same-origin',
body: JSON.stringify({ event, session }),
}).then((res) => res.json());
}
if (event === 'USER_UPDATED') {
}
setUser(session?.user ?? null);
setSession(session ?? null);
setLoading(false);
}
);
return () => {
listener?.unsubscribe();
};
}, []);
// Will be passed down to Signup, Login and Dashboard components
const value = {
signUp: (data) => supabase.auth.signUp(data),
signIn: (data) => supabase.auth.signIn(data),
signOut: () => supabase.auth.signOut(),
resetPassword: (data) => supabase.auth.api.resetPasswordForEmail(data),
user,
session,
};
return (
<AuthContext.Provider value={value}>
{!loading && children}
</AuthContext.Provider>
);
};
// export the useAuth hook
export const useAuth = () => useContext(AuthContext);

85
utils/AuthContext.tsx Normal file
View File

@@ -0,0 +1,85 @@
import {
ReactNode,
createContext,
useContext,
useEffect,
useState,
} from 'react';
import { UserCredentials } from '@supabase/gotrue-js';
import { supabase } from 'utils/supabaseClient';
type authContextType = {
user: boolean;
login: () => void;
logout: () => void;
};
type Properties = {
children: ReactNode;
};
const authContextDefaultValues: authContextType = {
user: false,
login: () => {},
logout: () => {},
};
// create a context for authentication
const AuthContext = createContext<authContextType>(authContextDefaultValues);
export const AuthProvider = ({ children }: Properties): JSX.Element => {
const [user, setUser] = useState(supabase.auth.session()?.user);
const [session, setSession] = useState(supabase.auth.session());
const [loading, setLoading] = useState(true);
useEffect(() => {
// Check active sessions and sets the user
const session = supabase.auth.session();
setUser(session?.user);
setSession(session);
setLoading(false);
// Listen for changes on auth state (logged in, signed out, etc.)
const { data: listener } = supabase.auth.onAuthStateChange(
(event, session): void => {
if (event === 'SIGNED_OUT' || event === 'SIGNED_IN') {
void fetch('/api/auth', {
method: 'POST',
headers: new Headers({ 'Content-Type': 'application/json' }),
credentials: 'same-origin',
body: JSON.stringify({ event, session }),
}).then((response) => response.json());
}
setUser(session?.user);
setSession(session);
setLoading(false);
}
);
return () => {
listener?.unsubscribe();
};
}, []);
// Will be passed down to Signup, Login and Dashboard components
const value = {
signUp: (data: UserCredentials) => supabase.auth.signUp(data),
signIn: (data: UserCredentials) => supabase.auth.signIn(data),
signOut: () => supabase.auth.signOut(),
resetPassword: (data: string) =>
supabase.auth.api.resetPasswordForEmail(data),
user,
session,
};
return (
<AuthContext.Provider value={value}>
{!loading && children}
</AuthContext.Provider>
);
};
// export the useAuth hook
export const useAuth = () => useContext(AuthContext);

View File

@@ -4,9 +4,9 @@
import type { NextApiRequest, NextApiResponse } from 'next';
export default function initMiddleware(middleware: any) {
return (req: NextApiRequest, res: NextApiResponse) =>
return (request: NextApiRequest, res: NextApiResponse) =>
new Promise((resolve, reject) => {
middleware(req, res, (result: any) => {
middleware(request, res, (result: any) => {
if (result instanceof Error) {
return reject(result);
}