Typescript first integration + fix all eslint errors (97 warnings)

This commit is contained in:
Michael
2021-08-10 22:03:23 +02:00
parent 8d69135404
commit 2cccdba402
55 changed files with 10477 additions and 2308 deletions

View File

@@ -6,8 +6,9 @@
"cypress/globals": true
},
"extends": [
"plugin:react/recommended",
"airbnb",
"next",
"next/core-web-vitals",
"plugin:@typescript-eslint/recommended",
"prettier"
],
"parserOptions": {
@@ -18,7 +19,7 @@
"sourceType": "module"
},
"plugins": [
"react",
"@typescript-eslint",
"cypress",
"simple-import-sort",
"prettier"
@@ -26,7 +27,12 @@
"rules": {
"no-console": "off",
"react/no-unescaped-entities": "off",
"prettier/prettier": "error",
"prettier/prettier": [
"error",
{
"endOfLine": "auto"
}
],
"react/react-in-jsx-scope": "off",
"react/jsx-filename-extension": [
2,

View File

@@ -1,5 +1,9 @@
{
"semi": true,
"trailingComma": "es5",
"singleQuote": true,
"tabWidth": 2,
"semi": true
"useTabs": true,
"endOfLine": "auto",
"printWidth": 100
}

View File

@@ -4,18 +4,14 @@ import authImage from 'public/auth.png';
const AuthText = () => (
<div className="lg:mt-0 max-w-lg flex flex-col text-xl">
<div className="mt-10 mb-3 m-auto">
<Image
src={authImage}
width={authImage.width / 1.5}
height={authImage.height / 1.5}
/>
<Image src={authImage} width={authImage.width / 1.5} height={authImage.height / 1.5} />
</div>
<h2 className="text-4xl font-title font-semibold text-center">
Join SupaNexTail for <span className="text-primary">free</span>!
</h2>
<p className="mb-5 mt-8 leading-9">
Create your website in a few minutes with our boilerplate. You can use the
login system, this will allow you to discover the sample dashboard page.
Create your website in a few minutes with our boilerplate. You can use the login system, this
will allow you to discover the sample dashboard page.
</p>
</div>
);

View File

@@ -6,21 +6,24 @@ You can tweak the max size, line 47
import { useEffect, useState } from 'react';
import Image from 'next/image';
import { supabase } from 'utils/supabaseClient';
const Avatar = ({ url, size, onUpload }) => {
const [avatarUrl, setAvatarUrl] = useState(null);
const [uploading, setUploading] = useState(false);
const customImgLoader = ({ src }) => {
return `${src}`;
};
useEffect(() => {
if (url) downloadImage(url);
}, [url]);
async function downloadImage(path) {
try {
const { data, error } = await supabase.storage
.from('avatars')
.download(path);
const { data, error } = await supabase.storage.from('avatars').download(path);
if (error) {
throw error;
}
@@ -51,9 +54,7 @@ const Avatar = ({ url, size, onUpload }) => {
return;
}
const { error: uploadError } = await supabase.storage
.from('avatars')
.upload(filePath, file);
const { error: uploadError } = await supabase.storage.from('avatars').upload(filePath, file);
if (uploadError) {
throw uploadError;
@@ -70,11 +71,16 @@ const Avatar = ({ url, size, onUpload }) => {
return (
<div className="m-auto mb-5">
{avatarUrl ? (
<img
<div className="w-full flex justify-center">
<Image
loader={customImgLoader} //Using custom loader because of this issue https://github.com/vercel/next.js/discussions/19732
src={avatarUrl}
height={100}
width={100}
alt="Avatar"
className="avatar rounded-full w-28 h-28 flex m-auto"
className="avatar rounded-full w-28 h-28"
/>
</div>
) : (
<div className="avatar rounded-full w-28 h-28" />
)}

View File

@@ -13,9 +13,8 @@ const CardsLanding = () => (
We've got you covered
</h2>
<p className="max-w-md m-auto text-center">
Dont waste your time and reinvent the wheel, we have provided you with a
maximum of features so that you only have one goal, to make your SaaS a
reality.
Dont waste your time and reinvent the wheel, we have provided you with a maximum of features
so that you only have one goal, to make your SaaS a reality.
</p>
<div className="flex flex-wrap justify-center mt-10">
<CardLanding
@@ -28,11 +27,7 @@ const CardsLanding = () => (
text="Integrated backend already setup with Next.js API Routes"
title="Backend"
/>
<CardLanding
image={cardAuth}
text="Auth and user management with Supabase"
title="Auth"
/>
<CardLanding image={cardAuth} text="Auth and user management with Supabase" title="Auth" />
<CardLanding
image={cardResponsive}
text="Mobile ready, fully responsive and customizable with Tailwind CSS"

View File

@@ -50,8 +50,8 @@ const Contact = () => {
</h2>
</div>
<p className="m-auto text-center">
Do you have a question about SupaNexTail? A cool feature you'd like us
to integrate? A bug to report? Don't hesitate!
Do you have a question about SupaNexTail? A cool feature you'd like us to integrate? A bug
to report? Don't hesitate!
</p>
</div>
<form className="m-auto mt-5 grid grid-cols-1 gap-4 md:grid-cols-2 p-5">

View File

@@ -61,9 +61,7 @@ export default function Dashboard(props) {
return (
<div className="flex flex-col text-left w-full max-w-xl m-auto px-5 py-10">
<div className="max-w-sm flex flex-col justify-center m-auto w-full p-5">
<h1 className="text-4xl font-bold md:text-5xl font-title text-center mb-10">
Dashboard
</h1>
<h1 className="text-4xl font-bold md:text-5xl font-title text-center mb-10">Dashboard</h1>
<Avatar
url={avatar_url}
size={150}

View File

@@ -10,12 +10,11 @@ const Landing = () => (
<div className="flex max-w-6xl m-auto justify-around">
<div className="max-w-sm mr-16 my-auto">
<h2 className="text-4xl font-bold font-title text-left leading-normal">
Build your <span className="text-primary">SaaS</span> in the blink of
an eye!
Build your <span className="text-primary">SaaS</span> in the blink of an eye!
</h2>
<p>
SupaNexTail got your back, and takes care of the initial setup,
sometimes time consuming, but essential to your success.
SupaNexTail got your back, and takes care of the initial setup, sometimes time consuming,
but essential to your success.
</p>
</div>
<div className="max-w-xl">
@@ -30,8 +29,8 @@ const Landing = () => (
All you need to start <span className="text-primary">now</span>
</h2>
<p>
SupaNexTail got your back, and takes care of the initial setup,
sometimes time consuming, but essential to your success.
SupaNexTail got your back, and takes care of the initial setup, sometimes time consuming,
but essential to your success.
</p>
</div>
<div className="max-w-xl">
@@ -44,14 +43,12 @@ const Landing = () => (
Leverage the power of <span className="text-primary">Supabase</span>
</h2>
<p>
Supabase is an open source Firebase alternative. Youll have a
database, an auth system, a storage system, and much more in one
product.
Supabase is an open source Firebase alternative. Youll have a database, an auth system, a
storage system, and much more in one product.
</p>
<p>
SupaNexTail uses Supabase at its core, and preconfigures all the
useful elements for your site. User registration, synchronization with
Stripe, weve got you covered!
SupaNexTail uses Supabase at its core, and preconfigures all the useful elements for your
site. User registration, synchronization with Stripe, weve got you covered!
</p>
</div>
<div className="max-w-md order-2 lg:order-1 flex">

View File

@@ -33,23 +33,9 @@ const Layout = (props) => {
return (
<div className="min-h-screen w-full bg-base-100 text-base-content m-auto font-body">
<Head>
<link
rel="apple-touch-icon"
sizes="180x180"
href="/apple-touch-icon.png"
/>
<link
rel="icon"
type="image/png"
sizes="32x32"
href="/favicon-32x32.png"
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="/favicon-16x16.png"
/>
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
<link rel="manifest" href="/site.webmanifest" />
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5" />
<meta name="msapplication-TileColor" content="#da532c" />

View File

@@ -55,8 +55,8 @@ const MailingList = () => {
<Image src={Mailing} />
<label className="label">
<p className="text-center max-w-md m-auto">
Want to be the first to know when SupaNexTail launches and get an
exclusive discount? Sign up for the newsletter!
Want to be the first to know when SupaNexTail launches and get an exclusive discount? Sign
up for the newsletter!
</p>
</label>
<div className="mt-5 m-auto">
@@ -66,15 +66,11 @@ const MailingList = () => {
}}
type="email"
placeholder="Your email"
className={`input input-primary input-bordered ${
valid ? null : 'input-error'
}`}
className={`input input-primary input-bordered ${valid ? null : 'input-error'}`}
/>
<button
onClick={validateEmail}
className={`btn ml-3 ${
loading ? 'btn-disabled loading' : 'btn-primary'
}`}
className={`btn ml-3 ${loading ? 'btn-disabled loading' : 'btn-primary'}`}
>
I'm in!
</button>

View File

@@ -1,74 +0,0 @@
/*
This is your Nav component. It contain a responsive navbar
*/
import { LogOut, Menu } from 'react-feather';
import Image from 'next/image';
import Link from 'next/link';
import Logo from 'public/logo.svg';
const Nav = (props) => {
// Modify you menu directly here
const NavMenu = (
<>
{props.user && (
<Link href="/dashboard">
<a className="nav-btn">Dashboard</a>
</Link>
)}
<Link href="/pricing">
<a className="nav-btn">Pricing</a>
</Link>
<Link href="/contact">
<a className="nav-btn">Contact Us</a>
</Link>
{props.user ? (
<button className="btn btn-xs text-xs" onClick={() => props.signOut()}>
<LogOut size={12} className="mr-2" />
Logout
</button>
) : (
<>
<Link href="/login">
<a className="nav-btn">Login</a>
</Link>
<Link href="/signup">
<a className="btn btn-sm btn-primary font-body normal-case font-normal">
Sign Up
</a>
</Link>
</>
)}
</>
);
return (
<nav className="navbar mb-2 w-full">
<Link href="/">
<a>
<Image src={Logo} />
</a>
</Link>
<div className="hidden lg:flex text-center flex-col lg:flex-row lg:space-x-10 font-body text-sm ml-auto">
{NavMenu}
</div>
<div className="ml-auto lg:hidden">
<div className="dropdown dropdown-end" data-cy="dropdown">
<div tabIndex="0" className="m-1 cursor-pointer">
<Menu />
</div>
<div className="menu dropdown-content mt-3 text-center space-y-3 w-24">
{NavMenu}
</div>
</div>
</div>
</nav>
);
};
export default Nav;

70
components/Nav.tsx Normal file
View File

@@ -0,0 +1,70 @@
/*
This is your Nav component. It contain a responsive navbar
*/
import { LogOut, Menu } from 'react-feather';
import Image from 'next/image';
import Link from 'next/link';
import Logo from 'public/logo.svg';
const Nav = (props: any) => {
// Modify you menu directly here
const NavMenu = (
<>
{props.user && (
<Link href="/dashboard">
<a className="nav-btn">Dashboard</a>
</Link>
)}
<Link href="/pricing">
<a className="nav-btn">Pricing</a>
</Link>
<Link href="/contact">
<a className="nav-btn">Contact Us</a>
</Link>
{props.user ? (
<button className="btn btn-xs text-xs" onClick={() => props.signOut()}>
<LogOut size={12} className="mr-2" />
Logout
</button>
) : (
<>
<Link href="/login">
<a className="nav-btn">Login</a>
</Link>
<Link href="/signup">
<a className="btn btn-sm btn-primary font-body normal-case font-normal">Sign Up</a>
</Link>
</>
)}
</>
);
return (
<nav className="navbar mb-2 w-full">
<Link href="/">
<a>
<Image src={Logo} alt="SupaNexTail Logo" />
</a>
</Link>
<div className="hidden lg:flex text-center flex-col lg:flex-row lg:space-x-10 font-body text-sm ml-auto">
{NavMenu}
</div>
<div className="ml-auto lg:hidden">
<div className="dropdown dropdown-end" data-cy="dropdown">
<div tabIndex={0} className="m-1 cursor-pointer">
<Menu />
</div>
<div className="menu dropdown-content mt-3 text-center space-y-3 w-24">{NavMenu}</div>
</div>
</div>
</nav>
);
};
export default Nav;

View File

@@ -29,10 +29,7 @@ const PaymentModal = (props) => {
</Transition.Child>
{/* This element is to trick the browser into centering the modal contents. */}
<span
className="inline-block h-screen align-middle"
aria-hidden="true"
>
<span className="inline-block h-screen align-middle" aria-hidden="true">
&#8203;
</span>
<Transition.Child
@@ -45,25 +42,15 @@ const PaymentModal = (props) => {
leaveTo="opacity-0 scale-95"
>
<div className="inline-block w-full max-w-lg p-8 my-8 overflow-hidden text-left align-middle transition-all transform shadow-xl rounded-2xl bg-base-100 text-base-content border-2 border-accent-focus">
<Dialog.Title
as="h3"
className="text-2xl font-bold leading-6 mb-5 text-center"
>
<Dialog.Title as="h3" className="text-2xl font-bold leading-6 mb-5 text-center">
Payment successful 🎉
</Dialog.Title>
<div className="mt-2">
<p>
Your payment has been successfully submitted. Thank you for
your support!
</p>
<p>Your payment has been successfully submitted. Thank you for your support!</p>
</div>
<div className="mt-4">
<button
type="button"
className="btn btn-accent flex m-auto"
onClick={closeModal}
>
<button type="button" className="btn btn-accent flex m-auto" onClick={closeModal}>
Got it, thanks!
</button>
</div>

View File

@@ -81,20 +81,15 @@ const Pricing = () => {
return (
<div className="w-full mx-auto px-5 py-10 mb-10">
<div className="text-center max-w-xl mx-auto">
<h1 className="text-3xl sm:text-5xl font-bold font-title mb-5">
Pricing
</h1>
<h1 className="text-3xl sm:text-5xl font-bold font-title mb-5">Pricing</h1>
<h3 className="text-lg font-light leading-8 p-3 mb-5">
This is an example of a pricing page. You can choose a payment method,
monthly or yearly.
This is an example of a pricing page. You can choose a payment method, monthly or yearly.
</h3>
</div>
{!flat && (
<div className="flex justify-between max-w-xs m-auto mb-3">
<div>
<p className={`${enabled ? 'text-gray-500' : null}`}>
Billed monthly
</p>
<p className={`${enabled ? 'text-gray-500' : null}`}>Billed monthly</p>
</div>
<div>
<Switch
@@ -114,9 +109,7 @@ const Pricing = () => {
</Switch>
</div>
<div>
<p className={`${!enabled ? 'text-gray-500' : null}`}>
Billed annually
</p>
<p className={`${!enabled ? 'text-gray-500' : null}`}>Billed annually</p>
</div>
</div>
)}
@@ -152,9 +145,7 @@ const Pricing = () => {
: (e) =>
handleSubmit(
e,
enabled
? Prices.personal.annually.id
: Prices.personal.monthly.id
enabled ? Prices.personal.annually.id : Prices.personal.monthly.id
)
: () => {
router.push('/auth');
@@ -169,11 +160,7 @@ const Pricing = () => {
<div className="w-full flex-grow">
<h2 className="text-center font-bold uppercase mb-4">Team</h2>
<h3 className="text-center font-bold text-4xl mb-5">
{flat
? pricing.flat.team
: enabled
? pricing.yearly.team
: pricing.monthly.team}
{flat ? pricing.flat.team : enabled ? pricing.yearly.team : pricing.monthly.team}
</h3>
<ul className="text-sm px-5 mb-8 text-left">
<li className="leading-tight">
@@ -203,12 +190,7 @@ const Pricing = () => {
portal();
}
: (e) =>
handleSubmit(
e,
enabled
? Prices.team.annually.id
: Prices.team.monthly.id
)
handleSubmit(e, enabled ? Prices.team.annually.id : Prices.team.monthly.id)
: () => {
router.push('/auth');
}
@@ -222,11 +204,7 @@ const Pricing = () => {
<div className="w-full flex-grow">
<h2 className="text-center font-bold uppercase mb-4">Pro</h2>
<h3 className="text-center font-bold text-4xl mb-5">
{flat
? pricing.flat.pro
: enabled
? pricing.yearly.pro
: pricing.monthly.pro}
{flat ? pricing.flat.pro : enabled ? pricing.yearly.pro : pricing.monthly.pro}
</h3>
<ul className="text-sm px-5 mb-8 text-left">
<li className="leading-tight">
@@ -256,12 +234,7 @@ const Pricing = () => {
portal();
}
: (e) =>
handleSubmit(
e,
enabled
? Prices.pro.annually.id
: Prices.pro.monthly.id
)
handleSubmit(e, enabled ? Prices.pro.annually.id : Prices.pro.monthly.id)
: () => {
router.push('/auth');
}

View File

@@ -1,66 +1,52 @@
const PrivacyPolicy = () => (
<div className="max-w-xl text-left m-auto py-10">
<h1 className="text-center">
Privacy Policy for {process.env.NEXT_PUBLIC_TITLE}
</h1>
<h1 className="text-center">Privacy Policy for {process.env.NEXT_PUBLIC_TITLE}</h1>
<p>
At {process.env.NEXT_PUBLIC_TITLE}, accessible from
https://www.supanextail.dev, one of our main priorities is the privacy of
our visitors. This Privacy Policy document contains types of information
that is collected and recorded by
At {process.env.NEXT_PUBLIC_TITLE}, accessible from https://www.supanextail.dev, one of our
main priorities is the privacy of our visitors. This Privacy Policy document contains types of
information that is collected and recorded by
{process.env.NEXT_PUBLIC_TITLE} and how we use it.
</p>
<p>
If you have additional questions or require more information about our
Privacy Policy, do not hesitate to contact us.
If you have additional questions or require more information about our Privacy Policy, do not
hesitate to contact us.
</p>
<h2>General Data Protection Regulation (GDPR)</h2>
<p>We are a Data Controller of your information.</p>
<p>
{process.env.NEXT_PUBLIC_TITLE} legal basis for collecting and using the
personal information described in this Privacy Policy depends on the
Personal Information we collect and the specific context in which we
collect the information:
{process.env.NEXT_PUBLIC_TITLE} legal basis for collecting and using the personal information
described in this Privacy Policy depends on the Personal Information we collect and the
specific context in which we collect the information:
</p>
<ul>
<li>{process.env.NEXT_PUBLIC_TITLE} needs to perform a contract with you</li>
<li>You have given {process.env.NEXT_PUBLIC_TITLE} permission to do so</li>
<li>
{process.env.NEXT_PUBLIC_TITLE} needs to perform a contract with you
</li>
<li>
You have given {process.env.NEXT_PUBLIC_TITLE} permission to do so
</li>
<li>
Processing your personal information is in{' '}
{process.env.NEXT_PUBLIC_TITLE} legitimate interests
Processing your personal information is in {process.env.NEXT_PUBLIC_TITLE} legitimate
interests
</li>
<li>{process.env.NEXT_PUBLIC_TITLE} needs to comply with the law</li>
</ul>
<p>
{process.env.NEXT_PUBLIC_TITLE} will retain your personal information only
for as long as is necessary for the purposes set out in this Privacy
Policy. We will retain and use your information to the extent necessary to
comply with our legal obligations, resolve disputes, and enforce our
policies.
{process.env.NEXT_PUBLIC_TITLE} will retain your personal information only for as long as is
necessary for the purposes set out in this Privacy Policy. We will retain and use your
information to the extent necessary to comply with our legal obligations, resolve disputes,
and enforce our policies.
</p>
<p>
If you are a resident of the European Economic Area (EEA), you have
certain data protection rights. If you wish to be informed what Personal
Information we hold about you and if you want it to be removed from our
systems, please contact us.
</p>
<p>
In certain circumstances, you have the following data protection rights:
If you are a resident of the European Economic Area (EEA), you have certain data protection
rights. If you wish to be informed what Personal Information we hold about you and if you want
it to be removed from our systems, please contact us.
</p>
<p>In certain circumstances, you have the following data protection rights:</p>
<ul>
<li>
The right to access, update or to delete the information we have on you.
</li>
<li>The right to access, update or to delete the information we have on you.</li>
<li>The right of rectification.</li>
<li>The right to object.</li>
<li>The right of restriction.</li>
@@ -71,88 +57,78 @@ const PrivacyPolicy = () => (
<h2>Log Files</h2>
<p>
{process.env.NEXT_PUBLIC_TITLE} follows a standard procedure of using log
files. These files log visitors when they visit websites. All hosting
companies do this and a part of hosting services' analytics. The
information collected by log files include internet protocol (IP)
addresses, browser type, Internet Service Provider (ISP), date and time
stamp, referring/exit pages, and possibly the number of clicks. These are
not linked to any information that is personally identifiable. The purpose
of the information is for analyzing trends, administering the site,
tracking users' movement on the website, and gathering demographic
information.
{process.env.NEXT_PUBLIC_TITLE} follows a standard procedure of using log files. These files
log visitors when they visit websites. All hosting companies do this and a part of hosting
services' analytics. The information collected by log files include internet protocol (IP)
addresses, browser type, Internet Service Provider (ISP), date and time stamp, referring/exit
pages, and possibly the number of clicks. These are not linked to any information that is
personally identifiable. The purpose of the information is for analyzing trends, administering
the site, tracking users' movement on the website, and gathering demographic information.
</p>
<h2>Privacy Policies</h2>
<p>
You may consult this list to find the Privacy Policy for each of the
advertising partners of {process.env.NEXT_PUBLIC_TITLE}.
You may consult this list to find the Privacy Policy for each of the advertising partners of{' '}
{process.env.NEXT_PUBLIC_TITLE}.
</p>
<p>
Third-party ad servers or ad networks uses technologies like cookies,
JavaScript, or Web Beacons that are used in their respective
advertisements and links that appear on {process.env.NEXT_PUBLIC_TITLE},
which are sent directly to users' browser. They automatically receive your
IP address when this occurs. These technologies are used to measure the
effectiveness of their advertising campaigns and/or to personalize the
advertising content that you see on websites that you visit.
Third-party ad servers or ad networks uses technologies like cookies, JavaScript, or Web
Beacons that are used in their respective advertisements and links that appear on{' '}
{process.env.NEXT_PUBLIC_TITLE}, which are sent directly to users' browser. They automatically
receive your IP address when this occurs. These technologies are used to measure the
effectiveness of their advertising campaigns and/or to personalize the advertising content
that you see on websites that you visit.
</p>
<p>
Note that {process.env.NEXT_PUBLIC_TITLE} has no access to or control over
these cookies that are used by third-party advertisers.
Note that {process.env.NEXT_PUBLIC_TITLE} has no access to or control over these cookies that
are used by third-party advertisers.
</p>
<h2>Third Party Privacy Policies</h2>
<p>
{process.env.NEXT_PUBLIC_TITLE}'s Privacy Policy does not apply to other
advertisers or websites. Thus, we are advising you to consult the
respective Privacy Policies of these third-party ad servers for more
detailed information. It may include their practices and instructions
about how to opt-out of certain options.{' '}
{process.env.NEXT_PUBLIC_TITLE}'s Privacy Policy does not apply to other advertisers or
websites. Thus, we are advising you to consult the respective Privacy Policies of these
third-party ad servers for more detailed information. It may include their practices and
instructions about how to opt-out of certain options.{' '}
</p>
<p>
You can choose to disable cookies through your individual browser options.
To know more detailed information about cookie management with specific
web browsers, it can be found at the browsers' respective websites.
You can choose to disable cookies through your individual browser options. To know more
detailed information about cookie management with specific web browsers, it can be found at
the browsers' respective websites.
</p>
<h2>Children's Information</h2>
<p>
Another part of our priority is adding protection for children while using
the internet. We encourage parents and guardians to observe, participate
in, and/or monitor and guide their online activity.
Another part of our priority is adding protection for children while using the internet. We
encourage parents and guardians to observe, participate in, and/or monitor and guide their
online activity.
</p>
<p>
{process.env.NEXT_PUBLIC_TITLE} does not knowingly collect any Personal
Identifiable Information from children under the age of 13. If you think
that your child provided this kind of information on our website, we
strongly encourage you to contact us immediately and we will do our best
efforts to promptly remove such information from our records.
{process.env.NEXT_PUBLIC_TITLE} does not knowingly collect any Personal Identifiable
Information from children under the age of 13. If you think that your child provided this kind
of information on our website, we strongly encourage you to contact us immediately and we will
do our best efforts to promptly remove such information from our records.
</p>
<h2>Online Privacy Policy Only</h2>
<p>
Our Privacy Policy applies only to our online activities and is valid for
visitors to our website with regards to the information that they shared
and/or collect in {process.env.NEXT_PUBLIC_TITLE}. This policy is not
applicable to any information collected offline or via channels other than
this website.
Our Privacy Policy applies only to our online activities and is valid for visitors to our
website with regards to the information that they shared and/or collect in{' '}
{process.env.NEXT_PUBLIC_TITLE}. This policy is not applicable to any information collected
offline or via channels other than this website.
</p>
<h2>Consent</h2>
<p>
By using our website, you hereby consent to our Privacy Policy and agree
to its terms.
</p>
<p>By using our website, you hereby consent to our Privacy Policy and agree to its terms.</p>
</div>
);

View File

@@ -3,231 +3,197 @@ const Terms = () => (
<h1>Terms and Conditions</h1>
<p>
The following terms and conditions (collectively, these "Terms and
Conditions") apply to your use of{' '}
<span className="website_url">https://www.supanextail.dev</span>,
including any content, functionality and services offered on or via{' '}
<span className="website_url">https://www.supanextail.dev</span> (the
"Website").
The following terms and conditions (collectively, these "Terms and Conditions") apply to your
use of <span className="website_url">https://www.supanextail.dev</span>, including any
content, functionality and services offered on or via{' '}
<span className="website_url">https://www.supanextail.dev</span> (the "Website").
</p>
<p>
Please read the Terms and Conditions carefully before you start using{' '}
<span className="website_name">{process.env.NEXT_PUBLIC_TITLE}</span>,
because by using the Website you accept and agree to be bound and abide by
these Terms and Conditions.
<span className="website_name">{process.env.NEXT_PUBLIC_TITLE}</span>, because by using the
Website you accept and agree to be bound and abide by these Terms and Conditions.
</p>
<p>
These Terms and Conditions are effective as of{' '}
<span className="date">06/22/2021</span>. We expressly reserve the right
to change these Terms and Conditions from time to time without notice to
you. You acknowledge and agree that it is your responsibility to review
this Website and these Terms and Conditions from time to time and to
familiarize yourself with any modifications. Your continued use of this
Website after such modifications will constitute acknowledgement of the
modified Terms and Conditions and agreement to abide and be bound by the
modified Terms and Conditions.
These Terms and Conditions are effective as of <span className="date">06/22/2021</span>. We
expressly reserve the right to change these Terms and Conditions from time to time without
notice to you. You acknowledge and agree that it is your responsibility to review this Website
and these Terms and Conditions from time to time and to familiarize yourself with any
modifications. Your continued use of this Website after such modifications will constitute
acknowledgement of the modified Terms and Conditions and agreement to abide and be bound by
the modified Terms and Conditions.
</p>
<h2>Conduct on Website</h2>
<p>
Your use of the Website is subject to all applicable laws and regulations,
and you are solely responsible for the substance of your communications
through the Website.
Your use of the Website is subject to all applicable laws and regulations, and you are solely
responsible for the substance of your communications through the Website.
</p>
<p>
By posting information in or otherwise using any communications service,
chat room, message board, newsgroup, software library, or other
interactive service that may be available to you on or through this
Website, you agree that you will not upload, share, post, or otherwise
distribute or facilitate distribution of any content including text,
communications, software, images, sounds, data, or other information
that:
By posting information in or otherwise using any communications service, chat room, message
board, newsgroup, software library, or other interactive service that may be available to you
on or through this Website, you agree that you will not upload, share, post, or otherwise
distribute or facilitate distribution of any content including text, communications,
software, images, sounds, data, or other information that:
</p>
<ul>
<li>
Is unlawful, threatening, abusive, harassing, defamatory, libelous,
deceptive, fraudulent, invasive of another's privacy, tortious, contains
explicit or graphic descriptions or accounts of sexual acts (including
but not limited to sexual language of a violent or threatening nature
directed at another individual or group of individuals), or otherwise
Is unlawful, threatening, abusive, harassing, defamatory, libelous, deceptive, fraudulent,
invasive of another's privacy, tortious, contains explicit or graphic descriptions or
accounts of sexual acts (including but not limited to sexual language of a violent or
threatening nature directed at another individual or group of individuals), or otherwise
violates our rules or policies
</li>
<li>
Victimizes, harasses, degrades, or intimidates an individual or group of
individuals on the basis of religion, gender, sexual orientation, race,
ethnicity, age, or disability
Victimizes, harasses, degrades, or intimidates an individual or group of individuals on the
basis of religion, gender, sexual orientation, race, ethnicity, age, or disability
</li>
<li>
Infringes on any patent, trademark, trade secret, copyright, right of
publicity, or other proprietary right of any party
Infringes on any patent, trademark, trade secret, copyright, right of publicity, or other
proprietary right of any party
</li>
<li>
Constitutes unauthorized or unsolicited advertising, junk or bulk email
(also known as "spamming"), chain letters, any other form of
unauthorized solicitation, or any form of lottery or gambling
Constitutes unauthorized or unsolicited advertising, junk or bulk email (also known as
"spamming"), chain letters, any other form of unauthorized solicitation, or any form of
lottery or gambling
</li>
<li>
Contains software viruses or any other computer code, files, or programs
that are designed or intended to disrupt, damage, or limit the
functioning of any software, hardware, or telecommunications equipment
or to damage or obtain unauthorized access to any data or other
Contains software viruses or any other computer code, files, or programs that are designed
or intended to disrupt, damage, or limit the functioning of any software, hardware, or
telecommunications equipment or to damage or obtain unauthorized access to any data or other
information of any third party
</li>
<li>
Impersonates any person or entity, including any of our employees or
representatives
</li>
<li>Impersonates any person or entity, including any of our employees or representatives</li>
</ul>
<p>
We neither endorse nor assume any liability for the contents of any
material uploaded or submitted by third party users of the Website. We
generally do not pre-screen, monitor, or edit the content posted by users
of communications services, chat rooms, message boards, newsgroups,
software libraries, or other interactive services that may be available on
or through this Website.
We neither endorse nor assume any liability for the contents of any material uploaded or
submitted by third party users of the Website. We generally do not pre-screen, monitor, or
edit the content posted by users of communications services, chat rooms, message boards,
newsgroups, software libraries, or other interactive services that may be available on or
through this Website.
</p>
<p>
However, we and our agents have the right at their sole discretion to
remove any content that, in our judgment, does not comply with these Terms
of Use and any other rules of user conduct for our Website, or is
otherwise harmful, objectionable, or inaccurate. We are not responsible
for any failure or delay in removing such content. You hereby consent to
such removal and waive any claim against us arising out of such removal of
content.
However, we and our agents have the right at their sole discretion to remove any content that,
in our judgment, does not comply with these Terms of Use and any other rules of user conduct
for our Website, or is otherwise harmful, objectionable, or inaccurate. We are not responsible
for any failure or delay in removing such content. You hereby consent to such removal and
waive any claim against us arising out of such removal of content.
</p>
<p>
You agree that we may at any time, and at our sole discretion, terminate
your membership, account, or other affiliation with our site without prior
notice to you for violating any of the above provisions.
You agree that we may at any time, and at our sole discretion, terminate your membership,
account, or other affiliation with our site without prior notice to you for violating any of
the above provisions.
</p>
<p>
In addition, you acknowledge that we will cooperate fully with
investigations of violations of systems or network security at other
sites, including cooperating with law enforcement authorities in
investigating suspected criminal violations.
In addition, you acknowledge that we will cooperate fully with investigations of violations of
systems or network security at other sites, including cooperating with law enforcement
authorities in investigating suspected criminal violations.
</p>
<h2>Intellectual Property</h2>
<p>
By accepting these Terms and Conditions, you acknowledge and agree that
all content presented to you on this Website is protected by copyrights,
trademarks, service marks, patents or other proprietary rights and laws,
and is the sole property of{' '}
By accepting these Terms and Conditions, you acknowledge and agree that all content presented
to you on this Website is protected by copyrights, trademarks, service marks, patents or other
proprietary rights and laws, and is the sole property of{' '}
<span className="website_name">{process.env.NEXT_PUBLIC_TITLE}</span>.
</p>
<p>
You are only permitted to use the content as expressly authorized by us or
the specific content provider. Except for a single copy made for personal
use only, you may not copy, reproduce, modify, republish, upload, post,
transmit, or distribute any documents or information from this Website in
any form or by any means without prior written permission from us or the
specific content provider, and you are solely responsible for obtaining
permission before reusing any copyrighted material that is available on
this Website.
You are only permitted to use the content as expressly authorized by us or the specific
content provider. Except for a single copy made for personal use only, you may not copy,
reproduce, modify, republish, upload, post, transmit, or distribute any documents or
information from this Website in any form or by any means without prior written permission
from us or the specific content provider, and you are solely responsible for obtaining
permission before reusing any copyrighted material that is available on this Website.
</p>
<h2>Third Party Websites</h2>
<p>
This Website may link you to other sites on the Internet or otherwise
include references to information, documents, software, materials and/or
services provided by other parties. These websites may contain information
or material that some people may find inappropriate or offensive.
This Website may link you to other sites on the Internet or otherwise include references to
information, documents, software, materials and/or services provided by other parties. These
websites may contain information or material that some people may find inappropriate or
offensive.
</p>
<p>
These other websites and parties are not under our control, and you
acknowledge that we are not responsible for the accuracy, copyright
compliance, legality, decency, or any other aspect of the content of such
sites, nor are we responsible for errors or omissions in any references to
other parties or their products and services. The inclusion of such a link
or reference is provided merely as a convenience and does not imply
endorsement of, or association with, the Website or party by us, or any
warranty of any kind, either express or implied.
These other websites and parties are not under our control, and you acknowledge that we are
not responsible for the accuracy, copyright compliance, legality, decency, or any other aspect
of the content of such sites, nor are we responsible for errors or omissions in any references
to other parties or their products and services. The inclusion of such a link or reference is
provided merely as a convenience and does not imply endorsement of, or association with, the
Website or party by us, or any warranty of any kind, either express or implied.
</p>
<h2>
Disclaimer of Warranties, Limitations of Liability and Indemnification
</h2>
<h2>Disclaimer of Warranties, Limitations of Liability and Indemnification</h2>
<p>
Your use of{' '}
<span className="website_name">{process.env.NEXT_PUBLIC_TITLE}</span> is
at your sole risk. The Website is provided "as is" and "as available". We
disclaim all warranties of any kind, express or implied, including,
without limitation, the warranties of merchantability, fitness for a
particular purpose and non-infringement.
Your use of <span className="website_name">{process.env.NEXT_PUBLIC_TITLE}</span> is at your
sole risk. The Website is provided "as is" and "as available". We disclaim all warranties of
any kind, express or implied, including, without limitation, the warranties of
merchantability, fitness for a particular purpose and non-infringement.
</p>
<p>
We are not liable for damages, direct or consequential, resulting from
your use of the Website, and you agree to defend, indemnify and hold us
harmless from any claims, losses, liability costs and expenses (including
but not limites to attorney's fees) arising from your violation of any
third-party's rights. You acknowledge that you have only a limited,
non-exclusive, nontransferable license to use the Website. Because the
Website is not error or bug free, you agree that you will use it carefully
and avoid using it ways which might result in any loss of your or any
third party's property or information.
We are not liable for damages, direct or consequential, resulting from your use of the
Website, and you agree to defend, indemnify and hold us harmless from any claims, losses,
liability costs and expenses (including but not limites to attorney's fees) arising from your
violation of any third-party's rights. You acknowledge that you have only a limited,
non-exclusive, nontransferable license to use the Website. Because the Website is not error or
bug free, you agree that you will use it carefully and avoid using it ways which might result
in any loss of your or any third party's property or information.
</p>
<h2>Term and termination</h2>
<p>
This Terms and Conditions will become effective in relation to you when
you create a{' '}
<span className="website_name">{process.env.NEXT_PUBLIC_TITLE}</span>{' '}
account or when you start using the{' '}
<span className="website_name">{process.env.NEXT_PUBLIC_TITLE}</span> and
will remain effective until terminated by you or by us.{' '}
This Terms and Conditions will become effective in relation to you when you create a{' '}
<span className="website_name">{process.env.NEXT_PUBLIC_TITLE}</span> account or when you
start using the <span className="website_name">{process.env.NEXT_PUBLIC_TITLE}</span> and will
remain effective until terminated by you or by us.{' '}
</p>
<p>
<span className="website_name">{process.env.NEXT_PUBLIC_TITLE}</span>{' '}
reserves the right to terminate this Terms and Conditions or suspend your
account at any time in case of unauthorized, or suspected unauthorized use
of the Website whether in contravention of this Terms and Conditions or
otherwise. If{' '}
<span className="website_name">{process.env.NEXT_PUBLIC_TITLE}</span>{' '}
terminates this Terms and Conditions, or suspends your account for any of
the reasons set out in this section,{' '}
<span className="website_name">{process.env.NEXT_PUBLIC_TITLE}</span>{' '}
shall have no liability or responsibility to you.
<span className="website_name">{process.env.NEXT_PUBLIC_TITLE}</span> reserves the right to
terminate this Terms and Conditions or suspend your account at any time in case of
unauthorized, or suspected unauthorized use of the Website whether in contravention of this
Terms and Conditions or otherwise. If{' '}
<span className="website_name">{process.env.NEXT_PUBLIC_TITLE}</span> terminates this Terms
and Conditions, or suspends your account for any of the reasons set out in this section,{' '}
<span className="website_name">{process.env.NEXT_PUBLIC_TITLE}</span> shall have no liability
or responsibility to you.
</p>
<h2>Assignment</h2>
<p>
<span className="website_name">{process.env.NEXT_PUBLIC_TITLE}</span> may
assign this Terms and Conditions or any part of it without restrictions.
You may not assign this Terms and Conditions or any part of it to any
third party.
<span className="website_name">{process.env.NEXT_PUBLIC_TITLE}</span> may assign this Terms
and Conditions or any part of it without restrictions. You may not assign this Terms and
Conditions or any part of it to any third party.
</p>
<h2>Governing Law</h2>
<p>
These Terms and Conditions and any dispute or claim arising out of, or
related to them, shall be governed by and construed in accordance with the
internal laws of the <span className="country">fr</span> without giving
effect to any choice or conflict of law provision or rule.
These Terms and Conditions and any dispute or claim arising out of, or related to them, shall
be governed by and construed in accordance with the internal laws of the{' '}
<span className="country">fr</span> without giving effect to any choice or conflict of law
provision or rule.
</p>
<p>
Any legal suit, action or proceeding arising out of, or related to, these
Terms of Service or the Website shall be instituted exclusively in the
federal courts of <span className="country">fr</span>.
Any legal suit, action or proceeding arising out of, or related to, these Terms of Service or
the Website shall be instituted exclusively in the federal courts of{' '}
<span className="country">fr</span>.
</p>
</div>
);

View File

@@ -39,9 +39,7 @@ const Login = (props) => {
<div className="p-10 bg-base-100 md:flex-1 rounded-md text-base-content shadow-md max-w-sm font-body">
{!forgot && (
<>
<h3 className="my-4 text-2xl font-semibold font-title">
Account Login
</h3>
<h3 className="my-4 text-2xl font-semibold font-title">Account Login</h3>
<form action="#" className="flex flex-col space-y-5">
<div className="flex flex-col space-y-1">
<label htmlFor="email" className="text-sm">
@@ -111,9 +109,7 @@ const Login = (props) => {
<div className="text-base-content">
<IoLogoGoogle />
</div>
<span className="text-sm font-medium text-base-content">
Gmail
</span>
<span className="text-sm font-medium text-base-content">Gmail</span>
</button>
</div>
</div>
@@ -125,10 +121,7 @@ const Login = (props) => {
<h3 className="my-4 text-2xl font-semibold">Password recovery</h3>
<form action="#" className="flex flex-col space-y-5">
<div className="flex flex-col space-y-1">
<label
htmlFor="email"
className="text-sm font-semibold text-gray-500"
>
<label htmlFor="email" className="text-sm font-semibold text-gray-500">
Email address
</label>
<input

View File

@@ -39,9 +39,7 @@ const SignUpPanel = (props) => {
<div className="p-10 bg-base-100 md:flex-1 rounded-md text-base-content shadow-md max-w-sm font-body">
{!forgot && (
<>
<h3 className="my-4 text-2xl font-semibold font-title">
Account Sign Up
</h3>
<h3 className="my-4 text-2xl font-semibold font-title">Account Sign Up</h3>
<form action="#" className="flex flex-col space-y-5">
<div className="flex flex-col space-y-1">
<label htmlFor="email" className="text-sm">
@@ -83,9 +81,7 @@ const SignUpPanel = (props) => {
<div className="flex flex-col space-y-5">
<span className="flex items-center justify-center space-x-2">
<span className="h-px bg-gray-400 w-14" />
<span className="font-normal text-gray-500">
or sign up with
</span>
<span className="font-normal text-gray-500">or sign up with</span>
<span className="h-px bg-gray-400 w-14" />
</span>
<div className="flex flex-col space-y-4">
@@ -100,9 +96,7 @@ const SignUpPanel = (props) => {
<div className="text-base-content">
<IoLogoGoogle />
</div>
<span className="text-sm font-medium text-base-content">
Gmail
</span>
<span className="text-sm font-medium text-base-content">Gmail</span>
</button>
</div>
</div>

View File

@@ -1,5 +0,0 @@
{
"compilerOptions": {
"baseUrl": "."
}
}

3
next-env.d.ts vendored Normal file
View File

@@ -0,0 +1,3 @@
/// <reference types="next" />
/// <reference types="next/types/global" />
/// <reference types="next/image-types/global" />

8294
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -16,6 +16,10 @@
"@stripe/stripe-js": "^1.15.1",
"@supabase/grid": "1.15.0",
"@supabase/supabase-js": "^1.21.0",
"@types/node": "^16.4.13",
"@types/react": "^17.0.16",
"@types/react-dom": "^17.0.9",
"@types/stripe": "^8.0.417",
"axios": "^0.21.1",
"cors": "^2.8.5",
"daisyui": "^1.11.0",
@@ -28,10 +32,14 @@
"react-feather": "^2.0.9",
"react-icons": "^4.2.0",
"react-toastify": "^7.0.4",
"stripe": "^8.168.0"
"stripe": "^8.168.0",
"typescript": "^4.3.5"
},
"devDependencies": {
"@next/eslint-plugin-next": "^11.0.1",
"@types/cors": "^2.8.12",
"@types/express-rate-limit": "^5.1.3",
"@typescript-eslint/eslint-plugin": "^4.29.1",
"autoprefixer": "^10.3.1",
"cypress": "^8.2.0",
"eslint": "^7.32.0",

View File

@@ -1,58 +0,0 @@
import Cors from 'cors';
import axios from 'axios';
import initMiddleware from 'utils/init-middleware';
const rateLimit = require('express-rate-limit');
export const config = {
api: {
externalResolver: true,
},
};
const cors = initMiddleware(
Cors({
methods: ['PUT'],
})
);
const limiter = initMiddleware(
rateLimit({
windowMs: 30000, // 30sec
max: 2, // Max 2 request per 30 sec
})
);
export default async function handler(req, res) {
await cors(req, res);
await limiter(req, res);
if (req.method === 'PUT') {
axios
.put(
'https://api.sendgrid.com/v3/marketing/contacts',
{
contacts: [{ email: `${req.body.mail}` }],
list_ids: [process.env.SENDGRID_MAILING_ID],
},
{
headers: {
'content-type': 'application/json',
Authorization: `Bearer ${process.env.SENDGRID_SECRET}`,
},
}
)
.then((result) => {
console.log(result);
res.status(200).send({
message:
'Your email has been succesfully added to the mailing list. Welcome 👋',
});
})
.catch((err) => {
res.status(500).send({
message:
'Oups, there was a problem with your subscription, please try again or contact us',
});
});
}
}

56
pages/api/mailingList.ts Normal file
View File

@@ -0,0 +1,56 @@
import Cors from 'cors';
import axios from 'axios';
import initMiddleware from 'utils/init-middleware';
import rateLimit from 'express-rate-limit';
export const config = {
api: {
externalResolver: true,
},
};
const cors = initMiddleware(
Cors({
methods: ['PUT'],
})
);
const limiter = initMiddleware(
rateLimit({
windowMs: 30000, // 30sec
max: 2, // Max 2 request per 30 sec
})
);
export default async function handler(req: any, res: any) {
await cors(req, res);
await limiter(req, res);
if (req.method === 'PUT') {
axios
.put(
'https://api.sendgrid.com/v3/marketing/contacts',
{
contacts: [{ email: `${req.body.mail}` }],
list_ids: [process.env.SENDGRID_MAILING_ID],
},
{
headers: {
'content-type': 'application/json',
Authorization: `Bearer ${process.env.SENDGRID_SECRET}`,
},
}
)
.then((result) => {
console.log(result);
res.status(200).send({
message: 'Your email has been succesfully added to the mailing list. Welcome 👋',
});
})
.catch((err) => {
res.status(500).send({
message:
'Oups, there was a problem with your subscription, please try again or contact us',
});
});
}
}

View File

@@ -2,7 +2,8 @@
This is a simple contact form for SupaNexTail
Using Sendgrid.
*/
const sgMail = require('@sendgrid/mail');
import sgMail from '@sendgrid/mail';
export default async function handler(req, res) {
if (req.method === 'POST') {
@@ -17,9 +18,7 @@ export default async function handler(req, res) {
sgMail
.send(msg)
.then(() => {
res
.status(200)
.send({ message: 'Your email has been sent', success: true });
res.status(200).send({ message: 'Your email has been sent', success: true });
})
.catch((error) => {
console.error(error);

View File

@@ -1,7 +1,8 @@
import * as Stripe from 'stripe';
import Cors from 'cors';
import initMiddleware from 'utils/init-middleware';
const rateLimit = require('express-rate-limit');
import rateLimit from 'express-rate-limit';
const cors = initMiddleware(
Cors({
@@ -17,7 +18,7 @@ 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 = require('stripe')(process.env.STRIPE_SECRET);
const stripe = new Stripe(process.env.STRIPE_SECRET);
export default async function handler(req, res) {
await cors(req, res);
@@ -33,7 +34,10 @@ export default async function handler(req, res) {
mode: req.body.pay_mode,
payment_method_types: ['card'],
client_reference_id: req.body.userId,
metadata: { token: req.body.tokenId, priceId: req.body.priceId },
metadata: {
token: req.body.tokenId,
priceId: req.body.priceId,
},
customer: req.body.customerId,
line_items: [
{
@@ -53,7 +57,10 @@ export default async function handler(req, res) {
payment_method_types: ['card'],
customer_email: req.body.email,
client_reference_id: req.body.userId,
metadata: { token: req.body.tokenId, priceId: req.body.priceId },
metadata: {
token: req.body.tokenId,
priceId: req.body.priceId,
},
line_items: [
{
price: priceId,

View File

@@ -1,10 +1,11 @@
/* Dont forget to create your customer portal on Stripe
https://dashboard.stripe.com/test/settings/billing/portal */
import * as Stripe from 'stripe';
import Cors from 'cors';
import initMiddleware from 'utils/init-middleware';
const rateLimit = require('express-rate-limit');
import rateLimit from 'express-rate-limit';
const cors = initMiddleware(
Cors({
@@ -20,7 +21,7 @@ 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 = require('stripe')(process.env.STRIPE_SECRET);
const stripe = new Stripe(process.env.STRIPE_SECRET);
export default async function handler(req, res) {
await cors(req, res);

View File

@@ -6,12 +6,13 @@ 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 Cors from 'cors';
import { buffer } from 'micro';
import { createClient } from '@supabase/supabase-js';
import initMiddleware from 'utils/init-middleware';
const rateLimit = require('express-rate-limit');
import rateLimit from 'express-rate-limit';
export const config = {
api: {
@@ -28,10 +29,7 @@ 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)
@@ -43,7 +41,8 @@ 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 = require('stripe')(process.env.STRIPE_SECRET);
const stripe = new Stripe(process.env.STRIPE_SECRET);
export default async function handler(req, res) {
await cors(req, res);
@@ -64,9 +63,7 @@ export default async function handler(req, res) {
} catch (err) {
console.log(err);
console.log(`⚠️ Webhook signature verification failed.`);
console.log(
`⚠️ Check the env file and enter the correct webhook secret.`
);
console.log(`⚠️ Check the env file and enter the correct webhook secret.`);
return res.send(400);
}
// Extract the object from the event.

View File

@@ -1,11 +1,13 @@
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 { createClient } from '@supabase/supabase-js';
import { useRouter } from 'next/router';
import { supabase } from '../utils/supabaseClient';
import Dashboard from '../components/Dashboard';
import { useRouter } from 'next/router';
const DashboardPage = ({ user, plan, profile }) => {
const [session, setSession] = useState(null);
@@ -37,12 +39,7 @@ 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}
/>
<Dashboard key={user.id} session={session} plan={plan} profile={profile} />
</>
)}
</Layout>
@@ -55,7 +52,7 @@ export async function getServerSideProps({ req }) {
process.env.SUPABASE_ADMIN_KEY
);
const { user } = await supabaseAdmin.auth.api.getUserByCookie(req);
const stripe = require('stripe')(process.env.STRIPE_SECRET);
const stripe = new Stripe(process.env.STRIPE_SECRET);
// If the user exist, you will retrieve the user profile and if he/she's a paid user
if (user) {
@@ -66,9 +63,7 @@ export async function getServerSideProps({ req }) {
.single();
// Check the subscription plan. If it doesnt exist, return null
const subscription = plan?.plan
? await stripe.subscriptions.retrieve(plan.plan)
: null;
const subscription = plan?.plan ? await stripe.subscriptions.retrieve(plan.plan) : null;
const { data: profile, errorProfile } = await supabaseAdmin
.from('profiles')

View File

@@ -18,10 +18,7 @@ const Home = () => (
<meta property="og:url" content="https://supanextail.dev/" />
<meta property="og:type" content="website" />
<meta
property="og:title"
content={`Welcome to ${process.env.NEXT_PUBLIC_TITLE} 👋`}
/>
<meta property="og:title" content={`Welcome to ${process.env.NEXT_PUBLIC_TITLE} 👋`} />
<meta
property="og:description"
content="SupaNexTail is a boilerplate for your SaaS, based on Next.js, Supabase, and TailwindCSS"
@@ -30,22 +27,13 @@ const Home = () => (
<meta name="twitter:card" content="summary_large_image" />
<meta property="twitter:domain" content="supanextail.dev" />
<meta
property="twitter:url"
content="https://supanextail.dev/ogimage.png"
/>
<meta
name="twitter:title"
content={`Welcome to ${process.env.NEXT_PUBLIC_TITLE} 👋`}
/>
<meta property="twitter:url" content="https://supanextail.dev/ogimage.png" />
<meta name="twitter:title" content={`Welcome to ${process.env.NEXT_PUBLIC_TITLE} 👋`} />
<meta
name="twitter:description"
content="SupaNexTail is a boilerplate for your SaaS, based on Next.js, Supabase, and TailwindCSS"
/>
<meta
name="twitter:image"
content="https://supanextail.dev/ogimage.png"
/>
<meta name="twitter:image" content="https://supanextail.dev/ogimage.png" />
<meta charSet="UTF-8" />
</Head>

View File

@@ -20,12 +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 signUp={signUp} signIn={signIn} signOut={signOut} resetPassword={resetPassword} />
</div>
</Layout>
</>

74
tsconfig.json Normal file
View File

@@ -0,0 +1,74 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */
/* Basic Options */
// "incremental": true, /* Enable incremental compilation */
"target": "esnext", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */
"module": "esnext", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */// "lib": [], /* Specify library files to be included in the compilation. */
// "allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */
"jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */// "declaration": true, /* Generates corresponding '.d.ts' file. */
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
// "sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
// "outDir": "./", /* Redirect output structure to the directory. */
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
// "composite": true, /* Enable project compilation */
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
"removeComments": false, /* Do not emit comments to output. */// "noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
/* Strict Type-Checking Options */
"strict": true, /* Enable all strict type-checking options. */
"noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
"strictNullChecks": true, /* Enable strict null checks. */
"strictFunctionTypes": true, /* Enable strict checking of function types. */
"strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
"strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
"noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
"alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. *//* Additional Checks */
"noUnusedLocals": true, /* Report errors on unused locals. */
"noUnusedParameters": true, /* Report errors on unused parameters. */
"noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
"noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an 'override' modifier. */
// "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. */
// "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. */
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
/* Source Map Options */
// "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. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
/* Advanced Options */
"skipLibCheck": true, /* Skip type checking of declaration files. */
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */,
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"noEmit": true,
"resolveJsonModule": true,
"isolatedModules": true
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx"
],
"exclude": [
"node_modules"
]
}

View File

@@ -17,8 +17,7 @@ export const AuthProvider = ({ children }) => {
setLoading(false);
// Listen for changes on auth state (logged in, signed out, etc.)
const { data: listener } = supabase.auth.onAuthStateChange(
async (event, session) => {
const { data: listener } = supabase.auth.onAuthStateChange(async (event, session) => {
if ((event === 'SIGNED_OUT') | (event === 'SIGNED_IN')) {
fetch('/api/auth', {
method: 'POST',
@@ -31,8 +30,7 @@ export const AuthProvider = ({ children }) => {
}
setUser(session?.user ?? null);
setLoading(false);
}
);
});
return () => {
listener?.unsubscribe();
@@ -48,11 +46,7 @@ export const AuthProvider = ({ children }) => {
user,
};
return (
<AuthContext.Provider value={value}>
{!loading && children}
</AuthContext.Provider>
);
return <AuthContext.Provider value={value}>{!loading && children}</AuthContext.Provider>;
};
// export the useAuth hook