first commit

This commit is contained in:
LeNei
2023-11-25 16:18:54 +01:00
commit 9c5d791d10
31 changed files with 3840 additions and 0 deletions

474
app/assets/components.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 4.1 MiB

358
app/dashboard/details.tsx Normal file
View File

@@ -0,0 +1,358 @@
"use client";
import { useOrganization, useSession, useUser } from "@clerk/nextjs";
import classNames from "classnames";
import { useEffect, useState } from "react";
import { CopyIcon, Dot } from "../icons";
import Image from "next/image";
import "./prism.css";
declare global {
interface Window {
Prism: any;
}
}
export function UserDetails() {
const { isLoaded, user } = useUser();
const [jsonOutput, setJsonOutput] = useState(false);
return (
<div
className="bg-white overflow-hidden sm:rounded-lg"
style={{
boxShadow: `0px 20px 24px -4px rgba(16, 24, 40, 0.08)`,
}}
>
<div className="flex p-8">
<h3 className="text-xl leading-6 font-semibold text-gray-900 my-auto">
User
</h3>
<Toggle
checked={jsonOutput}
onChange={() => setJsonOutput(!jsonOutput)}
disabled={!isLoaded}
/>
</div>
{isLoaded && user ? (
jsonOutput ? (
<div className="overflow-scroll max-h-96 pb-6">
<JSONOutput json={user} />
</div>
) : (
<div className="pb-6 max-h-96">
<dl>
<div className="px-8 py-2">
<dt className="text-sm font-semibold">User ID</dt>
<dd className="mt-1 text-sm text-gray-600 sm:mt-0 sm:col-span-2 flex gap-2">
{user.id}
<CopyButton text={user.id} />
</dd>
</div>
{user.firstName && (
<div className="px-8 py-2">
<dt className="text-sm font-semibold mb-1">First Name</dt>
<dd className="mt-1 text-sm text-gray-600 sm:mt-0 sm:col-span-2">
{user.firstName}
</dd>
</div>
)}
{user.lastName && (
<div className="px-8 py-2">
<dt className="text-sm font-semibold mb-1">Last Name</dt>
<dd className="mt-1 text-sm text-gray-600 sm:mt-0 sm:col-span-2">
{user.lastName}
</dd>
</div>
)}
<div className="px-8 py-2">
<dt className="text-sm font-semibold mb-1">Email addresses</dt>
<dd className="mt-1 text-sm text-gray-600 sm:mt-0 sm:col-span-2">
{user.emailAddresses.map((email) => (
<div key={email.id} className="flex gap-2 mb-1">
{email.emailAddress}
{user.primaryEmailAddressId === email.id && (
<span className="text-xs bg-primary-50 text-primary-700 rounded-2xl px-2 font-medium pt-[2px]">
Primary
</span>
)}
</div>
))}
</dd>
</div>
{user.imageUrl && (
<div className="px-8 py-2">
<dt className="text-sm font-semibold mb-1">Profile Image</dt>
<dd className="mt-1 text-sm text-gray-600 sm:mt-0 sm:col-span-2">
<img
src={user.imageUrl}
className="rounded-full w-12 h-12"
/>
</dd>
</div>
)}
</dl>
</div>
)
) : (
<div className="text-gray-700 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
Loading user data...
</div>
)}
</div>
);
}
export function SessionDetails() {
const { isLoaded, session } = useSession();
const [jsonOutput, setJsonOutput] = useState(false);
return (
<div
className="bg-white shadow overflow-hidden sm:rounded-lg"
style={{
boxShadow: `0px 20px 24px -4px rgba(16, 24, 40, 0.08)`,
}}
>
<div className="flex p-8">
<h3 className="text-xl leading-6 font-semibold text-gray-900 my-auto">
Session
</h3>
<Toggle
checked={jsonOutput}
onChange={() => setJsonOutput(!jsonOutput)}
disabled={!isLoaded}
/>
</div>
{isLoaded && session ? (
jsonOutput ? (
<div className="overflow-scroll max-h-96 pb-6">
<JSONOutput
json={{
...session,
user: undefined,
}}
/>
</div>
) : (
<div className="pb-6 max-h-96">
<dl>
<div className="px-8 py-2">
<dt className="text-sm font-semibold">Session ID</dt>
<dd className="mt-1 text-sm text-gray-600 sm:mt-0 sm:col-span-2 flex gap-2">
{session.id}
<CopyButton text={session.id} />
</dd>
</div>
<div className="px-8 py-2">
<dt className="text-sm font-semibold mb-1">Status</dt>
<dd className="mt-1 text-sm text-gray-600 sm:mt-0 sm:col-span-2">
{session.status === `active` && (
<span className="text-xs bg-success-50 text-success-700 flex w-min gap-1 px-2 py-[1px] rounded-2xl font-medium">
<div className="m-auto">
<Dot />
</div>
Active
</span>
)}
</dd>
</div>
<div className="px-8 py-2">
<dt className="text-sm font-semibold mb-1">Last Active</dt>
<dd className="mt-1 text-sm text-gray-600 sm:mt-0 sm:col-span-2">
{session.lastActiveAt.toLocaleString()}
</dd>
</div>
<div className="px-8 py-2">
<dt className="text-sm font-semibold mb-1">Expiry</dt>
<dd className="mt-1 text-sm text-gray-600 sm:mt-0 sm:col-span-2">
{session.expireAt.toLocaleString()}
</dd>
</div>
</dl>
</div>
)
) : (
<div className="text-gray-700 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
Loading user data...
</div>
)}
</div>
);
}
export function OrgDetails() {
const { isLoaded, organization } = useOrganization();
const [jsonOutput, setJsonOutput] = useState(false);
return (
<div
className="bg-white shadow overflow-hidden sm:rounded-lg"
style={{
boxShadow: `0px 20px 24px -4px rgba(16, 24, 40, 0.08)`,
}}
>
<div className="flex p-8">
<h3 className="text-xl leading-6 font-semibold text-gray-900 my-auto">
Organization
</h3>
<Toggle
checked={jsonOutput}
onChange={() => setJsonOutput(!jsonOutput)}
disabled={!(isLoaded && organization)}
/>
</div>
{isLoaded ? (
organization ? (
jsonOutput ? (
<div className="overflow-scroll max-h-96 pb-6">
<JSONOutput json={organization} />
</div>
) : (
<div className="pb-6 max-h-96">
<dl>
<div className="px-8 py-2">
<dt className="text-sm font-semibold">Organization ID</dt>
<dd className="mt-1 text-sm text-gray-600 sm:mt-0 sm:col-span-2 flex gap-2">
{organization.id}
<CopyButton text={organization.id} />
</dd>
</div>
<div className="px-8 py-2">
<dt className="text-sm font-semibold mb-1">Name</dt>
<dd className="mt-1 text-sm text-gray-600 sm:mt-0 sm:col-span-2">
{organization.name}
</dd>
</div>
<div className="px-8 py-2">
<dt className="text-sm font-semibold mb-1">Members</dt>
<dd className="mt-1 text-sm text-gray-600 sm:mt-0 sm:col-span-2">
{organization.membersCount}
</dd>
</div>
<div className="px-8 py-2">
<dt className="text-sm font-semibold mb-1">
Pending invitations
</dt>
<dd className="mt-1 text-sm text-gray-600 sm:mt-0 sm:col-span-2">
{organization.pendingInvitationsCount}
</dd>
</div>
<div className="px-8 py-2">
<dt className="text-sm font-semibold mb-1">Image</dt>
<dd className="mt-1 text-sm text-gray-600 sm:mt-0 sm:col-span-2">
<Image
className="rounded"
src={organization.imageUrl}
alt={`Logo for ${organization.name}`}
width={48}
height={48}
/>
</dd>
</div>
</dl>
</div>
)
) : (
<div className="text-gray-700 px-8 pb-5 text-sm">
You are currently logged in to your personal workspace.
<br />
Create or switch to an organization to see its details.
</div>
)
) : (
<div className="text-gray-700 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
Loading organization data...
</div>
)}
</div>
);
}
function Toggle(props: {
checked: boolean;
onChange: () => void;
disabled: boolean;
}) {
return (
<div className="flex items-center justify-end flex-1">
<button
disabled={props.disabled}
onClick={props.onChange}
className={classNames({
"rounded-l-lg py-2 px-4 border-solid border border-gray-300 transition text-sm font-semibold":
true,
"bg-gray-100": !props.checked,
"bg-gray-50 text-gray-500 cursor-not-allowed": props.disabled,
})}
>
List
</button>
<button
disabled={props.disabled}
onClick={props.onChange}
className={classNames({
"rounded-r-lg py-2 px-4 border-solid border border-gray-300 -ml-[1px] transition text-sm font-semibold":
true,
"bg-gray-100": props.checked,
"bg-gray-50 text-gray-500 cursor-not-allowed": props.disabled,
})}
>
JSON
</button>
</div>
);
}
function CopyButton(props: { text: string }) {
const [tooltipShown, setTooltipShown] = useState(false);
useEffect(() => {
if (tooltipShown) {
const timeout = setTimeout(() => setTooltipShown(false), 2000);
return () => clearTimeout(timeout);
}
}, [tooltipShown]);
return (
<>
<button
onClick={() => {
if (navigator.clipboard) navigator.clipboard.writeText(props.text);
setTooltipShown(true);
}}
>
<CopyIcon />
</button>
<div
className={classNames({
"absolute z-10 bg-gray-900 text-white rounded p-2 text-xs transition-all ease-in-out translate-x-60 shadow-sm shadow-gray-500":
true,
"translate-y-10 opacity-0": !tooltipShown,
"translate-y-6": tooltipShown,
})}
>
Copied!
</div>
</>
);
}
function JSONOutput(props: { json: any }) {
useEffect(() => {
if (window.Prism) {
console.log(`highlighting`);
window.Prism.highlightAll();
}
}, []);
return (
<pre className="px-8 sm:px-6 text-black text-sm">
<code className="language-json">
{JSON.stringify(props.json, null, 2)}
</code>
</pre>
);
}

