Add Poppins font + Pricing page + Improve comments + Avatar system (WIP)

This commit is contained in:
Michael
2021-06-21 10:22:16 +02:00
parent cfe19b1fa5
commit 8680223516
19 changed files with 988 additions and 389 deletions

View File

@@ -1,6 +1,10 @@
/*
This is the Auth component. It will allow your user to login.
By default, it is available with the auth.js page, but you can use it everywhere you want!
CONFIGURE THE AUTH COMPONENT LINE 30
You can select your auth providers, or just keep the email/password. You can
check the providers available here: https://supabase.io/docs/guides/auth
*/
import { Auth } from "@supabase/ui";
import { supabase } from "utils/supabaseClient";
@@ -9,14 +13,14 @@ const Container = (props) => {
const { user } = Auth.useUser();
if (user)
return (
<>
<div className='w-80 md:w-96 order-first lg:order-last'>
<p>Hello {user.email}! 👋 You are already logged in</p>
<button
className='btn btn-primary'
onClick={() => props.supabaseClient.auth.signOut()}>
Sign out
</button>
</>
</div>
);
return props.children;
};
@@ -25,7 +29,14 @@ const AuthComponent = () => {
return (
<Auth.UserContextProvider supabaseClient={supabase}>
<Container supabaseClient={supabase}>
<Auth supabaseClient={supabase} />
<Auth
supabaseClient={supabase}
providers={["google"]}
socialLayout='horizontal'
socialButtonSize='xlarge'
socialColors={true}
className='p-5'
/>
</Container>
</Auth.UserContextProvider>
);

24
components/AuthText.js Normal file
View File

@@ -0,0 +1,24 @@
import Image from "next/image";
import authImage from "public/auth.png";
const AuthText = () => {
return (
<div className='lg:mt-0 max-w-lg flex flex-col text-xl'>
<div className='mt-10 mb-3'>
<Image
src={authImage}
width={authImage.width / 1.5}
height={authImage.height / 1.5}
/>
</div>
<h2 className='text-4xl font-title font-semibold'>
Join SupaNexTail for <span className='text-accent'>free</span>!
</h2>
<p className='mb-5 mt-8'>
Create your website in a few minutes with our boilerplate.
</p>
</div>
);
};
export default AuthText;

90
components/Avatar.js Normal file
View File

@@ -0,0 +1,90 @@
import { useEffect, useState } from "react";
import { supabase } from "utils/supabaseClient";
const Avatar = ({ url, size, onUpload }) => {
const [avatarUrl, setAvatarUrl] = useState(null);
const [uploading, setUploading] = useState(false);
useEffect(() => {
if (url) downloadImage(url);
}, [url]);
async function downloadImage(path) {
try {
const { data, error } = await supabase.storage
.from("avatars")
.download(path);
if (error) {
throw error;
}
const url = URL.createObjectURL(data);
setAvatarUrl(url);
} catch (error) {
console.log("Error downloading image: ", error.message);
}
}
async function uploadAvatar(event) {
try {
setUploading(true);
if (!event.target.files || event.target.files.length === 0) {
throw new Error("You must select an image to upload.");
}
const file = event.target.files[0];
const fileExt = file.name.split(".").pop();
const fileName = `${Math.random()}.${fileExt}`;
const filePath = `${fileName}`;
let { error: uploadError } = await supabase.storage
.from("avatars")
.upload(filePath, file);
if (uploadError) {
throw uploadError;
}
onUpload(filePath);
} catch (error) {
alert(error.message);
} finally {
setUploading(false);
}
}
return (
<div className='m-auto mb-5'>
{avatarUrl ? (
<img
src={avatarUrl}
alt='Avatar'
className='avatar rounded-full w-28 h-28 flex m-auto'
/>
) : (
<div className='avatar rounded-full w-28 h-28' />
)}
<div style={{ width: size }}>
<label
className='mt-2 btn btn-primary text-center cursor-pointer text-xs'
htmlFor='single'>
{uploading ? "Uploading ..." : "Update my avatar"}
</label>
<input
style={{
visibility: "hidden",
position: "absolute",
}}
type='file'
id='single'
accept='image/*'
onChange={uploadAvatar}
disabled={uploading}
/>
</div>
</div>
);
};
export default Avatar;

View File

@@ -46,7 +46,7 @@ const Contact = () => {
<div className='font-main container'>
<div className=' m-auto max-w-lg'>
<div className='flex justify-center'>
<h2 className='text-3xl sm:text-5xl text-center mb-5 mt-0 font-bold'>
<h2 className='text-3xl sm:text-5xl text-center mb-5 mt-0 font-bold font-title'>
Contact
</h2>
</div>

View File

@@ -6,6 +6,8 @@ function with your new elements.
import { useEffect, useState } from "react";
import Avatar from "./Avatar";
import MyModal from "./MyModal";
import { supabase } from "../utils/supabaseClient";
export default function Account({ session }) {
@@ -74,12 +76,30 @@ export default function Account({ session }) {
return (
<div className='form-widget mt-10 flex flex-col text-left'>
<div className="mb-5 flex">
<label htmlFor='email' className="my-auto">Email</label>
<input className="input input-primary input-bordered input-sm ml-2 flex-1" id='email' type='text' value={session.user.email} disabled />
<Avatar
url={avatar_url}
size={150}
onUpload={(url) => {
setAvatarUrl(url);
updateProfile({ username, website, avatar_url: url });
}}
/>
<div className='mb-5 flex'>
<label htmlFor='email' className='my-auto'>
Email
</label>
<input
className='input input-primary input-bordered input-sm ml-2 flex-1'
id='email'
type='text'
value={session.user.email}
disabled
/>
</div>
<div className="mb-5 flex">
<label htmlFor='username' className="my-auto">Name</label>
<div className='mb-5 flex'>
<label htmlFor='username' className='my-auto'>
Name
</label>
<input
className='input input-primary input-bordered input-sm flex-1 ml-2'
id='username'
@@ -88,8 +108,10 @@ export default function Account({ session }) {
onChange={(e) => setUsername(e.target.value)}
/>
</div>
<div className="mb-5 flex">
<label htmlFor='website' className="my-auto">Website</label>
<div className='mb-5 flex'>
<label htmlFor='website' className='my-auto'>
Website
</label>
<input
className='input input-primary input-bordered input-sm flex-1 ml-2'
id='website'
@@ -99,7 +121,7 @@ export default function Account({ session }) {
/>
</div>
<div className="m-auto">
<div className='m-auto'>
<button
className='btn btn-primary btn-sm'
onClick={() => updateProfile({ username, website, avatar_url })}

View File

@@ -56,7 +56,7 @@ const Layout = (props) => {
<meta name='theme-color' content='#ffffff' />
</Head>
<Nav user={user} />
<main className='flex flex-col items-center justify-center w-full flex-1 p-2 text-center'>
<main className='flex flex-col items-center justify-center w-full flex-1 p-2 text-center font-body'>
{props.children}
</main>
<ToastContainer

92
components/MyModal.js Normal file
View File

@@ -0,0 +1,92 @@
import { Dialog, Transition } from '@headlessui/react'
import { Fragment, useState } from 'react'
export default function MyModal() {
let [isOpen, setIsOpen] = useState(false)
function closeModal() {
setIsOpen(false)
}
function openModal() {
setIsOpen(true)
}
return (
<>
<div className=" inset-0 flex items-center justify-center">
<button
type="button"
onClick={openModal}
className="px-4 py-2 text-sm font-medium text-white bg-black rounded-md bg-opacity-20 hover:bg-opacity-30 focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75"
>
Open dialog
</button>
</div>
<Transition appear show={isOpen} as={Fragment}>
<Dialog
as="div"
className="fixed inset-0 z-10 overflow-y-auto"
onClose={closeModal}
>
<div className="min-h-screen px-4 text-center">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Dialog.Overlay className="fixed inset-0" />
</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"
>
&#8203;
</span>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<div className="inline-block w-full max-w-md p-6 my-8 overflow-hidden text-left align-middle transition-all transform bg-white shadow-xl rounded-2xl">
<Dialog.Title
as="h3"
className="text-lg font-medium leading-6 text-gray-900"
>
Payment successful
</Dialog.Title>
<div className="mt-2">
<p className="text-sm text-gray-500">
Your payment has been successfully submitted. Weve sent
your an email with all of the details of your order.
</p>
</div>
<div className="mt-4">
<button
type="button"
className="inline-flex justify-center px-4 py-2 text-sm font-medium text-blue-900 bg-blue-100 border border-transparent rounded-md hover:bg-blue-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-blue-500"
onClick={closeModal}
>
Got it, thanks!
</button>
</div>
</div>
</Transition.Child>
</div>
</Dialog>
</Transition>
</>
)
}

View File

@@ -39,6 +39,10 @@ const Nav = (props) => {
</Link>
)}
<Link href='/pricing'>
<a className='btn btn-ghost btn-sm'>Pricing</a>
</Link>
<Link href='/'>
<a className='btn btn-ghost btn-sm'>About</a>
</Link>
@@ -62,7 +66,7 @@ const Nav = (props) => {
);
return (
<nav className='navbar mb-2 w-full'>
<nav className='navbar mb-2 w-full px-5'>
<Link href='/'>
<a>
<Image src={Logo} />

136
components/Pricing.js Normal file
View File

@@ -0,0 +1,136 @@
import { Switch } from "@headlessui/react";
import { useState } from "react";
const Pricing = () => {
const [enabled, setEnabled] = useState(false);
const pricing = {
monthly: {
personal: "$5/mo",
team: "15/mo",
pro: "35/mo",
},
yearly: {
personal: "$50/yr",
team: "$150/yr",
pro: "$350/yr",
},
};
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>
<h3 className='text-lg font-light leading-8 p-3 mb-5'>
Lorem ipsum dolor sit amet consectetur adipisicing elit repellat
dignissimos laboriosam odit accusamus porro
</h3>
</div>
<div className='flex justify-between max-w-xs m-auto mb-3'>
<div>
<p className={`${enabled ? "text-gray-500" : null}`}>
Billed monthly
</p>
</div>
<div>
<Switch
checked={enabled}
onChange={setEnabled}
className={`bg-primary relative inline-flex flex-shrink-0 h-[38px] w-[74px]
border-2 border-transparent rounded-full cursor-pointer transition-colors
ease-in-out duration-200 focus:outline-none focus-visible:ring-2
focus-visible:ring-white focus-visible:ring-opacity-75`}>
<span className='sr-only'>Switch bill</span>
<span
aria-hidden='true'
className={`${enabled ? "translate-x-9" : "translate-x-0"}
pointer-events-none inline-block h-[34px] w-[34px] rounded-full bg-white shadow-lg transform ring-0 transition ease-in-out duration-200`}
/>
</Switch>
</div>
<div>
<p className={`${!enabled ? "text-gray-500" : null}`}>
Billed annually
</p>
</div>
</div>
<div className='max-w-4xl mx-auto md:flex'>
<div className='w-full md:w-1/3 md:max-w-none bg-white px-8 md:px-10 py-8 md:py-10 mb-3 mx-auto md:my-6 rounded-md shadow-lg shadow-gray-600 md:flex md:flex-col'>
<div className='w-full flex-grow'>
<h2 className='text-center font-bold uppercase mb-4'>Personal</h2>
<h3 className='text-center font-bold text-4xl mb-5'>
{enabled ? pricing.yearly.personal : pricing.monthly.personal}
</h3>
<ul className='text-sm px-5 mb-8'>
<li className='leading-tight'>
<i className='mdi mdi-check-bold text-lg'></i> Lorem ipsum
</li>
<li className='leading-tight'>
<i className='mdi mdi-check-bold text-lg'></i> Dolor sit amet
</li>
</ul>
</div>
<div className='w-full'>
<button className='btn btn-primary w-full'>Buy Now</button>
</div>
</div>
<div className='w-full md:w-1/3 md:max-w-none bg-white px-8 md:px-10 py-8 md:py-10 mb-3 mx-auto md:-mx-3 md:mb-0 rounded-md shadow-lg shadow-gray-600 md:relative md:z-50 md:flex md:flex-col'>
<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 md:text-5xl mb-5'>
{enabled ? pricing.yearly.team : pricing.monthly.team}
</h3>
<ul className='text-sm px-5 mb-8'>
<li className='leading-tight'>
<i className='mdi mdi-check-bold text-lg'></i> Lorem ipsum
</li>
<li className='leading-tight'>
<i className='mdi mdi-check-bold text-lg'></i> Dolor sit amet
</li>
<li className='leading-tight'>
<i className='mdi mdi-check-bold text-lg'></i> Consectetur
</li>
<li className='leading-tight'>
<i className='mdi mdi-check-bold text-lg'></i> Adipisicing
</li>
<li className='leading-tight'>
<i className='mdi mdi-check-bold text-lg'></i> Elit repellat
</li>
</ul>
</div>
<div className='w-full'>
<button className='btn btn-primary w-full'>Buy Now</button>
</div>
</div>
<div className='w-full md:w-1/3 md:max-w-none bg-white px-8 md:px-10 py-8 md:py-10 mb-3 mx-auto md:my-6 rounded-md shadow-lg shadow-gray-600 md:flex md:flex-col'>
<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'>
{enabled ? pricing.yearly.pro : pricing.monthly.pro}
</h3>
<ul className='text-sm px-5 mb-8'>
<li className='leading-tight'>
<i className='mdi mdi-check-bold text-lg'></i> Lorem ipsum
</li>
<li className='leading-tight'>
<i className='mdi mdi-check-bold text-lg'></i> Dolor sit amet
</li>
<li className='leading-tight'>
<i className='mdi mdi-check-bold text-lg'></i> Consectetur
</li>
<li className='leading-tight'>
<i className='mdi mdi-check-bold text-lg'></i> Adipisicing
</li>
<li className='leading-tight'>
<i className='mdi mdi-check-bold text-lg'></i> Much more...
</li>
</ul>
</div>
<div className='w-full'>
<button className='btn btn-primary w-full'>Buy Now</button>
</div>
</div>
</div>
</div>
);
};
export default Pricing;

924
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -8,6 +8,7 @@
"start": "next start"
},
"dependencies": {
"@headlessui/react": "^1.2.0",
"@sendgrid/mail": "^7.4.4",
"@supabase/supabase-js": "^1.15.0",
"@supabase/ui": "^0.27.3",
@@ -23,6 +24,6 @@
"devDependencies": {
"autoprefixer": "^10.0.4",
"postcss": "^8.1.10",
"tailwindcss": "^2.1.1"
"tailwindcss": "^2.2.2"
}
}

View File

@@ -1,4 +1,4 @@
import "tailwindcss/tailwind.css";
import "./global.css";
import { Auth } from "@supabase/ui";
import { DefaultSeo } from "next-seo";

View File

@@ -1,5 +1,6 @@
import { Auth } from "@supabase/ui";
import AuthComponent from "../components/Auth";
import AuthText from "components/AuthText";
import Layout from "components/Layout";
import { NextSeo } from "next-seo";
import { useEffect } from "react";
@@ -23,15 +24,9 @@ const AuthPage = () => {
/>
<Layout>
<div>
<h1 className='text-4xl font-bold md:text-5xl'>Auth</h1>
<div className='container'>
{!session && (
<div className='max-w-md'>
<AuthComponent />
</div>
)}
</div>
<div className='flex flex-wrap justify-evenly w-full'>
<AuthText />
<div>{!session && <AuthComponent />}</div>
</div>
</Layout>
</>

View File

@@ -34,7 +34,7 @@ const DashboardPage = ({ user }) => {
<Layout>
<div>
<h1 className='text-4xl font-bold md:text-5xl'>Dashboard</h1>
<h1 className='text-4xl font-bold md:text-5xl font-title'>Dashboard</h1>
<>
{!session ? (
<div className='max-w-md'>

2
pages/global.css Normal file
View File

@@ -0,0 +1,2 @@
@import "tailwindcss/tailwind.css";
@import url("https://fonts.googleapis.com/css2?family=Open+Sans&family=Poppins:wght@400;600;800&display=swap");

View File

@@ -9,7 +9,7 @@ const Home = () => {
description={`SupaNexTail is a boilerplate for your website, based on Next.js, Supabase, and TailwindCSS`}
/>
<Layout>
<h2 className='text-5xl md:text-6xl font-bold'>
<h2 className='text-5xl md:text-6xl font-bold font-title'>
SupaNexTail <span className='text-blue-600'>Boilerplate</span>
</h2>
</Layout>

18
pages/pricing.js Normal file
View File

@@ -0,0 +1,18 @@
import Layout from "components/Layout";
import { NextSeo } from "next-seo";
import Pricing from "components/Pricing";
const PricingPage = () => {
return (
<>
<NextSeo
title={`Welcome to ${process.env.NEXT_PUBLIC_TITLE} 👋`}
description={`SupaNexTail is a boilerplate for your website, based on Next.js, Supabase, and TailwindCSS`}
/>
<Layout>
<Pricing />
</Layout>
</>
);
};
export default PricingPage;

BIN
public/auth.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -3,6 +3,10 @@ module.exports = {
purge: ["./pages/**/*.{js,ts,jsx,tsx}", "./components/**/*.{js,ts,jsx,tsx}"],
darkMode: false, // or 'media' or 'class'
theme: {
fontFamily: {
title: ["Poppins"],
body: ["Open Sans"],
},
extend: {},
},
variants: {