mirror of
https://github.com/fergalmoran/supanextail.git
synced 2025-12-22 09:17:54 +00:00
Typescript first integration + fix all eslint errors (97 warnings)
This commit is contained in:
@@ -2,22 +2,18 @@ import Image from 'next/image';
|
||||
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}
|
||||
/>
|
||||
</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.
|
||||
</p>
|
||||
</div>
|
||||
<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} />
|
||||
</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.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default AuthText;
|
||||
|
||||
@@ -6,99 +6,105 @@ 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 [avatarUrl, setAvatarUrl] = useState(null);
|
||||
const [uploading, setUploading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (url) downloadImage(url);
|
||||
}, [url]);
|
||||
const customImgLoader = ({ src }) => {
|
||||
return `${src}`;
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
useEffect(() => {
|
||||
if (url) downloadImage(url);
|
||||
}, [url]);
|
||||
|
||||
async function uploadAvatar(event) {
|
||||
try {
|
||||
setUploading(true);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
if (!event.target.files || event.target.files.length === 0) {
|
||||
throw new Error('You must select an image to upload.');
|
||||
}
|
||||
async function uploadAvatar(event) {
|
||||
try {
|
||||
setUploading(true);
|
||||
|
||||
const file = event.target.files[0];
|
||||
const fileExt = file.name.split('.').pop();
|
||||
const fileName = `${Math.random()}.${fileExt}`;
|
||||
const filePath = `${fileName}`;
|
||||
if (!event.target.files || event.target.files.length === 0) {
|
||||
throw new Error('You must select an image to upload.');
|
||||
}
|
||||
|
||||
if (event.target.files[0].size > 150000) {
|
||||
alert('File is too big!');
|
||||
event.target.value = '';
|
||||
setUploading(false);
|
||||
return;
|
||||
}
|
||||
const file = event.target.files[0];
|
||||
const fileExt = file.name.split('.').pop();
|
||||
const fileName = `${Math.random()}.${fileExt}`;
|
||||
const filePath = `${fileName}`;
|
||||
|
||||
const { error: uploadError } = await supabase.storage
|
||||
.from('avatars')
|
||||
.upload(filePath, file);
|
||||
if (event.target.files[0].size > 150000) {
|
||||
alert('File is too big!');
|
||||
event.target.value = '';
|
||||
setUploading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (uploadError) {
|
||||
throw uploadError;
|
||||
}
|
||||
const { error: uploadError } = await supabase.storage.from('avatars').upload(filePath, file);
|
||||
|
||||
onUpload(filePath);
|
||||
} catch (error) {
|
||||
alert(error.message);
|
||||
} finally {
|
||||
setUploading(false);
|
||||
}
|
||||
}
|
||||
if (uploadError) {
|
||||
throw uploadError;
|
||||
}
|
||||
|
||||
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 btn-sm"
|
||||
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>
|
||||
);
|
||||
onUpload(filePath);
|
||||
} catch (error) {
|
||||
alert(error.message);
|
||||
} finally {
|
||||
setUploading(false);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="m-auto mb-5">
|
||||
{avatarUrl ? (
|
||||
<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"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<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 btn-sm"
|
||||
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;
|
||||
|
||||
@@ -8,53 +8,48 @@ import cardStripe from 'public/landing/stripe.svg';
|
||||
import cardTheme from 'public/landing/theme.svg';
|
||||
|
||||
const CardsLanding = () => (
|
||||
<div className="mt-14">
|
||||
<h2 className="uppercase font-bold text-4xl tracking-wide text-center">
|
||||
We've got you covered
|
||||
</h2>
|
||||
<p className="max-w-md m-auto text-center">
|
||||
Don’t 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
|
||||
image={cardPage}
|
||||
text="7 pages fully designed and easily customizable"
|
||||
title="Templates"
|
||||
/>
|
||||
<CardLanding
|
||||
image={cardServer}
|
||||
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={cardResponsive}
|
||||
text="Mobile ready, fully responsive and customizable with Tailwind CSS"
|
||||
title="Responsive"
|
||||
/>
|
||||
<CardLanding
|
||||
image={cardTheme}
|
||||
text="Custom themes available and easily switch to dark mode"
|
||||
title="Themes"
|
||||
/>
|
||||
<CardLanding
|
||||
image={cardStripe}
|
||||
text="Stripe integration. Fully functional subscription system"
|
||||
title="Payment"
|
||||
/>
|
||||
<CardLanding
|
||||
image={cardFee}
|
||||
text="One-time fee. No subscription, you’ll have access to all the updates"
|
||||
title="Lifetime access"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-14">
|
||||
<h2 className="uppercase font-bold text-4xl tracking-wide text-center">
|
||||
We've got you covered
|
||||
</h2>
|
||||
<p className="max-w-md m-auto text-center">
|
||||
Don’t 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
|
||||
image={cardPage}
|
||||
text="7 pages fully designed and easily customizable"
|
||||
title="Templates"
|
||||
/>
|
||||
<CardLanding
|
||||
image={cardServer}
|
||||
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={cardResponsive}
|
||||
text="Mobile ready, fully responsive and customizable with Tailwind CSS"
|
||||
title="Responsive"
|
||||
/>
|
||||
<CardLanding
|
||||
image={cardTheme}
|
||||
text="Custom themes available and easily switch to dark mode"
|
||||
title="Themes"
|
||||
/>
|
||||
<CardLanding
|
||||
image={cardStripe}
|
||||
text="Stripe integration. Fully functional subscription system"
|
||||
title="Payment"
|
||||
/>
|
||||
<CardLanding
|
||||
image={cardFee}
|
||||
text="One-time fee. No subscription, you’ll have access to all the updates"
|
||||
title="Lifetime access"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default CardsLanding;
|
||||
|
||||
@@ -10,92 +10,92 @@ import axios from 'axios';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
const Contact = () => {
|
||||
const sendEmail = () => {
|
||||
const name = document.getElementById('name').value;
|
||||
const email = document.getElementById('email').value;
|
||||
const message = document.getElementById('message').value;
|
||||
const sendEmail = () => {
|
||||
const name = document.getElementById('name').value;
|
||||
const email = document.getElementById('email').value;
|
||||
const message = document.getElementById('message').value;
|
||||
|
||||
if (name && email && message) {
|
||||
axios
|
||||
.post('/api/sendgrid', { email, name, message })
|
||||
.then((result) => {
|
||||
if (result.data.success === true) {
|
||||
toast.success(result.data.message);
|
||||
document.getElementById('name').value = '';
|
||||
document.getElementById('email').value = '';
|
||||
document.getElementById('message').value = '';
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
});
|
||||
} else {
|
||||
toast.info('Please enter at least one URL', {
|
||||
position: 'top-center',
|
||||
autoClose: 2000,
|
||||
hideProgressBar: true,
|
||||
closeOnClick: true,
|
||||
pauseOnHover: true,
|
||||
draggable: true,
|
||||
progress: undefined,
|
||||
});
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div className="max-w-xl m-auto px-5 py-10">
|
||||
<div>
|
||||
<div className="flex justify-center">
|
||||
<h2 className="text-3xl sm:text-4xl text-center mb-5 mt-0 font-bold font-title">
|
||||
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!
|
||||
</p>
|
||||
</div>
|
||||
<form className="m-auto mt-5 grid grid-cols-1 gap-4 md:grid-cols-2 p-5">
|
||||
<div className="flex flex-col max-w-xs">
|
||||
<p className="font-light mb-4 text-left">Your Name</p>
|
||||
<input
|
||||
id="name"
|
||||
name="name"
|
||||
placeholder="Enter your name"
|
||||
className="input input-primary input-bordered"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col max-w-xs mb-3">
|
||||
<p className="font-light mb-4 text-left">Your email</p>
|
||||
<input
|
||||
id="email"
|
||||
name="email"
|
||||
placeholder="Enter your email adress"
|
||||
className="input input-primary input-bordered"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col col-span-full w-fulll">
|
||||
<p className="font-light mb-4 text-left">Message</p>
|
||||
<textarea
|
||||
id="message"
|
||||
name="message"
|
||||
placeholder="Enter your message here..."
|
||||
rows="5"
|
||||
className="input input-primary input-bordered resize-none w-full h-32 pt-2"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-primary btn-sm"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
sendEmail();
|
||||
}}
|
||||
>
|
||||
Submit{' '}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
if (name && email && message) {
|
||||
axios
|
||||
.post('/api/sendgrid', { email, name, message })
|
||||
.then((result) => {
|
||||
if (result.data.success === true) {
|
||||
toast.success(result.data.message);
|
||||
document.getElementById('name').value = '';
|
||||
document.getElementById('email').value = '';
|
||||
document.getElementById('message').value = '';
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
});
|
||||
} else {
|
||||
toast.info('Please enter at least one URL', {
|
||||
position: 'top-center',
|
||||
autoClose: 2000,
|
||||
hideProgressBar: true,
|
||||
closeOnClick: true,
|
||||
pauseOnHover: true,
|
||||
draggable: true,
|
||||
progress: undefined,
|
||||
});
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div className="max-w-xl m-auto px-5 py-10">
|
||||
<div>
|
||||
<div className="flex justify-center">
|
||||
<h2 className="text-3xl sm:text-4xl text-center mb-5 mt-0 font-bold font-title">
|
||||
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!
|
||||
</p>
|
||||
</div>
|
||||
<form className="m-auto mt-5 grid grid-cols-1 gap-4 md:grid-cols-2 p-5">
|
||||
<div className="flex flex-col max-w-xs">
|
||||
<p className="font-light mb-4 text-left">Your Name</p>
|
||||
<input
|
||||
id="name"
|
||||
name="name"
|
||||
placeholder="Enter your name"
|
||||
className="input input-primary input-bordered"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col max-w-xs mb-3">
|
||||
<p className="font-light mb-4 text-left">Your email</p>
|
||||
<input
|
||||
id="email"
|
||||
name="email"
|
||||
placeholder="Enter your email adress"
|
||||
className="input input-primary input-bordered"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col col-span-full w-fulll">
|
||||
<p className="font-light mb-4 text-left">Message</p>
|
||||
<textarea
|
||||
id="message"
|
||||
name="message"
|
||||
placeholder="Enter your message here..."
|
||||
rows="5"
|
||||
className="input input-primary input-bordered resize-none w-full h-32 pt-2"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-primary btn-sm"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
sendEmail();
|
||||
}}
|
||||
>
|
||||
Submit{' '}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Contact;
|
||||
|
||||
@@ -17,117 +17,115 @@ import PaymentModal from './PaymentModal';
|
||||
import Avatar from './Avatar';
|
||||
|
||||
export default function Dashboard(props) {
|
||||
const router = useRouter();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [username, setUsername] = useState(props.profile.username);
|
||||
const [website, setWebsite] = useState(props.profile.website);
|
||||
const [avatar_url, setAvatarUrl] = useState(props.profile.avatar_url);
|
||||
const [payment, setPayment] = useState(false);
|
||||
const router = useRouter();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [username, setUsername] = useState(props.profile.username);
|
||||
const [website, setWebsite] = useState(props.profile.website);
|
||||
const [avatar_url, setAvatarUrl] = useState(props.profile.avatar_url);
|
||||
const [payment, setPayment] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (router.query.session_id && router.query.session_id !== 'canceled') {
|
||||
setPayment(true);
|
||||
}
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
if (router.query.session_id && router.query.session_id !== 'canceled') {
|
||||
setPayment(true);
|
||||
}
|
||||
}, []);
|
||||
|
||||
async function updateProfile({ username, website, avatar_url }) {
|
||||
try {
|
||||
setLoading(true);
|
||||
const user = supabase.auth.user();
|
||||
async function updateProfile({ username, website, avatar_url }) {
|
||||
try {
|
||||
setLoading(true);
|
||||
const user = supabase.auth.user();
|
||||
|
||||
const updates = {
|
||||
id: user.id,
|
||||
username,
|
||||
website,
|
||||
avatar_url,
|
||||
updated_at: new Date(),
|
||||
};
|
||||
const updates = {
|
||||
id: user.id,
|
||||
username,
|
||||
website,
|
||||
avatar_url,
|
||||
updated_at: new Date(),
|
||||
};
|
||||
|
||||
const { error } = await supabase.from('profiles').upsert(updates, {
|
||||
returning: 'minimal', // Don't return the value after inserting
|
||||
});
|
||||
const { error } = await supabase.from('profiles').upsert(updates, {
|
||||
returning: 'minimal', // Don't return the value after inserting
|
||||
});
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
} catch (error) {
|
||||
alert(error.message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
toast.success('Your profile has been updated');
|
||||
}
|
||||
}
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
} catch (error) {
|
||||
alert(error.message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
toast.success('Your profile has been updated');
|
||||
}
|
||||
}
|
||||
|
||||
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>
|
||||
<Avatar
|
||||
url={avatar_url}
|
||||
size={150}
|
||||
onUpload={(url) => {
|
||||
setAvatarUrl(url);
|
||||
updateProfile({ username, website, avatar_url: url });
|
||||
}}
|
||||
/>
|
||||
<div className="mb-5 flex flex-col">
|
||||
<label htmlFor="email" className="my-auto text-sm mb-2">
|
||||
Email
|
||||
</label>
|
||||
<input
|
||||
className="input input-primary input-bordered input-sm flex-1 text-base-100"
|
||||
id="email"
|
||||
type="text"
|
||||
value={props.session.user.email}
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-5 flex flex-col">
|
||||
<label htmlFor="username" className="my-auto text-sm mb-2">
|
||||
Name
|
||||
</label>
|
||||
<input
|
||||
className="input input-primary input-bordered input-sm flex-1"
|
||||
id="username"
|
||||
type="text"
|
||||
value={username || ''}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-5 flex flex-col">
|
||||
<label htmlFor="website" className="my-auto text-sm mb-2">
|
||||
Website
|
||||
</label>
|
||||
<input
|
||||
className="input input-primary input-bordered input-sm flex-1"
|
||||
id="website"
|
||||
type="website"
|
||||
value={website || ''}
|
||||
onChange={(e) => setWebsite(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
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>
|
||||
<Avatar
|
||||
url={avatar_url}
|
||||
size={150}
|
||||
onUpload={(url) => {
|
||||
setAvatarUrl(url);
|
||||
updateProfile({ username, website, avatar_url: url });
|
||||
}}
|
||||
/>
|
||||
<div className="mb-5 flex flex-col">
|
||||
<label htmlFor="email" className="my-auto text-sm mb-2">
|
||||
Email
|
||||
</label>
|
||||
<input
|
||||
className="input input-primary input-bordered input-sm flex-1 text-base-100"
|
||||
id="email"
|
||||
type="text"
|
||||
value={props.session.user.email}
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-5 flex flex-col">
|
||||
<label htmlFor="username" className="my-auto text-sm mb-2">
|
||||
Name
|
||||
</label>
|
||||
<input
|
||||
className="input input-primary input-bordered input-sm flex-1"
|
||||
id="username"
|
||||
type="text"
|
||||
value={username || ''}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-5 flex flex-col">
|
||||
<label htmlFor="website" className="my-auto text-sm mb-2">
|
||||
Website
|
||||
</label>
|
||||
<input
|
||||
className="input input-primary input-bordered input-sm flex-1"
|
||||
id="website"
|
||||
type="website"
|
||||
value={website || ''}
|
||||
onChange={(e) => setWebsite(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="m-auto">
|
||||
<button
|
||||
className="btn btn-primary btn-sm"
|
||||
onClick={() => updateProfile({ username, website, avatar_url })}
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? 'Loading ...' : 'Update My Profile'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="m-auto">
|
||||
<button
|
||||
className="btn btn-primary btn-sm"
|
||||
onClick={() => updateProfile({ username, website, avatar_url })}
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? 'Loading ...' : 'Update My Profile'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="max-w-xl flex flex-row flex-wrap m-auto w-full p-5 bordered border-2 border-primary shadow-lg my-5">
|
||||
<Image src={Plan} />
|
||||
<div className="flex flex-col m-auto">
|
||||
<h2>Your current plan</h2>
|
||||
<p className="">{props.plan ? PriceIds[props.plan] : 'Free tier'}</p>
|
||||
</div>
|
||||
</div>
|
||||
<PaymentModal open={payment} setPayment={setPayment} />
|
||||
</div>
|
||||
);
|
||||
<div className="max-w-xl flex flex-row flex-wrap m-auto w-full p-5 bordered border-2 border-primary shadow-lg my-5">
|
||||
<Image src={Plan} />
|
||||
<div className="flex flex-col m-auto">
|
||||
<h2>Your current plan</h2>
|
||||
<p className="">{props.plan ? PriceIds[props.plan] : 'Free tier'}</p>
|
||||
</div>
|
||||
</div>
|
||||
<PaymentModal open={payment} setPayment={setPayment} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,27 +2,27 @@ import Link from 'next/link';
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
const Footer = () => {
|
||||
const ThemeToggle = dynamic(() => import('components/UI/ThemeToggle.js'), {
|
||||
ssr: false,
|
||||
});
|
||||
return (
|
||||
<footer className="w-full flex">
|
||||
<nav className=" mr-auto">
|
||||
<div className="flex flex-col sm:flex-row justify-evenly w-full sm:space-x-10">
|
||||
<div className="">© {process.env.NEXT_PUBLIC_TITLE}</div>
|
||||
<Link href="/privacy">
|
||||
<a>Privacy Policy</a>
|
||||
</Link>
|
||||
<Link href="/terms">
|
||||
<a>Terms of service</a>
|
||||
</Link>
|
||||
</div>
|
||||
</nav>
|
||||
<div className="mr-5 my-auto">
|
||||
<ThemeToggle />
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
const ThemeToggle = dynamic(() => import('components/UI/ThemeToggle.js'), {
|
||||
ssr: false,
|
||||
});
|
||||
return (
|
||||
<footer className="w-full flex">
|
||||
<nav className=" mr-auto">
|
||||
<div className="flex flex-col sm:flex-row justify-evenly w-full sm:space-x-10">
|
||||
<div className="">© {process.env.NEXT_PUBLIC_TITLE}</div>
|
||||
<Link href="/privacy">
|
||||
<a>Privacy Policy</a>
|
||||
</Link>
|
||||
<Link href="/terms">
|
||||
<a>Terms of service</a>
|
||||
</Link>
|
||||
</div>
|
||||
</nav>
|
||||
<div className="mr-5 my-auto">
|
||||
<ThemeToggle />
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
};
|
||||
|
||||
export default Footer;
|
||||
|
||||
@@ -6,60 +6,57 @@ import supabaseImage from 'public/landing/supabase.svg';
|
||||
import MailingList from './MailingList';
|
||||
|
||||
const Landing = () => (
|
||||
<div className="mt-10 mb-20 text-base-content w-full">
|
||||
<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!
|
||||
</h2>
|
||||
<p>
|
||||
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">
|
||||
<Image src={landTop} height={417} width={583} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-10 mb-20 text-base-content w-full">
|
||||
<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!
|
||||
</h2>
|
||||
<p>
|
||||
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">
|
||||
<Image src={landTop} height={417} width={583} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CardsLanding />
|
||||
<div className="flex max-w-6xl m-auto justify-around mt-14 flex-wrap">
|
||||
<div className="max-w-sm mr-16 my-auto">
|
||||
<h2 className="text-4xl font-bold font-title text-left leading-normal">
|
||||
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.
|
||||
</p>
|
||||
</div>
|
||||
<div className="max-w-xl">
|
||||
<Image src={start} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex max-w-6xl m-auto justify-around mt-24 flex-wrap">
|
||||
<div className="max-w-md my-auto order-1 lg:order-2">
|
||||
<h2 className="text-4xl font-bold font-title text-left leading-normal">
|
||||
Leverage the power of <span className="text-primary">Supabase</span>
|
||||
</h2>
|
||||
<p>
|
||||
Supabase is an open source Firebase alternative. You’ll 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, we’ve got you covered!
|
||||
</p>
|
||||
</div>
|
||||
<div className="max-w-md order-2 lg:order-1 flex">
|
||||
<Image src={supabaseImage} />
|
||||
</div>
|
||||
</div>
|
||||
<MailingList />
|
||||
</div>
|
||||
<CardsLanding />
|
||||
<div className="flex max-w-6xl m-auto justify-around mt-14 flex-wrap">
|
||||
<div className="max-w-sm mr-16 my-auto">
|
||||
<h2 className="text-4xl font-bold font-title text-left leading-normal">
|
||||
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.
|
||||
</p>
|
||||
</div>
|
||||
<div className="max-w-xl">
|
||||
<Image src={start} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex max-w-6xl m-auto justify-around mt-24 flex-wrap">
|
||||
<div className="max-w-md my-auto order-1 lg:order-2">
|
||||
<h2 className="text-4xl font-bold font-title text-left leading-normal">
|
||||
Leverage the power of <span className="text-primary">Supabase</span>
|
||||
</h2>
|
||||
<p>
|
||||
Supabase is an open source Firebase alternative. You’ll 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, we’ve got you covered!
|
||||
</p>
|
||||
</div>
|
||||
<div className="max-w-md order-2 lg:order-1 flex">
|
||||
<Image src={supabaseImage} />
|
||||
</div>
|
||||
</div>
|
||||
<MailingList />
|
||||
</div>
|
||||
);
|
||||
|
||||
export default Landing;
|
||||
|
||||
@@ -19,57 +19,43 @@ import Nav from './Nav';
|
||||
import Footer from './Footer';
|
||||
|
||||
const Layout = (props) => {
|
||||
const { user, signOut } = useAuth();
|
||||
const { user, signOut } = useAuth();
|
||||
|
||||
const toastStyle = {
|
||||
// Style your toast elements here
|
||||
success: 'bg-accent',
|
||||
error: 'bg-red-600',
|
||||
info: 'bg-gray-600',
|
||||
warning: 'bg-orange-400',
|
||||
default: 'bg-primary',
|
||||
dark: 'bg-white-600 font-gray-300',
|
||||
};
|
||||
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="manifest" href="/site.webmanifest" />
|
||||
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5" />
|
||||
<meta name="msapplication-TileColor" content="#da532c" />
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
</Head>
|
||||
<div className="max-w-7xl flex flex-col min-h-screen mx-auto p-5">
|
||||
<Nav user={user} signOut={signOut} />
|
||||
<main className="flex-1">{props.children}</main>
|
||||
<ToastContainer
|
||||
position="bottom-center"
|
||||
toastClassName={({ type }) =>
|
||||
`${
|
||||
toastStyle[type || 'default']
|
||||
} flex p-5 my-5 min-h-10 rounded-md justify-between overflow-hidden cursor-pointer `
|
||||
}
|
||||
/>
|
||||
<Footer />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
const toastStyle = {
|
||||
// Style your toast elements here
|
||||
success: 'bg-accent',
|
||||
error: 'bg-red-600',
|
||||
info: 'bg-gray-600',
|
||||
warning: 'bg-orange-400',
|
||||
default: 'bg-primary',
|
||||
dark: 'bg-white-600 font-gray-300',
|
||||
};
|
||||
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="manifest" href="/site.webmanifest" />
|
||||
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5" />
|
||||
<meta name="msapplication-TileColor" content="#da532c" />
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
</Head>
|
||||
<div className="max-w-7xl flex flex-col min-h-screen mx-auto p-5">
|
||||
<Nav user={user} signOut={signOut} />
|
||||
<main className="flex-1">{props.children}</main>
|
||||
<ToastContainer
|
||||
position="bottom-center"
|
||||
toastClassName={({ type }) =>
|
||||
`${
|
||||
toastStyle[type || 'default']
|
||||
} flex p-5 my-5 min-h-10 rounded-md justify-between overflow-hidden cursor-pointer `
|
||||
}
|
||||
/>
|
||||
<Footer />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Layout;
|
||||
|
||||
@@ -10,77 +10,73 @@ import { toast } from 'react-toastify';
|
||||
import { useState } from 'react';
|
||||
|
||||
const MailingList = () => {
|
||||
const [mail, setMail] = useState(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [valid, setValid] = useState(true);
|
||||
const [mail, setMail] = useState(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [valid, setValid] = useState(true);
|
||||
|
||||
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,}))$/;
|
||||
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,}))$/;
|
||||
|
||||
if (regex.test(mail)) {
|
||||
// this is a valid email address
|
||||
subscribe();
|
||||
setValid(true);
|
||||
} else {
|
||||
// invalid email.
|
||||
toast.error('Your email is invalid');
|
||||
setValid(false);
|
||||
}
|
||||
};
|
||||
if (regex.test(mail)) {
|
||||
// this is a valid email address
|
||||
subscribe();
|
||||
setValid(true);
|
||||
} else {
|
||||
// invalid email.
|
||||
toast.error('Your email is invalid');
|
||||
setValid(false);
|
||||
}
|
||||
};
|
||||
|
||||
const subscribe = () => {
|
||||
setLoading(true);
|
||||
axios
|
||||
.put('api/mailingList', {
|
||||
mail,
|
||||
})
|
||||
.then((result) => {
|
||||
if (result.status === 200) {
|
||||
toast.success(result.data.message);
|
||||
setLoading(false);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
return (
|
||||
<div className="my-10 mt-24 m-auto flex flex-col">
|
||||
<h2 className="text-3xl md:text-4xl font-bold font-title uppercase text-center">
|
||||
Stay Tuned
|
||||
</h2>
|
||||
<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!
|
||||
</p>
|
||||
</label>
|
||||
<div className="mt-5 m-auto">
|
||||
<input
|
||||
onChange={(e) => {
|
||||
setMail(e.target.value);
|
||||
}}
|
||||
type="email"
|
||||
placeholder="Your email"
|
||||
className={`input input-primary input-bordered ${
|
||||
valid ? null : 'input-error'
|
||||
}`}
|
||||
/>
|
||||
<button
|
||||
onClick={validateEmail}
|
||||
className={`btn ml-3 ${
|
||||
loading ? 'btn-disabled loading' : 'btn-primary'
|
||||
}`}
|
||||
>
|
||||
I'm in!
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
const subscribe = () => {
|
||||
setLoading(true);
|
||||
axios
|
||||
.put('api/mailingList', {
|
||||
mail,
|
||||
})
|
||||
.then((result) => {
|
||||
if (result.status === 200) {
|
||||
toast.success(result.data.message);
|
||||
setLoading(false);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
return (
|
||||
<div className="my-10 mt-24 m-auto flex flex-col">
|
||||
<h2 className="text-3xl md:text-4xl font-bold font-title uppercase text-center">
|
||||
Stay Tuned
|
||||
</h2>
|
||||
<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!
|
||||
</p>
|
||||
</label>
|
||||
<div className="mt-5 m-auto">
|
||||
<input
|
||||
onChange={(e) => {
|
||||
setMail(e.target.value);
|
||||
}}
|
||||
type="email"
|
||||
placeholder="Your email"
|
||||
className={`input input-primary input-bordered ${valid ? null : 'input-error'}`}
|
||||
/>
|
||||
<button
|
||||
onClick={validateEmail}
|
||||
className={`btn ml-3 ${loading ? 'btn-disabled loading' : 'btn-primary'}`}
|
||||
>
|
||||
I'm in!
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default MailingList;
|
||||
|
||||
@@ -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
70
components/Nav.tsx
Normal 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;
|
||||
@@ -3,77 +3,64 @@ import { Dialog, Transition } from '@headlessui/react';
|
||||
import { Fragment } from 'react';
|
||||
|
||||
const PaymentModal = (props) => {
|
||||
function closeModal() {
|
||||
props.setPayment(false);
|
||||
}
|
||||
function closeModal() {
|
||||
props.setPayment(false);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Transition appear show={props.open} as={Fragment}>
|
||||
<Dialog
|
||||
as="div"
|
||||
className="fixed inset-0 z-10 overflow-y-auto bg-gray-500 bg-opacity-50"
|
||||
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>
|
||||
return (
|
||||
<>
|
||||
<Transition appear show={props.open} as={Fragment}>
|
||||
<Dialog
|
||||
as="div"
|
||||
className="fixed inset-0 z-10 overflow-y-auto bg-gray-500 bg-opacity-50"
|
||||
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"
|
||||
>
|
||||
​
|
||||
</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-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"
|
||||
>
|
||||
Payment successful 🎉
|
||||
</Dialog.Title>
|
||||
<div className="mt-2">
|
||||
<p>
|
||||
Your payment has been successfully submitted. Thank you for
|
||||
your support!
|
||||
</p>
|
||||
</div>
|
||||
{/* This element is to trick the browser into centering the modal contents. */}
|
||||
<span className="inline-block h-screen align-middle" aria-hidden="true">
|
||||
​
|
||||
</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-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">
|
||||
Payment successful 🎉
|
||||
</Dialog.Title>
|
||||
<div className="mt-2">
|
||||
<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}
|
||||
>
|
||||
Got it, thanks!
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
</>
|
||||
);
|
||||
<div className="mt-4">
|
||||
<button type="button" className="btn btn-accent flex m-auto" onClick={closeModal}>
|
||||
Got it, thanks!
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default PaymentModal;
|
||||
|
||||
@@ -16,264 +16,237 @@ import router from 'next/router';
|
||||
import { useAuth } from 'utils/AuthContext';
|
||||
|
||||
const Pricing = () => {
|
||||
const [enabled, setEnabled] = useState(false);
|
||||
const { user } = useAuth();
|
||||
const [customerId, setCustomerId] = useState(null);
|
||||
const [sub, setSub] = useState(false);
|
||||
const flat = false; // Switch between subscription system or flat prices
|
||||
const [enabled, setEnabled] = useState(false);
|
||||
const { user } = useAuth();
|
||||
const [customerId, setCustomerId] = useState(null);
|
||||
const [sub, setSub] = useState(false);
|
||||
const flat = false; // Switch between subscription system or flat prices
|
||||
|
||||
const portal = () => {
|
||||
axios
|
||||
.post('/api/stripe/customer-portal', {
|
||||
customerId,
|
||||
})
|
||||
.then((result) => {
|
||||
router.push(result.data.url);
|
||||
});
|
||||
};
|
||||
const portal = () => {
|
||||
axios
|
||||
.post('/api/stripe/customer-portal', {
|
||||
customerId,
|
||||
})
|
||||
.then((result) => {
|
||||
router.push(result.data.url);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (user) {
|
||||
getSub().then((result) => setSub(result));
|
||||
supabase
|
||||
.from('subscriptions')
|
||||
.select(`customer_id`)
|
||||
.eq('id', user.id)
|
||||
.single()
|
||||
.then((result) => {
|
||||
setCustomerId(result.data?.customer_id);
|
||||
});
|
||||
}
|
||||
}, [user]);
|
||||
useEffect(() => {
|
||||
if (user) {
|
||||
getSub().then((result) => setSub(result));
|
||||
supabase
|
||||
.from('subscriptions')
|
||||
.select(`customer_id`)
|
||||
.eq('id', user.id)
|
||||
.single()
|
||||
.then((result) => {
|
||||
setCustomerId(result.data?.customer_id);
|
||||
});
|
||||
}
|
||||
}, [user]);
|
||||
|
||||
const pricing = {
|
||||
monthly: {
|
||||
personal: '$5/mo',
|
||||
team: '$15/mo',
|
||||
pro: '$35/mo',
|
||||
},
|
||||
yearly: {
|
||||
personal: '$50/yr',
|
||||
team: '$150/yr',
|
||||
pro: '$350/yr',
|
||||
},
|
||||
flat: {
|
||||
personal: '€49',
|
||||
team: '€99',
|
||||
pro: '€149',
|
||||
},
|
||||
};
|
||||
const pricing = {
|
||||
monthly: {
|
||||
personal: '$5/mo',
|
||||
team: '$15/mo',
|
||||
pro: '$35/mo',
|
||||
},
|
||||
yearly: {
|
||||
personal: '$50/yr',
|
||||
team: '$150/yr',
|
||||
pro: '$350/yr',
|
||||
},
|
||||
flat: {
|
||||
personal: '€49',
|
||||
team: '€99',
|
||||
pro: '€149',
|
||||
},
|
||||
};
|
||||
|
||||
const handleSubmit = async (e, priceId) => {
|
||||
e.preventDefault();
|
||||
// Create a Checkout Session. This will redirect the user to the Stripe website for the payment.
|
||||
axios
|
||||
.post('/api/stripe/create-checkout-session', {
|
||||
priceId,
|
||||
email: user.email,
|
||||
customerId,
|
||||
userId: user.id,
|
||||
tokenId: session.access_token,
|
||||
pay_mode: flat ? 'payment' : 'subscription',
|
||||
})
|
||||
.then((result) => router.push(result.data.url));
|
||||
};
|
||||
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">
|
||||
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>
|
||||
</div>
|
||||
<div>
|
||||
<Switch
|
||||
checked={enabled}
|
||||
onChange={setEnabled}
|
||||
className={`bg-primary relative inline-flex flex-shrink-0 h-[38px] w-[74px]
|
||||
const handleSubmit = async (e, priceId) => {
|
||||
e.preventDefault();
|
||||
// Create a Checkout Session. This will redirect the user to the Stripe website for the payment.
|
||||
axios
|
||||
.post('/api/stripe/create-checkout-session', {
|
||||
priceId,
|
||||
email: user.email,
|
||||
customerId,
|
||||
userId: user.id,
|
||||
tokenId: session.access_token,
|
||||
pay_mode: flat ? 'payment' : 'subscription',
|
||||
})
|
||||
.then((result) => router.push(result.data.url));
|
||||
};
|
||||
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">
|
||||
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>
|
||||
</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'}
|
||||
>
|
||||
<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 space-x-4">
|
||||
<div className="w-full md:w-1/3 md:max-w-none 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">
|
||||
{flat
|
||||
? pricing.flat.personal
|
||||
: enabled
|
||||
? pricing.yearly.personal
|
||||
: pricing.monthly.personal}
|
||||
</h3>
|
||||
<ul className="text-sm px-5 mb-8 text-left">
|
||||
<li className="leading-tight">
|
||||
<i className="mdi mdi-check-bold text-lg" /> A cool feature
|
||||
</li>
|
||||
<li className="leading-tight">
|
||||
<i className="mdi mdi-check-bold text-lg" /> Another feature
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="w-full">
|
||||
<button
|
||||
className="btn btn-primary w-full"
|
||||
onClick={
|
||||
user
|
||||
? sub
|
||||
? () => {
|
||||
portal();
|
||||
}
|
||||
: (e) =>
|
||||
handleSubmit(
|
||||
e,
|
||||
enabled
|
||||
? Prices.personal.annually.id
|
||||
: Prices.personal.monthly.id
|
||||
)
|
||||
: () => {
|
||||
router.push('/auth');
|
||||
}
|
||||
}
|
||||
>
|
||||
{user ? (sub ? 'Upgrade' : 'Buy Now') : 'Register'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full md:w-1/3 md:max-w-none 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">Team</h2>
|
||||
<h3 className="text-center font-bold text-4xl mb-5">
|
||||
{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">
|
||||
<i className="mdi mdi-check-bold text-lg" /> All basic features
|
||||
</li>
|
||||
<li className="leading-tight">
|
||||
<i className="mdi mdi-check-bold text-lg" /> Dolor sit amet
|
||||
</li>
|
||||
<li className="leading-tight">
|
||||
<i className="mdi mdi-check-bold text-lg" /> Consectetur
|
||||
</li>
|
||||
<li className="leading-tight">
|
||||
<i className="mdi mdi-check-bold text-lg" /> Adipisicing
|
||||
</li>
|
||||
<li className="leading-tight">
|
||||
<i className="mdi mdi-check-bold text-lg" /> Elit repellat
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="w-full">
|
||||
<button
|
||||
className="btn btn-primary w-full"
|
||||
onClick={
|
||||
user
|
||||
? sub
|
||||
? () => {
|
||||
portal();
|
||||
}
|
||||
: (e) =>
|
||||
handleSubmit(
|
||||
e,
|
||||
enabled
|
||||
? Prices.team.annually.id
|
||||
: Prices.team.monthly.id
|
||||
)
|
||||
: () => {
|
||||
router.push('/auth');
|
||||
}
|
||||
}
|
||||
>
|
||||
{user ? (sub ? 'Upgrade' : 'Buy Now') : 'Register'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full md:w-1/3 md:max-w-none 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">
|
||||
{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">
|
||||
<i className="mdi mdi-check-bold text-lg" /> Lorem ipsum
|
||||
</li>
|
||||
<li className="leading-tight">
|
||||
<i className="mdi mdi-check-bold text-lg" /> Dolor sit amet
|
||||
</li>
|
||||
<li className="leading-tight">
|
||||
<i className="mdi mdi-check-bold text-lg" /> Consectetur
|
||||
</li>
|
||||
<li className="leading-tight">
|
||||
<i className="mdi mdi-check-bold text-lg" /> Adipisicing
|
||||
</li>
|
||||
<li className="leading-tight">
|
||||
<i className="mdi mdi-check-bold text-lg" /> Much more...
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="w-full">
|
||||
<button
|
||||
className="btn btn-primary w-full"
|
||||
onClick={
|
||||
user
|
||||
? sub
|
||||
? () => {
|
||||
portal();
|
||||
}
|
||||
: (e) =>
|
||||
handleSubmit(
|
||||
e,
|
||||
enabled
|
||||
? Prices.pro.annually.id
|
||||
: Prices.pro.monthly.id
|
||||
)
|
||||
: () => {
|
||||
router.push('/auth');
|
||||
}
|
||||
}
|
||||
>
|
||||
{user ? (sub ? 'Upgrade' : 'Buy Now') : 'Register'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
/>
|
||||
</Switch>
|
||||
</div>
|
||||
<div>
|
||||
<p className={`${!enabled ? 'text-gray-500' : null}`}>Billed annually</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="max-w-4xl mx-auto md:flex space-x-4">
|
||||
<div className="w-full md:w-1/3 md:max-w-none 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">
|
||||
{flat
|
||||
? pricing.flat.personal
|
||||
: enabled
|
||||
? pricing.yearly.personal
|
||||
: pricing.monthly.personal}
|
||||
</h3>
|
||||
<ul className="text-sm px-5 mb-8 text-left">
|
||||
<li className="leading-tight">
|
||||
<i className="mdi mdi-check-bold text-lg" /> A cool feature
|
||||
</li>
|
||||
<li className="leading-tight">
|
||||
<i className="mdi mdi-check-bold text-lg" /> Another feature
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="w-full">
|
||||
<button
|
||||
className="btn btn-primary w-full"
|
||||
onClick={
|
||||
user
|
||||
? sub
|
||||
? () => {
|
||||
portal();
|
||||
}
|
||||
: (e) =>
|
||||
handleSubmit(
|
||||
e,
|
||||
enabled ? Prices.personal.annually.id : Prices.personal.monthly.id
|
||||
)
|
||||
: () => {
|
||||
router.push('/auth');
|
||||
}
|
||||
}
|
||||
>
|
||||
{user ? (sub ? 'Upgrade' : 'Buy Now') : 'Register'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full md:w-1/3 md:max-w-none 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">Team</h2>
|
||||
<h3 className="text-center font-bold text-4xl mb-5">
|
||||
{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">
|
||||
<i className="mdi mdi-check-bold text-lg" /> All basic features
|
||||
</li>
|
||||
<li className="leading-tight">
|
||||
<i className="mdi mdi-check-bold text-lg" /> Dolor sit amet
|
||||
</li>
|
||||
<li className="leading-tight">
|
||||
<i className="mdi mdi-check-bold text-lg" /> Consectetur
|
||||
</li>
|
||||
<li className="leading-tight">
|
||||
<i className="mdi mdi-check-bold text-lg" /> Adipisicing
|
||||
</li>
|
||||
<li className="leading-tight">
|
||||
<i className="mdi mdi-check-bold text-lg" /> Elit repellat
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="w-full">
|
||||
<button
|
||||
className="btn btn-primary w-full"
|
||||
onClick={
|
||||
user
|
||||
? sub
|
||||
? () => {
|
||||
portal();
|
||||
}
|
||||
: (e) =>
|
||||
handleSubmit(e, enabled ? Prices.team.annually.id : Prices.team.monthly.id)
|
||||
: () => {
|
||||
router.push('/auth');
|
||||
}
|
||||
}
|
||||
>
|
||||
{user ? (sub ? 'Upgrade' : 'Buy Now') : 'Register'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full md:w-1/3 md:max-w-none 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">
|
||||
{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">
|
||||
<i className="mdi mdi-check-bold text-lg" /> Lorem ipsum
|
||||
</li>
|
||||
<li className="leading-tight">
|
||||
<i className="mdi mdi-check-bold text-lg" /> Dolor sit amet
|
||||
</li>
|
||||
<li className="leading-tight">
|
||||
<i className="mdi mdi-check-bold text-lg" /> Consectetur
|
||||
</li>
|
||||
<li className="leading-tight">
|
||||
<i className="mdi mdi-check-bold text-lg" /> Adipisicing
|
||||
</li>
|
||||
<li className="leading-tight">
|
||||
<i className="mdi mdi-check-bold text-lg" /> Much more...
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="w-full">
|
||||
<button
|
||||
className="btn btn-primary w-full"
|
||||
onClick={
|
||||
user
|
||||
? sub
|
||||
? () => {
|
||||
portal();
|
||||
}
|
||||
: (e) =>
|
||||
handleSubmit(e, enabled ? Prices.pro.annually.id : Prices.pro.monthly.id)
|
||||
: () => {
|
||||
router.push('/auth');
|
||||
}
|
||||
}
|
||||
>
|
||||
{user ? (sub ? 'Upgrade' : 'Buy Now') : 'Register'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Pricing;
|
||||
|
||||
@@ -1,159 +1,135 @@
|
||||
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>
|
||||
<div className="max-w-xl text-left m-auto py-10">
|
||||
<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
|
||||
{process.env.NEXT_PUBLIC_TITLE} and how we use it.
|
||||
</p>
|
||||
<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
|
||||
{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.
|
||||
</p>
|
||||
<p>
|
||||
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>
|
||||
<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:
|
||||
</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>
|
||||
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} 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>
|
||||
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.
|
||||
</p>
|
||||
<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.
|
||||
</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:
|
||||
</p>
|
||||
<ul>
|
||||
<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>
|
||||
<li>The right to data portability</li>
|
||||
<li>The right to withdraw consent</li>
|
||||
</ul>
|
||||
<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:</p>
|
||||
<ul>
|
||||
<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>
|
||||
<li>The right to data portability</li>
|
||||
<li>The right to withdraw consent</li>
|
||||
</ul>
|
||||
|
||||
<h2>Log Files</h2>
|
||||
<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.
|
||||
</p>
|
||||
<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.
|
||||
</p>
|
||||
|
||||
<h2>Privacy Policies</h2>
|
||||
<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}.
|
||||
</p>
|
||||
<p>
|
||||
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.
|
||||
</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.
|
||||
</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.
|
||||
</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.
|
||||
</p>
|
||||
|
||||
<h2>Third Party Privacy Policies</h2>
|
||||
<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.{' '}
|
||||
</p>
|
||||
<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.{' '}
|
||||
</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.
|
||||
</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.
|
||||
</p>
|
||||
|
||||
<h2>Children's Information</h2>
|
||||
<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.
|
||||
</p>
|
||||
<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.
|
||||
</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.
|
||||
</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.
|
||||
</p>
|
||||
|
||||
<h2>Online Privacy Policy Only</h2>
|
||||
<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.
|
||||
</p>
|
||||
<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.
|
||||
</p>
|
||||
|
||||
<h2>Consent</h2>
|
||||
<h2>Consent</h2>
|
||||
|
||||
<p>
|
||||
By using our website, you hereby consent to our Privacy Policy and agree
|
||||
to its terms.
|
||||
</p>
|
||||
</div>
|
||||
<p>By using our website, you hereby consent to our Privacy Policy and agree to its terms.</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default PrivacyPolicy;
|
||||
|
||||
@@ -12,31 +12,31 @@ import { useAuth } from 'utils/AuthContext';
|
||||
import SignUpPanel from './UI/SignUpPanel';
|
||||
|
||||
const Container = (props) => {
|
||||
const { user, signOut } = useAuth();
|
||||
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={() => signOut()}>
|
||||
Sign out
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
return props.children;
|
||||
const { user, signOut } = useAuth();
|
||||
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={() => signOut()}>
|
||||
Sign out
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
return props.children;
|
||||
};
|
||||
|
||||
const AuthComponent = () => {
|
||||
const { signUp, signIn, signOut, resetPassword } = useAuth();
|
||||
return (
|
||||
<Container supabaseClient={supabase}>
|
||||
<SignUpPanel
|
||||
signUp={signUp}
|
||||
signIn={signIn}
|
||||
signOut={signOut}
|
||||
resetPassword={resetPassword}
|
||||
/>
|
||||
</Container>
|
||||
);
|
||||
const { signUp, signIn, signOut, resetPassword } = useAuth();
|
||||
return (
|
||||
<Container supabaseClient={supabase}>
|
||||
<SignUpPanel
|
||||
signUp={signUp}
|
||||
signIn={signIn}
|
||||
signOut={signOut}
|
||||
resetPassword={resetPassword}
|
||||
/>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default AuthComponent;
|
||||
|
||||
@@ -1,235 +1,201 @@
|
||||
const Terms = () => (
|
||||
<div className="max-w-xl text-left m-auto py-10">
|
||||
<h1>Terms and Conditions</h1>
|
||||
<div className="max-w-xl text-left m-auto py-10">
|
||||
<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").
|
||||
</p>
|
||||
<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").
|
||||
</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.
|
||||
</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.
|
||||
</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.
|
||||
</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.
|
||||
</p>
|
||||
|
||||
<h2>Conduct on Website</h2>
|
||||
<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.
|
||||
</p>
|
||||
<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.
|
||||
</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:
|
||||
</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:
|
||||
</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
|
||||
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
|
||||
</li>
|
||||
<li>
|
||||
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
|
||||
</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
|
||||
information of any third party
|
||||
</li>
|
||||
<li>
|
||||
Impersonates any person or entity, including any of our employees or
|
||||
representatives
|
||||
</li>
|
||||
</ul>
|
||||
<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
|
||||
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
|
||||
</li>
|
||||
<li>
|
||||
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
|
||||
</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
|
||||
information of any third party
|
||||
</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.
|
||||
</p>
|
||||
<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.
|
||||
</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.
|
||||
</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.
|
||||
</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.
|
||||
</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.
|
||||
</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.
|
||||
</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.
|
||||
</p>
|
||||
|
||||
<h2>Intellectual Property</h2>
|
||||
<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{' '}
|
||||
<span className="website_name">{process.env.NEXT_PUBLIC_TITLE}</span>.
|
||||
</p>
|
||||
<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{' '}
|
||||
<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.
|
||||
</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.
|
||||
</p>
|
||||
|
||||
<h2>Third Party Websites</h2>
|
||||
<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.
|
||||
</p>
|
||||
<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.
|
||||
</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.
|
||||
</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.
|
||||
</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.
|
||||
</p>
|
||||
<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.
|
||||
</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.
|
||||
</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.
|
||||
</p>
|
||||
|
||||
<h2>Term and termination</h2>
|
||||
<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.{' '}
|
||||
</p>
|
||||
<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.{' '}
|
||||
</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.
|
||||
</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.
|
||||
</p>
|
||||
|
||||
<h2>Assignment</h2>
|
||||
<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.
|
||||
</p>
|
||||
<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.
|
||||
</p>
|
||||
|
||||
<h2>Governing Law</h2>
|
||||
<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.
|
||||
</p>
|
||||
<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.
|
||||
</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>.
|
||||
</p>
|
||||
</div>
|
||||
<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>.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default Terms;
|
||||
|
||||
@@ -5,22 +5,22 @@ This card is used on the landing page
|
||||
import Image from 'next/image';
|
||||
|
||||
const CardLanding = (props) => {
|
||||
const { image, title, text } = props;
|
||||
return (
|
||||
<div className="w-80 h-48 p-5 sm:ml-5 mb-5 bg-base-100 flex">
|
||||
<div>
|
||||
<div className="rounded-full w-12 h-12 border flex bg-neutral-content">
|
||||
<div className="m-auto flex">
|
||||
<Image src={image} width={24} height={24} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="ml-8">
|
||||
<p className="font-semibold font-title text-lg">{title}</p>
|
||||
<p className="mt-3">{text}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
const { image, title, text } = props;
|
||||
return (
|
||||
<div className="w-80 h-48 p-5 sm:ml-5 mb-5 bg-base-100 flex">
|
||||
<div>
|
||||
<div className="rounded-full w-12 h-12 border flex bg-neutral-content">
|
||||
<div className="m-auto flex">
|
||||
<Image src={image} width={24} height={24} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="ml-8">
|
||||
<p className="font-semibold font-title text-lg">{title}</p>
|
||||
<p className="mt-3">{text}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CardLanding;
|
||||
|
||||
@@ -5,12 +5,12 @@ This card is used on the landing page
|
||||
import { FiStar } from 'react-icons/fi';
|
||||
|
||||
const KeyFeature = (props) => (
|
||||
<div className="shadow-sm p-5 mb-5 bg-base-100 flex italic">
|
||||
<div className="p-2 bg-accent-focus w-12 h-12 text-white rounded-sm my-auto flex">
|
||||
<FiStar className="text-2xl m-auto" />
|
||||
</div>
|
||||
<div className="m-auto ml-3">{props.children}</div>
|
||||
</div>
|
||||
<div className="shadow-sm p-5 mb-5 bg-base-100 flex italic">
|
||||
<div className="p-2 bg-accent-focus w-12 h-12 text-white rounded-sm my-auto flex">
|
||||
<FiStar className="text-2xl m-auto" />
|
||||
</div>
|
||||
<div className="m-auto ml-3">{props.children}</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default KeyFeature;
|
||||
|
||||
@@ -4,170 +4,163 @@ import { toast } from 'react-toastify';
|
||||
import { useState } from 'react';
|
||||
|
||||
const Login = (props) => {
|
||||
const [email, setEmail] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [forgot, setForgot] = useState(false);
|
||||
const [email, setEmail] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [forgot, setForgot] = useState(false);
|
||||
|
||||
const resetPassword = () => {
|
||||
props.resetPassword(email).then((result) => {
|
||||
if (result.error) {
|
||||
toast.error(result.error.message);
|
||||
} else toast.success('Check your email to reset your password!');
|
||||
});
|
||||
};
|
||||
const resetPassword = () => {
|
||||
props.resetPassword(email).then((result) => {
|
||||
if (result.error) {
|
||||
toast.error(result.error.message);
|
||||
} else toast.success('Check your email to reset your password!');
|
||||
});
|
||||
};
|
||||
|
||||
const login = (e) => {
|
||||
e.preventDefault();
|
||||
const login = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
// Handle the login. Go to the homepage if success or display an error.
|
||||
props
|
||||
.signIn({
|
||||
email,
|
||||
password,
|
||||
})
|
||||
.then((result) => {
|
||||
if (result.data) {
|
||||
router.push('/');
|
||||
}
|
||||
if (result.error) {
|
||||
toast.error(result.error.message);
|
||||
}
|
||||
});
|
||||
};
|
||||
// Handle the login. Go to the homepage if success or display an error.
|
||||
props
|
||||
.signIn({
|
||||
email,
|
||||
password,
|
||||
})
|
||||
.then((result) => {
|
||||
if (result.data) {
|
||||
router.push('/');
|
||||
}
|
||||
if (result.error) {
|
||||
toast.error(result.error.message);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<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>
|
||||
<form action="#" className="flex flex-col space-y-5">
|
||||
<div className="flex flex-col space-y-1">
|
||||
<label htmlFor="email" className="text-sm">
|
||||
Email address
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
autoFocus
|
||||
className="input input-primary input-bordered input-sm"
|
||||
value={email}
|
||||
onChange={(event) => {
|
||||
setEmail(event.target.value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col space-y-1">
|
||||
<div className="flex items-center justify-between">
|
||||
<label htmlFor="password" className="text-sm">
|
||||
Password
|
||||
</label>
|
||||
<button
|
||||
onClick={() => {
|
||||
setForgot(true);
|
||||
}}
|
||||
className="text-sm text-blue-600 hover:underline focus:text-blue-800"
|
||||
>
|
||||
Forgot Password?
|
||||
</button>
|
||||
</div>
|
||||
<input
|
||||
type="password"
|
||||
id="password"
|
||||
className="input input-primary input-bordered input-sm"
|
||||
value={password}
|
||||
onChange={(event) => {
|
||||
setPassword(event.target.value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
return (
|
||||
<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>
|
||||
<form action="#" className="flex flex-col space-y-5">
|
||||
<div className="flex flex-col space-y-1">
|
||||
<label htmlFor="email" className="text-sm">
|
||||
Email address
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
autoFocus
|
||||
className="input input-primary input-bordered input-sm"
|
||||
value={email}
|
||||
onChange={(event) => {
|
||||
setEmail(event.target.value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col space-y-1">
|
||||
<div className="flex items-center justify-between">
|
||||
<label htmlFor="password" className="text-sm">
|
||||
Password
|
||||
</label>
|
||||
<button
|
||||
onClick={() => {
|
||||
setForgot(true);
|
||||
}}
|
||||
className="text-sm text-blue-600 hover:underline focus:text-blue-800"
|
||||
>
|
||||
Forgot Password?
|
||||
</button>
|
||||
</div>
|
||||
<input
|
||||
type="password"
|
||||
id="password"
|
||||
className="input input-primary input-bordered input-sm"
|
||||
value={password}
|
||||
onChange={(event) => {
|
||||
setPassword(event.target.value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button
|
||||
className="btn btn-primary w-full"
|
||||
onClick={(event) => {
|
||||
login(event);
|
||||
}}
|
||||
>
|
||||
Log in
|
||||
</button>
|
||||
</div>
|
||||
<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 login with</span>
|
||||
<span className="h-px bg-gray-400 w-14" />
|
||||
</span>
|
||||
<div className="flex flex-col space-y-4">
|
||||
<button
|
||||
href="#"
|
||||
className="flex items-center justify-center px-4 py-2 space-x-2 transition-colors duration-300 border border-base-200 rounded-md group hover:bg-base-300 focus:outline-none "
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
props.signIn({ provider: 'google' });
|
||||
}}
|
||||
>
|
||||
<div className="text-base-content">
|
||||
<IoLogoGoogle />
|
||||
</div>
|
||||
<span className="text-sm font-medium text-base-content">
|
||||
Gmail
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</>
|
||||
)}
|
||||
{forgot && (
|
||||
<>
|
||||
<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"
|
||||
>
|
||||
Email address
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
autoFocus
|
||||
className="input input-primary input-bordered input-sm"
|
||||
value={email}
|
||||
onChange={(event) => {
|
||||
setEmail(event.target.value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
className="btn btn-primary w-full"
|
||||
onClick={(event) => {
|
||||
login(event);
|
||||
}}
|
||||
>
|
||||
Log in
|
||||
</button>
|
||||
</div>
|
||||
<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 login with</span>
|
||||
<span className="h-px bg-gray-400 w-14" />
|
||||
</span>
|
||||
<div className="flex flex-col space-y-4">
|
||||
<button
|
||||
href="#"
|
||||
className="flex items-center justify-center px-4 py-2 space-x-2 transition-colors duration-300 border border-base-200 rounded-md group hover:bg-base-300 focus:outline-none "
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
props.signIn({ provider: 'google' });
|
||||
}}
|
||||
>
|
||||
<div className="text-base-content">
|
||||
<IoLogoGoogle />
|
||||
</div>
|
||||
<span className="text-sm font-medium text-base-content">Gmail</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</>
|
||||
)}
|
||||
{forgot && (
|
||||
<>
|
||||
<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">
|
||||
Email address
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
autoFocus
|
||||
className="input input-primary input-bordered input-sm"
|
||||
value={email}
|
||||
onChange={(event) => {
|
||||
setEmail(event.target.value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button
|
||||
className="btn btn-primary w-full btn-sm"
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
resetPassword();
|
||||
}}
|
||||
>
|
||||
Recover my password
|
||||
</button>
|
||||
</div>
|
||||
<hr />
|
||||
<button
|
||||
onClick={() => {
|
||||
setForgot(false);
|
||||
}}
|
||||
className="text-sm text-blue-600 hover:underline focus:text-blue-800"
|
||||
>
|
||||
Go back to sign in
|
||||
</button>
|
||||
</form>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
<div>
|
||||
<button
|
||||
className="btn btn-primary w-full btn-sm"
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
resetPassword();
|
||||
}}
|
||||
>
|
||||
Recover my password
|
||||
</button>
|
||||
</div>
|
||||
<hr />
|
||||
<button
|
||||
onClick={() => {
|
||||
setForgot(false);
|
||||
}}
|
||||
className="text-sm text-blue-600 hover:underline focus:text-blue-800"
|
||||
>
|
||||
Go back to sign in
|
||||
</button>
|
||||
</form>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Login;
|
||||
|
||||
@@ -4,113 +4,107 @@ import { toast } from 'react-toastify';
|
||||
import { useState } from 'react';
|
||||
|
||||
const SignUpPanel = (props) => {
|
||||
const [email, setEmail] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [forgot, setForgot] = useState(false);
|
||||
const [email, setEmail] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [forgot, setForgot] = useState(false);
|
||||
|
||||
const resetPassword = () => {
|
||||
props.resetPassword(email).then((result) => {
|
||||
if (result.error) {
|
||||
toast.error(result.error.message);
|
||||
} else toast.success('Check your email to reset your password!');
|
||||
});
|
||||
};
|
||||
const resetPassword = () => {
|
||||
props.resetPassword(email).then((result) => {
|
||||
if (result.error) {
|
||||
toast.error(result.error.message);
|
||||
} else toast.success('Check your email to reset your password!');
|
||||
});
|
||||
};
|
||||
|
||||
const signup = (e) => {
|
||||
e.preventDefault();
|
||||
const signup = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
// Handle the login. Go to the homepage if success or display an error.
|
||||
props
|
||||
.signUp({
|
||||
email,
|
||||
password,
|
||||
})
|
||||
.then((result) => {
|
||||
if (result.data) {
|
||||
router.push('/');
|
||||
}
|
||||
if (result.error) {
|
||||
toast.error(result.error.message);
|
||||
}
|
||||
});
|
||||
};
|
||||
// Handle the login. Go to the homepage if success or display an error.
|
||||
props
|
||||
.signUp({
|
||||
email,
|
||||
password,
|
||||
})
|
||||
.then((result) => {
|
||||
if (result.data) {
|
||||
router.push('/');
|
||||
}
|
||||
if (result.error) {
|
||||
toast.error(result.error.message);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<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>
|
||||
<form action="#" className="flex flex-col space-y-5">
|
||||
<div className="flex flex-col space-y-1">
|
||||
<label htmlFor="email" className="text-sm">
|
||||
Email address
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
autoFocus
|
||||
className="input input-primary input-bordered input-sm"
|
||||
value={email}
|
||||
onChange={(event) => {
|
||||
setEmail(event.target.value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col space-y-1">
|
||||
<input
|
||||
type="password"
|
||||
id="password"
|
||||
className="input input-primary input-bordered input-sm"
|
||||
value={password}
|
||||
onChange={(event) => {
|
||||
setPassword(event.target.value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
return (
|
||||
<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>
|
||||
<form action="#" className="flex flex-col space-y-5">
|
||||
<div className="flex flex-col space-y-1">
|
||||
<label htmlFor="email" className="text-sm">
|
||||
Email address
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
autoFocus
|
||||
className="input input-primary input-bordered input-sm"
|
||||
value={email}
|
||||
onChange={(event) => {
|
||||
setEmail(event.target.value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col space-y-1">
|
||||
<input
|
||||
type="password"
|
||||
id="password"
|
||||
className="input input-primary input-bordered input-sm"
|
||||
value={password}
|
||||
onChange={(event) => {
|
||||
setPassword(event.target.value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button
|
||||
className="btn btn-primary w-full"
|
||||
onClick={(event) => {
|
||||
signup(event);
|
||||
}}
|
||||
>
|
||||
Sign Up
|
||||
</button>
|
||||
</div>
|
||||
<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="h-px bg-gray-400 w-14" />
|
||||
</span>
|
||||
<div className="flex flex-col space-y-4">
|
||||
<button
|
||||
href="#"
|
||||
className="flex items-center justify-center px-4 py-2 space-x-2 transition-colors duration-300 border border-base-200 rounded-md group hover:bg-base-300 focus:outline-none "
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
props.signIn({ provider: 'google' });
|
||||
}}
|
||||
>
|
||||
<div className="text-base-content">
|
||||
<IoLogoGoogle />
|
||||
</div>
|
||||
<span className="text-sm font-medium text-base-content">
|
||||
Gmail
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
<div>
|
||||
<button
|
||||
className="btn btn-primary w-full"
|
||||
onClick={(event) => {
|
||||
signup(event);
|
||||
}}
|
||||
>
|
||||
Sign Up
|
||||
</button>
|
||||
</div>
|
||||
<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="h-px bg-gray-400 w-14" />
|
||||
</span>
|
||||
<div className="flex flex-col space-y-4">
|
||||
<button
|
||||
href="#"
|
||||
className="flex items-center justify-center px-4 py-2 space-x-2 transition-colors duration-300 border border-base-200 rounded-md group hover:bg-base-300 focus:outline-none "
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
props.signIn({ provider: 'google' });
|
||||
}}
|
||||
>
|
||||
<div className="text-base-content">
|
||||
<IoLogoGoogle />
|
||||
</div>
|
||||
<span className="text-sm font-medium text-base-content">Gmail</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SignUpPanel;
|
||||
|
||||
@@ -7,28 +7,28 @@ import { HiOutlineMoon, HiOutlineSun } from 'react-icons/hi';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
const theme = {
|
||||
primary: 'supaTheme',
|
||||
secondary: 'dark',
|
||||
primary: 'supaTheme',
|
||||
secondary: 'dark',
|
||||
};
|
||||
|
||||
const ThemeToggle = () => {
|
||||
const [activeTheme, setActiveTheme] = useState(document.body.dataset.theme);
|
||||
const inactiveTheme = activeTheme === 'supaTheme' ? 'dark' : 'supaTheme';
|
||||
const [activeTheme, setActiveTheme] = useState(document.body.dataset.theme);
|
||||
const inactiveTheme = activeTheme === 'supaTheme' ? 'dark' : 'supaTheme';
|
||||
|
||||
useEffect(() => {
|
||||
document.body.dataset.theme = activeTheme;
|
||||
window.localStorage.setItem('theme', activeTheme);
|
||||
}, [activeTheme]);
|
||||
useEffect(() => {
|
||||
document.body.dataset.theme = activeTheme;
|
||||
window.localStorage.setItem('theme', activeTheme);
|
||||
}, [activeTheme]);
|
||||
|
||||
return (
|
||||
<button className="flex ml-3" onClick={() => setActiveTheme(inactiveTheme)}>
|
||||
{activeTheme === theme.secondary ? (
|
||||
<HiOutlineSun className="m-auto text-xl hover:text-accent" />
|
||||
) : (
|
||||
<HiOutlineMoon className="m-auto text-xl hover:text-accent" />
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
return (
|
||||
<button className="flex ml-3" onClick={() => setActiveTheme(inactiveTheme)}>
|
||||
{activeTheme === theme.secondary ? (
|
||||
<HiOutlineSun className="m-auto text-xl hover:text-accent" />
|
||||
) : (
|
||||
<HiOutlineMoon className="m-auto text-xl hover:text-accent" />
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export default ThemeToggle;
|
||||
|
||||
Reference in New Issue
Block a user