47
app/dashboard/page.tsx Normal file
View File

@@ -0,0 +1,47 @@
import { auth, clerkClient, useClerk } from "@clerk/nextjs";
import { redirect, useRouter } from "next/navigation";
import { OrgDetails, SessionDetails, UserDetails } from "./details";
import Link from "next/link";
export default async function DashboardPage() {
const { userId } = auth();
const router = useRouter();
const { signOut } = useClerk();
if (!userId) {
redirect("/");
}
const user = await clerkClient.users.getUser(userId);
return (
<div className="px-8 py-12 sm:py-16 md:px-20">
{user && (
<>
<h1 className="text-3xl font-semibold text-black">
👋 Hi, {user.firstName || `Stranger`}
</h1>
<button onClick={() => signOut(() => router.push("/"))}>
Sign out
</button>
<div className="grid gap-4 mt-8 lg:grid-cols-3">
<UserDetails />
<SessionDetails />
<OrgDetails />
</div>
<h2 className="mt-16 mb-4 text-3xl font-semibold text-black">
What's next?
</h2>
Read the{" "}
<Link
className="font-medium text-primary-600 hover:underline"
href="https://clerk.com/docs?utm_source=vercel-template&utm_medium=template_repos&utm_campaign=nextjs_template"
target="_blank"
>
Clerk Docs -&gt;
</Link>
</>
)}
</div>
);
}

