chore:gallery,comissions,subscription pages, + navbar

This commit is contained in:
Damien Ostler 2024-05-24 19:30:37 -04:00
parent f6871f4934
commit ea8a876b6f
96 changed files with 194 additions and 345 deletions

18
app/commissions/page.jsx Normal file
View File

@ -0,0 +1,18 @@
import { createClient } from "@/utils/supabase/server";
import { redirect } from "next/navigation";
export default async function Commissions() {
const supabase = createClient();
const {
data: { user },
} = await supabase.auth.getUser();
if (!user) {
return redirect("/login");
}
return (
<h1>This is protected.</h1>
);
}

14
app/gallery/page.jsx Normal file
View File

@ -0,0 +1,14 @@
import { createClient } from "@/utils/supabase/server";
import { redirect } from "next/navigation";
export default async function Gallery() {
const supabase = createClient();
const {
data: { user },
} = await supabase.auth.getUser();
return (
<h1>This is unprotected.</h1>
);
}

View File

@ -1,7 +1,6 @@
import { GeistSans } from "geist/font/sans";
import "./globals.css";
import AuthButton from "@/components/AuthButton";
import DeployButton from "@/components/DeployButton";
import NavigationBar from "@/components/NavigationBar";
const defaultUrl = process.env.VERCEL_URL
? `https://${process.env.VERCEL_URL}`
@ -22,14 +21,13 @@ export default function RootLayout({
<html lang="en" className={GeistSans.className}>
<body className="bg-background text-foreground">
<div className="w-full">
<nav className="w-full flex justify-center h-16">
<div className="w-full max-w-4xl flex justify-between items-center p-3 text-sm">
<AuthButton />
</div>
</nav>
<NavigationBar/>
</div>
<main className="min-h-screen flex flex-col items-center">
{children}
<div className="flex-1 w-full flex flex-col gap-20 items-center animate-in">
{children}
</div>
</main>
</body>
</html>

View File

@ -1,8 +1,4 @@
import DeployButton from "../components/DeployButton";
import AuthButton from "../components/AuthButton";
import { createClient } from "@/utils/supabase/server";
import ConnectSupabaseSteps from "@/components/tutorial/ConnectSupabaseSteps";
import SignUpUserSteps from "@/components/tutorial/SignUpUserSteps";
export default async function Index() {
const canInitSupabaseClient = () => {
@ -19,10 +15,6 @@ export default async function Index() {
const isSupabaseConnected = canInitSupabaseClient();
return (
<div className="flex-1 w-full flex flex-col gap-20 items-center">
<div className="animate-in flex-1 flex flex-col gap-20 opacity-0 max-w-4xl px-3">
</div>
</div>
<h1>This is unprotected.</h1>
);
}

View File

@ -1,7 +1,4 @@
import DeployButton from "@/components/DeployButton";
import AuthButton from "@/components/AuthButton";
import { createClient } from "@/utils/supabase/server";
import FetchDataSteps from "@/components/tutorial/FetchDataSteps";
import { redirect } from "next/navigation";
export default async function ProtectedPage() {
@ -16,28 +13,6 @@ export default async function ProtectedPage() {
}
return (
<div className="flex-1 w-full flex flex-col gap-20 items-center">
<div className="animate-in flex-1 flex flex-col gap-20 opacity-0 max-w-4xl px-3">
<main className="flex-1 flex flex-col gap-6">
<h2 className="font-bold text-4xl mb-4">Next steps</h2>
<FetchDataSteps />
</main>
</div>
<footer className="w-full border-t border-t-foreground/10 p-8 flex justify-center text-center text-xs">
<p>
Powered by{" "}
<a
href="https://supabase.com/?utm_source=create-next-app&utm_medium=template&utm_term=nextjs"
target="_blank"
className="font-bold hover:underline"
rel="noreferrer"
>
Supabase
</a>
</p>
</footer>
</div>
<h1>This is protected.</h1>
);
}

View File

@ -0,0 +1,18 @@
import { createClient } from "@/utils/supabase/server";
import { redirect } from "next/navigation";
export default async function Subscriptions() {
const supabase = createClient();
const {
data: { user },
} = await supabase.auth.getUser();
if (!user) {
return redirect("/login");
}
return (
<h1>This is protected.</h1>
);
}

View File

@ -1,37 +0,0 @@
import { createClient } from "@/utils/supabase/server";
import Link from "next/link";
import { redirect } from "next/navigation";
export default async function AuthButton() {
const supabase = createClient();
const {
data: { user },
} = await supabase.auth.getUser();
const signOut = async () => {
"use server";
const supabase = createClient();
await supabase.auth.signOut();
return redirect("/login");
};
return user ? (
<div className="flex items-center gap-4">
Hey, {user.email}!
<form action={signOut}>
<button className="py-2 px-4 rounded-md no-underline bg-btn-background hover:bg-btn-background-hover">
Logout
</button>
</form>
</div>
) : (
<Link
href="/login"
className="py-2 px-3 flex rounded-md no-underline bg-btn-background hover:bg-btn-background-hover"
>
Login
</Link>
);
}

View File

@ -0,0 +1,103 @@
import { createClient } from "@/utils/supabase/server";
import Link from "next/link";
import { redirect } from "next/navigation";
import crypto from 'crypto';
export default async function AuthButton() {
const supabase = createClient();
const {
data: { user },
} = await supabase.auth.getUser();
const signOut = async () => {
"use server";
const supabase = createClient();
await supabase.auth.signOut();
return redirect("/login");
};
if(user){
let email = user.email;
if(email != null){
const emailHash = crypto.createHash('md5').update(email.trim().toLowerCase()).digest('hex');
const gravatarUrl = `https://www.gravatar.com/avatar/${emailHash}`;
return(
<nav className="w-full flex justify-center h-16">
<div className="w-full max-w-2xl flex justify-between items-center p-3 text-sm">
<div className="flex items-center gap-2 ">
<Link
href="/gallery"
className="py-2 px-3 flex rounded-md no-underline bg-btn-background hover:bg-btn-background-hover"
>
Gallery
</Link>
<Link
href="/commissions"
className="py-2 px-3 flex rounded-md no-underline bg-btn-background hover:bg-btn-background-hover"
>
Commissions
</Link>
<Link
href="/subscriptions"
className="py-2 px-3 flex rounded-md no-underline bg-btn-background hover:bg-btn-background-hover"
>
Subscription
</Link>
</div>
<div className="flex items-center gap-2">
<img src={gravatarUrl} alt="Profile" className="w-10 h-10 object-cover rounded-full cursor-pointer" />
<form action={signOut}>
<button className="py-2 px-4 rounded-md no-underline bg-btn-background hover:bg-btn-background-hover">
Logout
</button>
</form>
</div>
</div>
</nav>)
}
}
else{
return(
<nav className="w-full flex justify-center h-16">
<div className="w-full max-w-2xl flex justify-between items-center p-3 text-sm">
<div className="flex items-center gap-2 ">
<Link
href="/gallery"
className="py-2 px-3 flex rounded-md no-underline bg-btn-background hover:bg-btn-background-hover"
>
Gallery
</Link>
<Link
href="/commissions"
className="py-2 px-3 flex rounded-md no-underline bg-btn-background hover:bg-btn-background-hover"
>
Commissions
</Link>
<Link
href="/subscriptions"
className="py-2 px-3 flex rounded-md no-underline bg-btn-background hover:bg-btn-background-hover"
>
Subscription
</Link>
</div>
<div className="flex items-center gap-2">
<Link
href="/login"
className="py-2 px-3 flex rounded-md no-underline bg-btn-background hover:bg-btn-background-hover"
>
Login
</Link>
<Link
href="/login"
className="py-2 px-3 flex rounded-md no-underline bg-btn-background hover:bg-btn-background-hover"
>
Signup
</Link>
</div>
</div>
</nav>)
}
}

View File

@ -1,58 +0,0 @@
"use client";
import { useState } from "react";
const CopyIcon = () => (
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
</svg>
);
const CheckIcon = () => (
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<polyline points="20 6 9 17 4 12"></polyline>
</svg>
);
export default function Code({ code }: { code: string }) {
const [icon, setIcon] = useState(CopyIcon);
const copy = async () => {
await navigator?.clipboard?.writeText(code);
setIcon(CheckIcon);
setTimeout(() => setIcon(CopyIcon), 2000);
};
return (
<pre className="bg-foreground/5 rounded-md p-8 my-8 relative">
<button
onClick={copy}
className="absolute top-4 right-4 p-2 rounded-md bg-foreground/5 hover:bg-foreground/10"
>
{icon}
</button>
<code>{code}</code>
</pre>
);
}

View File

@ -1,62 +0,0 @@
import Step from "./Step";
export default function ConnectSupabaseSteps() {
return (
<ol className="flex flex-col gap-6">
<Step title="Create Supabase project">
<p>
Head over to{" "}
<a
href="https://app.supabase.com/project/_/settings/api"
target="_blank"
className="font-bold hover:underline text-foreground/80"
rel="noreferrer"
>
database.new
</a>{" "}
and create a new Supabase project.
</p>
</Step>
<Step title="Declare environment variables">
<p>
Rename the{" "}
<span className="px-2 py-1 rounded-md bg-foreground/20 text-foreground/80">
.env.example
</span>{" "}
file in your Next.js app to{" "}
<span className="px-2 py-1 rounded-md bg-foreground/20 text-foreground/80">
.env.local
</span>{" "}
and populate with values from{" "}
<a
href="https://app.supabase.com/project/_/settings/api"
target="_blank"
className="font-bold hover:underline text-foreground/80"
rel="noreferrer"
>
your Supabase project's API Settings
</a>
.
</p>
</Step>
<Step title="Restart your Next.js development server">
<p>
You may need to quit your Next.js development server and run{" "}
<span className="px-2 py-1 rounded-md bg-foreground/20 text-foreground/80">
npm run dev
</span>{" "}
again to load the new environment variables.
</p>
</Step>
<Step title="Refresh the page">
<p>
You may need to refresh the page for Next.js to load the new
environment variables.
</p>
</Step>
</ol>
);
}

View File

@ -1,99 +0,0 @@
import Step from "./Step";
import Code from "./Code";
const create = `
create table notes (
id bigserial primary key,
title text
);
insert into notes(title)
values
('Today I created a Supabase project.'),
('I added some data and queried it from Next.js.'),
('It was awesome!');
`.trim();
const server = `
import { createClient } from '@/utils/supabase/server'
export default async function Page() {
const supabase = createClient()
const { data: notes } = await supabase.from('notes').select()
return <pre>{JSON.stringify(notes, null, 2)}</pre>
}
`.trim();
const client = `
'use client'
import { createClient } from '@/utils/supabase/client'
import { useEffect, useState } from 'react'
export default function Page() {
const [notes, setNotes] = useState<any[] | null>(null)
const supabase = createClient()
useEffect(() => {
const getData = async () => {
const { data } = await supabase.from('notes').select()
setNotes(data)
}
getData()
}, [])
return <pre>{JSON.stringify(notes, null, 2)}</pre>
}
`.trim();
export default function FetchDataSteps() {
return (
<ol className="flex flex-col gap-6">
<Step title="Create some tables and insert some data">
<p>
Head over to the{" "}
<a
href="https://supabase.com/dashboard/project/_/editor"
className="font-bold hover:underline text-foreground/80"
target="_blank"
rel="noreferrer"
>
Table Editor
</a>{" "}
for your Supabase project to create a table and insert some example
data. If you're stuck for creativity, you can copy and paste the
following into the{" "}
<a
href="https://supabase.com/dashboard/project/_/sql/new"
className="font-bold hover:underline text-foreground/80"
target="_blank"
rel="noreferrer"
>
SQL Editor
</a>{" "}
and click RUN!
</p>
<Code code={create} />
</Step>
<Step title="Query Supabase data from Next.js">
<p>
To create a Supabase client and query data from an Async Server
Component, create a new page.tsx file at{" "}
<span className="px-2 py-1 rounded-md bg-foreground/20 text-foreground/80">
/app/notes/page.tsx
</span>{" "}
and add the following.
</p>
<Code code={server} />
<p>Alternatively, you can use a Client Component.</p>
<Code code={client} />
</Step>
<Step title="Build in a weekend and scale to millions!">
<p>You're ready to launch your product to the world! 🚀</p>
</Step>
</ol>
);
}

View File

@ -1,22 +0,0 @@
import Link from "next/link";
import Step from "./Step";
export default function SignUpUserSteps() {
return (
<ol className="flex flex-col gap-6">
<Step title="Sign up your first user">
<p>
Head over to the{" "}
<Link
href="/login"
className="font-bold hover:underline text-foreground/80"
>
Login
</Link>{" "}
page and sign up your first user. It's okay if this is just you for
now. Your awesome idea will have plenty of users later!
</p>
</Step>
</ol>
);
}

View File

@ -1,24 +0,0 @@
export default function Step({
title,
children,
}: {
title: string;
children: React.ReactNode;
}) {
return (
<li className="mx-4">
<input type="checkbox" id={title} className={`mr-2 peer`} />
<label
htmlFor={title}
className={`text-lg text-foreground/90 peer-checked:line-through font-semibold hover:cursor-pointer`}
>
{title}
</label>
<div
className={`mx-6 text-foreground/80 text-sm peer-checked:line-through`}
>
{children}
</div>
</li>
);
}

32
package-lock.json generated
View File

@ -9,6 +9,7 @@
"@supabase/supabase-js": "latest",
"autoprefixer": "10.4.17",
"geist": "^1.2.1",
"md5": "^2.3.0",
"next": "latest",
"postcss": "8.4.33",
"react": "18.2.0",
@ -604,6 +605,14 @@
}
]
},
"node_modules/charenc": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz",
"integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==",
"engines": {
"node": "*"
}
},
"node_modules/chokidar": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
@ -688,6 +697,14 @@
"node": ">= 8"
}
},
"node_modules/crypt": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz",
"integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==",
"engines": {
"node": "*"
}
},
"node_modules/cssesc": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
@ -919,6 +936,11 @@
"node": ">=8"
}
},
"node_modules/is-buffer": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
},
"node_modules/is-core-module": {
"version": "2.13.1",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz",
@ -1032,6 +1054,16 @@
"node": "14 || >=16.14"
}
},
"node_modules/md5": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz",
"integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==",
"dependencies": {
"charenc": "0.0.2",
"crypt": "0.0.2",
"is-buffer": "~1.1.6"
}
},
"node_modules/merge2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",

View File

@ -10,6 +10,7 @@
"@supabase/supabase-js": "latest",
"autoprefixer": "10.4.17",
"geist": "^1.2.1",
"md5": "^2.3.0",
"next": "latest",
"postcss": "8.4.33",
"react": "18.2.0",

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.