144
app/dashboard/prism.css Normal file
View File

@@ -0,0 +1,144 @@
/**
* prism.js default theme for JavaScript, CSS and HTML
* Based on dabblet (http://dabblet.com)
* @author Lea Verou
*/
code[class*='language-'],
pre[class*='language-'] {
color: black;
background: none;
text-shadow: 0 1px white;
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
font-size: 1em;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
pre[class*='language-']::-moz-selection,
pre[class*='language-'] ::-moz-selection,
code[class*='language-']::-moz-selection,
code[class*='language-'] ::-moz-selection {
text-shadow: none;
background: #b3d4fc;
}
pre[class*='language-']::selection,
pre[class*='language-'] ::selection,
code[class*='language-']::selection,
code[class*='language-'] ::selection {
text-shadow: none;
background: #b3d4fc;
}
@media print {
code[class*='language-'],
pre[class*='language-'] {
text-shadow: none;
}
}
/* Code blocks */
pre[class*='language-'] {
padding: 1em;
margin: 0.5em 0;
overflow: auto;
}
/* :not(pre) > code[class*='language-'],
pre[class*='language-'] {
background: #f5f2f0;
} */
/* Inline code */
:not(pre) > code[class*='language-'] {
padding: 0.1em;
border-radius: 0.3em;
white-space: normal;
}
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: slategray;
}
.token.punctuation {
color: #999;
}
.token.namespace {
opacity: 0.7;
}
.token.property,
.token.tag,
.token.boolean,
.token.number,
.token.constant,
.token.symbol,
.token.deleted {
color: #905;
}
.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.inserted {
color: #690;
}
.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string {
color: #9a6e3a;
/* This background color was intended by the author of this theme. */
background: hsla(0, 0%, 100%, 0.5);
}
.token.atrule,
.token.attr-value,
.token.keyword {
color: #07a;
}
.token.function,
.token.class-name {
color: #dd4a68;
}
.token.regex,
.token.important,
.token.variable {
color: #e90;
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}

BIN
app/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

3
app/globals.css Normal file
View File

@@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

37
app/home.css Normal file
View File

@@ -0,0 +1,37 @@
.gradient {
position: relative;
background-image: repeating-linear-gradient(
to right,
#63DFFA,
#6C47FF,
#63DFFA
);
z-index: 1;
background-position-x: 0%;
background-size: 200%;
animation: gradimate 3s linear infinite;
background-clip: text;
}
@keyframes gradimate {
0% {
background-position-x: 0%;
}
50% {
background-position-x: 100%;
}
100% {
background-position-x: 200%;
}
}
/* .cta .arrow {
opacity: 0;
transition: 0.1s linear;
margin-left: -4px;
}
.cta:hover .arrow {
opacity: 100;
margin-left: 0;
} */

181
app/icons/index.tsx Normal file
View File

@@ -0,0 +1,181 @@
export function RightArrow() {
return (
<svg
width="14"
height="14"
viewBox="0 0 14 14"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M1.16669 7.00033H12.8334M12.8334 7.00033L7.00002 1.16699M12.8334 7.00033L7.00002 12.8337"
stroke="white"
stroke-width="1.66667"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
);
}
export function DownArrow() {
return (
<svg
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6.66666 10.0003L9.99999 13.3337M9.99999 13.3337L13.3333 10.0003M9.99999 13.3337V6.66699M18.3333 10.0003C18.3333 14.6027 14.6024 18.3337 9.99999 18.3337C5.39762 18.3337 1.66666 14.6027 1.66666 10.0003C1.66666 5.39795 5.39762 1.66699 9.99999 1.66699C14.6024 1.66699 18.3333 5.39795 18.3333 10.0003Z"
stroke="#475467"
stroke-width="1.66667"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
);
}
export function CopyIcon() {
return (
<svg
width="19"
height="20"
viewBox="0 0 19 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M3.5 12.5003C2.72343 12.5003 2.33515 12.5003 2.02886 12.3735C1.62048 12.2043 1.29602 11.8798 1.12687 11.4715C1 11.1652 1 10.7769 1 10.0003V4.33366C1 3.40024 1 2.93353 1.18166 2.57701C1.34144 2.2634 1.59641 2.00844 1.91002 1.84865C2.26654 1.66699 2.73325 1.66699 3.66667 1.66699H9.33333C10.1099 1.66699 10.4982 1.66699 10.8045 1.79386C11.2129 1.96302 11.5373 2.28747 11.7065 2.69585C11.8333 3.00214 11.8333 3.39042 11.8333 4.16699M9.5 18.3337H15C15.9334 18.3337 16.4001 18.3337 16.7567 18.152C17.0703 17.9922 17.3252 17.7372 17.485 17.4236C17.6667 17.0671 17.6667 16.6004 17.6667 15.667V10.167C17.6667 9.23357 17.6667 8.76686 17.485 8.41034C17.3252 8.09674 17.0703 7.84177 16.7567 7.68198C16.4001 7.50033 15.9334 7.50033 15 7.50033H9.5C8.56658 7.50033 8.09987 7.50033 7.74335 7.68198C7.42975 7.84177 7.17478 8.09674 7.01499 8.41034C6.83333 8.76686 6.83333 9.23357 6.83333 10.167V15.667C6.83333 16.6004 6.83333 17.0671 7.01499 17.4236C7.17478 17.7372 7.42975 17.9922 7.74335 18.152C8.09987 18.3337 8.56658 18.3337 9.5 18.3337Z"
stroke="black"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
);
}
export function Times() {
return (
<svg
width="12"
height="12"
viewBox="0 0 12 12"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M11 1L1 11M1 1L11 11"
stroke="black"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
);
}
export function Dot() {
return (
<svg
width="7"
height="6"
viewBox="0 0 7 6"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<circle cx="3.3335" cy="3" r="3" fill="#12B76A" />
</svg>
);
}
export function Docs() {
return (
<svg
width="20"
height="18"
viewBox="0 0 20 18"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M9.99999 16.5L9.91661 16.3749C9.33774 15.5066 9.04831 15.0725 8.66591 14.7582C8.32737 14.4799 7.93729 14.2712 7.51799 14.1438C7.04437 14 6.52258 14 5.47901 14H4.33332C3.3999 14 2.93319 14 2.57667 13.8183C2.26307 13.6586 2.0081 13.4036 1.84831 13.09C1.66666 12.7335 1.66666 12.2668 1.66666 11.3333V4.16667C1.66666 3.23325 1.66666 2.76654 1.84831 2.41002C2.0081 2.09641 2.26307 1.84144 2.57667 1.68166C2.93319 1.5 3.3999 1.5 4.33332 1.5H4.66666C6.5335 1.5 7.46692 1.5 8.17996 1.86331C8.80717 2.18289 9.3171 2.69282 9.63668 3.32003C9.99999 4.03307 9.99999 4.96649 9.99999 6.83333M9.99999 16.5V6.83333M9.99999 16.5L10.0834 16.3749C10.6622 15.5066 10.9517 15.0725 11.3341 14.7582C11.6726 14.4799 12.0627 14.2712 12.482 14.1438C12.9556 14 13.4774 14 14.521 14H15.6667C16.6001 14 17.0668 14 17.4233 13.8183C17.7369 13.6586 17.9919 13.4036 18.1517 13.09C18.3333 12.7335 18.3333 12.2668 18.3333 11.3333V4.16667C18.3333 3.23325 18.3333 2.76654 18.1517 2.41002C17.9919 2.09641 17.7369 1.84144 17.4233 1.68166C17.0668 1.5 16.6001 1.5 15.6667 1.5H15.3333C13.4665 1.5 12.5331 1.5 11.82 1.86331C11.1928 2.18289 10.6829 2.69282 10.3633 3.32003C9.99999 4.03307 9.99999 4.96649 9.99999 6.83333"
stroke="#344054"
stroke-width="1.66667"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
);
}
export function Github() {
return (
<svg
width="18"
height="18"
viewBox="0 0 18 18"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M9 0.25C4.16562 0.25 0.25 4.16562 0.25 9C0.25 12.8719 2.75469 16.1422 6.23281 17.3016C6.67031 17.3781 6.83437 17.1156 6.83437 16.8859C6.83437 16.6781 6.82344 15.9891 6.82344 15.2563C4.625 15.6609 4.05625 14.7203 3.88125 14.2281C3.78281 13.9766 3.35625 13.2 2.98438 12.9922C2.67812 12.8281 2.24063 12.4234 2.97344 12.4125C3.6625 12.4016 4.15469 13.0469 4.31875 13.3094C5.10625 14.6328 6.36406 14.2609 6.86719 14.0312C6.94375 13.4625 7.17344 13.0797 7.425 12.8609C5.47813 12.6422 3.44375 11.8875 3.44375 8.54062C3.44375 7.58906 3.78281 6.80156 4.34062 6.18906C4.25313 5.97031 3.94687 5.07344 4.42812 3.87031C4.42812 3.87031 5.16094 3.64063 6.83437 4.76719C7.53438 4.57031 8.27813 4.47187 9.02188 4.47187C9.76563 4.47187 10.5094 4.57031 11.2094 4.76719C12.8828 3.62969 13.6156 3.87031 13.6156 3.87031C14.0969 5.07344 13.7906 5.97031 13.7031 6.18906C14.2609 6.80156 14.6 7.57812 14.6 8.54062C14.6 11.8984 12.5547 12.6422 10.6078 12.8609C10.925 13.1344 11.1984 13.6594 11.1984 14.4797C11.1984 15.65 11.1875 16.5906 11.1875 16.8859C11.1875 17.1156 11.3516 17.3891 11.7891 17.3016C13.5261 16.7152 15.0355 15.5988 16.1048 14.1096C17.1741 12.6204 17.7495 10.8333 17.75 9C17.75 4.16562 13.8344 0.25 9 0.25Z"
fill="black"
/>
</svg>
);
}
export function Twitter() {
return (
<svg
width="19"
height="15"
viewBox="0 0 19 15"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6.36796 14.5C4.34609 14.5 2.46136 13.9119 0.875 12.8973C2.22187 12.9844 4.59879 12.7757 6.07724 11.3655C3.85317 11.2634 2.85016 9.55768 2.71934 8.82873C2.90831 8.90162 3.80956 8.9891 4.31834 8.78499C1.75994 8.14351 1.36745 5.89833 1.45467 5.21311C1.93437 5.54843 2.74841 5.66506 3.06821 5.6359C0.684246 3.93015 1.54189 1.36422 1.96345 0.810218C3.67426 3.18042 6.23825 4.51161 9.41024 4.58565C9.35043 4.32335 9.31885 4.05026 9.31885 3.76978C9.31885 1.75682 10.9459 0.125 12.9529 0.125C14.0016 0.125 14.9465 0.570471 15.6098 1.28302C16.3106 1.11882 17.3652 0.734417 17.8808 0.402003C17.6209 1.33507 16.8118 2.11343 16.3224 2.40192C16.3185 2.39207 16.3265 2.41174 16.3224 2.40192C16.7523 2.3369 17.9155 2.11336 18.375 1.8016C18.1478 2.32578 17.29 3.19733 16.5861 3.68526C16.717 9.46129 12.2978 14.5 6.36796 14.5Z"
fill="#47ACDF"
/>
</svg>
);
}
export function Discord() {
return (
<svg
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M1.25 7.25C1.25 5.1498 1.25 4.0997 1.65873 3.29754C2.01825 2.59193 2.59193 2.01825 3.29754 1.65873C4.0997 1.25 5.1498 1.25 7.25 1.25H12.75C14.8502 1.25 15.9003 1.25 16.7025 1.65873C17.4081 2.01825 17.9817 2.59193 18.3413 3.29754C18.75 4.0997 18.75 5.1498 18.75 7.25V12.75C18.75 14.8502 18.75 15.9003 18.3413 16.7025C17.9817 17.4081 17.4081 17.9817 16.7025 18.3413C15.9003 18.75 14.8502 18.75 12.75 18.75H7.25C5.1498 18.75 4.0997 18.75 3.29754 18.3413C2.59193 17.9817 2.01825 17.4081 1.65873 16.7025C1.25 15.9003 1.25 14.8502 1.25 12.75V7.25Z"
fill="url(#paint0_linear_158_4828)"
/>
<path
d="M15.172 6.26667C14.2259 5.46667 13.0906 5.06667 11.8922 5L11.703 5.2C12.7752 5.46667 13.7213 6 14.6044 6.73333C13.5321 6.13333 12.3337 5.73333 11.0722 5.6C10.6938 5.53333 10.3784 5.53333 10 5.53333C9.62156 5.53333 9.30619 5.53333 8.92775 5.6C7.66628 5.73333 6.46789 6.13333 5.39564 6.73333C6.27867 6 7.22477 5.46667 8.29702 5.2L8.1078 5C6.9094 5.06667 5.77408 5.46667 4.82798 6.26667C3.75573 8.4 3.18807 10.8 3.125 13.2667C4.0711 14.3333 5.39564 15 6.78326 15C6.78326 15 7.22477 14.4667 7.54014 14C6.72018 13.8 5.9633 13.3333 5.45872 12.6C5.90023 12.8667 6.34174 13.1333 6.78326 13.3333C7.35092 13.6 7.91858 13.7333 8.48624 13.8667C8.99083 13.9333 9.49541 14 10 14C10.5046 14 11.0092 13.9333 11.5138 13.8667C12.0814 13.7333 12.6491 13.6 13.2167 13.3333C13.6583 13.1333 14.0998 12.8667 14.5413 12.6C14.0367 13.3333 13.2798 13.8 12.4599 14C12.7752 14.4667 13.2167 15 13.2167 15C14.6044 15 15.9289 14.3333 16.875 13.2667C16.8119 10.8 16.2443 8.4 15.172 6.26667ZM7.91858 12.0667C7.28784 12.0667 6.72018 11.4667 6.72018 10.7333C6.72018 10 7.28784 9.4 7.91858 9.4C8.54931 9.4 9.11697 10 9.11697 10.7333C9.11697 11.4667 8.54931 12.0667 7.91858 12.0667ZM12.0814 12.0667C11.4507 12.0667 10.883 11.4667 10.883 10.7333C10.883 10 11.4507 9.4 12.0814 9.4C12.7122 9.4 13.2798 10 13.2798 10.7333C13.2798 11.4667 12.7122 12.0667 12.0814 12.0667Z"
fill="white"
/>
<defs>
<linearGradient
id="paint0_linear_158_4828"
x1="10"
y1="1.25"
x2="10"
y2="18.75"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#687EC9" />
<stop offset="1" stop-color="#5971C3" />
</linearGradient>
</defs>
</svg>
);
}

140
app/layout.tsx Normal file
View File

@@ -0,0 +1,140 @@
import {
ClerkProvider,
OrganizationSwitcher,
SignedIn,
UserButton,
} from "@clerk/nextjs";
import "./globals.css";
import { Inter } from "next/font/google";
import Image from "next/image";
import Link from "next/link";
import Script from "next/script";
import { Docs, Github, Times } from "./icons";
import { Twitter } from "./icons";
import { Discord } from "./icons";
import { Metadata } from "next";
const inter = Inter({ subsets: ["latin"] });
export const metadata: Metadata = {
title: "Next.js Clerk Template",
description:
"A simple and powerful Next.js template featuring authentication and user management powered by Clerk.",
openGraph: { images: ["/og.png"] },
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<ClerkProvider
appearance={{
variables: { colorPrimary: "#000000" },
elements: {
formButtonPrimary:
"bg-black border border-black border-solid hover:bg-white hover:text-black",
socialButtonsBlockButton:
"bg-white border-gray-200 hover:bg-transparent hover:border-black text-gray-600 hover:text-black",
socialButtonsBlockButtonText: "font-semibold",
formButtonReset:
"bg-white border border-solid border-gray-200 hover:bg-transparent hover:border-black text-gray-500 hover:text-black",
membersPageInviteButton:
"bg-black border border-black border-solid hover:bg-white hover:text-black",
card: "bg-[#fafafa]",
},
}}
>
<body className={`${inter.className} min-h-screen flex flex-col`}>
<header className="flex items-center h-20 gap-4 px-4 border-b border-black border-solid sm:px-8 border-opacity-20">
<Link href="/" className="flex items-center h-20 gap-2 sm:gap-4">
<Image
src="/clerk.svg"
alt="Clerk Logo"
width={102}
height={32}
priority
/>
<Times />
<Image
src="/next.svg"
alt="Next.js Logo"
width={90}
height={18}
priority
/>
</Link>
<div className="grow" />
<SignedIn>
<div className="hidden sm:block">
<OrganizationSwitcher afterCreateOrganizationUrl="/dashboard" />
</div>
<div className="block sm:hidden">
<OrganizationSwitcher
afterCreateOrganizationUrl="/dashboard"
appearance={{
elements: {
organizationSwitcherTriggerIcon: `hidden`,
organizationPreviewTextContainer: `hidden`,
organizationSwitcherTrigger: `pr-0`,
},
}}
/>
</div>
<UserButton afterSignOutUrl="/" />
</SignedIn>
</header>
<main className="grow">{children}</main>
<footer className="flex items-center h-20 gap-1 px-8 font-medium border-t md:px-20">
<Image
src="/clerk.svg"
alt="Clerk Logo"
width={64}
height={32}
priority
/>
<span className="text-sm">© 2023</span>
<nav className="flex justify-end grow sm:gap-2">
<a
className="flex gap-2 px-3 py-2 text-sm font-semibold text-gray-600 transition duration-100 rounded-md hover:text-gray-800"
href="https://clerk.com/docs?utm_source=vercel-template&utm_medium=template_repos&utm_campaign=nextjs_template"
>
<div className="m-auto">
<Docs />
</div>
<span className="hidden sm:inline"> Visit Clerk Docs</span>
<span className="inline sm:hidden"> Docs</span>
</a>
<a
className="flex gap-2 px-3 py-2 text-sm font-semibold text-gray-600 transition duration-100 rounded-md hover:text-gray-800"
href="https://github.com/clerkinc/clerk-next-app"
>
<div className="m-auto">
<Github />
</div>
<span className="hidden sm:inline"> View on Github</span>
</a>
<a
className="flex flex-col justify-center p-2 hover:underline"
href="https://twitter.com/ClerkDev"
>
<Twitter />
</a>
<a
className="flex flex-col justify-center p-2 hover:underline"
href="https://discord.com/invite/b5rXHjAg7A"
>
<Discord />
</a>
</nav>
</footer>
</body>
</ClerkProvider>
<Script src="https://cdn.jsdelivr.net/npm/prismjs@1/components/prism-core.min.js" />
<Script src="https://cdn.jsdelivr.net/npm/prismjs@1/plugins/autoloader/prism-autoloader.min.js" />
</html>
);
}

110
app/page.tsx Normal file
View File

@@ -0,0 +1,110 @@
import Image from "next/image";
import Link from "next/link";
import componentsImg from "./assets/components.svg";
import { DownArrow, RightArrow } from "./icons";
import "./home.css";
export default function Home() {
return (
<main className="">
<article className="grid lg:grid-cols-2">
<div className="px-8 py-20 md:px-20 lg:py-48">
<h1 className="text-5xl font-semibold text-transparent md:text-6xl gradient">
Auth starts here.
</h1>
<p className="mt-2 text-lg">
A simple and powerful Next.js template featuring authentication and
user management powered by Clerk.
</p>
<div className="flex gap-2 mt-8">
<Link
href="/dashboard"
className="flex content-center gap-2 px-4 py-2 font-semibold text-white transition-colors duration-200 rounded-lg bg-primary-600 hover:bg-primary-700"
>
View Demo
<div className="m-auto">
<RightArrow />
</div>
</Link>
<a
className="flex gap-2 px-4 py-2 font-semibold text-gray-600 transition duration-100 rounded-lg hover:text-gray-800"
href="#features"
>
Learn more
<div className="m-auto">
<DownArrow />
</div>
</a>
</div>
</div>
<div className="flex flex-col justify-center">
<Image src={componentsImg} alt="Clerk embeddable components" />
</div>
</article>
<article
className="px-8 py-12 bg-black bg-opacity-5 md:px-20 md:py-24"
id="features"
>
<h2 className="text-3xl font-semibold">What's under the hood?</h2>
<p className="mt-2">
This template repo uses some of the following features provided by
Clerk. To learn more, read the{" "}
<a
href="https://clerk.com/docs?utm_source=vercel-template&utm_medium=template_repos&utm_campaign=nextjs_template"
className="font-medium text-primary-600 hover:underline"
>
Clerk Docs
</a>
.
</p>
<div className="grid gap-8 mt-8 lg:grid-cols-3">
<div className="flex flex-col h-56 gap-1 p-8 bg-white shadow-lg rounded-2xl">
<h3 className="text-lg font-medium">Customizable Components</h3>
<p className="text-gray-700">
Prebuilt components to handle essential functionality like user
sign-in, sign-up, and account management.
</p>
<div className="grow"></div>
<a
href="https://clerk.com/docs/component-reference/overview?utm_source=vercel-template&utm_medium=template_repos&utm_campaign=nextjs_template"
className="text-primary-600 cta hover:underline"
target="_blank"
>
Components <span className="arrow">-&gt;</span>
</a>
</div>
<div className="flex flex-col h-56 gap-1 p-8 bg-white shadow-lg rounded-2xl">
<h3 className="text-lg font-medium">React Hooks</h3>
<p className="text-gray-700">
Build custom functionality by accessing auth state, user and
session data, and more with Clerk's React Hooks.
</p>
<div className="grow"></div>
<a
href="https://clerk.com/docs/reference/clerk-react/useuser?utm_source=vercel-template&utm_medium=template_repos&utm_campaign=nextjs_template"
className="text-primary-600 cta hover:underline"
target="_blank"
>
React Hooks <span className="arrow">-&gt;</span>
</a>
</div>
<div className="flex flex-col h-56 gap-1 p-8 bg-white shadow-lg rounded-2xl">
<h3 className="text-lg font-medium">Multitenancy</h3>
<p className="text-gray-700">
Seamlessly create and switch between organizations, invite and
manage members, and assign custom roles.
</p>
<div className="grow"></div>
<a
href="https://clerk.com/docs/organizations/overview?utm_source=vercel-template&utm_medium=template_repos&utm_campaign=nextjs_template"
className="text-primary-600 cta hover:underline"
target="_blank"
>
Organizations <span className="arrow">-&gt;</span>
</a>
</div>
</div>
</article>
</main>
);
}

View File

@@ -0,0 +1,9 @@
import { SignIn } from "@clerk/nextjs";
export default function Page() {
return (
<div className="flex justify-center py-24">
<SignIn />
</div>
);
}

View File

@@ -0,0 +1,9 @@
import { SignUp } from "@clerk/nextjs";
export default function Page() {
return (
<div className="flex justify-center py-24">
<SignUp />
</div>
);
}