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

9
.dockerignore Normal file
View File

@@ -0,0 +1,9 @@
Dockerfile
.dockerignore
node_modules
npm-debug.log
README.md
.next
docker
.git
.env.template

5
.env Normal file
View File

@@ -0,0 +1,5 @@
NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/dashboard
NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/dashboard

2
.env.template Normal file
View File

@@ -0,0 +1,2 @@
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=your_clerk_pub_key
CLERK_SECRET_KEY=your_clerk_secret_key

35
.gitignore vendored Normal file
View File

@@ -0,0 +1,35 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts

54
Dockerfile Normal file
View File

@@ -0,0 +1,54 @@
FROM node:18-alpine AS base
# 1. Install dependencies only when needed
FROM base AS deps
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat
WORKDIR /app
# Install dependencies based on the preferred package manager
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
RUN \
if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
elif [ -f package-lock.json ]; then npm ci; \
elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i; \
else echo "Lockfile not found." && exit 1; \
fi
# 2. Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# This will do the trick, use the corresponding env file for each environment.
COPY .env .env.production
RUN npm run build
# 3. Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
COPY --from=builder /app/public ./public
# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT 3000
ENV HOSTNAME "0.0.0.0"
CMD ["node", "server.js"]

94
README.md Normal file
View File

@@ -0,0 +1,94 @@
<p align="center">
<a href="https://clerk.com?utm_source=github&utm_medium=clerk_docs" target="_blank" rel="noopener noreferrer">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="./public/light-logo.png">
<img src="./public/dark-logo.png" height="64">
</picture>
</a>
<br />
</p>
<div align="center">
<h1>
Clerk and Next.js App Router template
</h1>
<a href="https://www.npmjs.com/package/@clerk/clerk-js">
<img alt="" src="https://img.shields.io/npm/dm/@clerk/clerk-js" />
</a>
<a href="https://discord.com/invite/b5rXHjAg7A">
<img alt="Discord" src="https://img.shields.io/discord/856971667393609759?color=7389D8&label&logo=discord&logoColor=ffffff" />
</a>
<a href="https://twitter.com/clerkdev">
<img alt="Twitter" src="https://img.shields.io/twitter/url.svg?label=%40clerkdev&style=social&url=https%3A%2F%2Ftwitter.com%2Fclerkdev" />
</a>
<br />
<br />
<img alt="Clerk Hero Image" src="public/og.png">
</div>
## Introduction
Clerk is a developer-first authentication and user management solution. It provides pre-built React components and hooks for sign-in, sign-up, user profile, and organization management. Clerk is designed to be easy to use and customize, and can be dropped into any React or Next.js application.
This template allows you to get started with Clerk and Next.js (App Router) in a matter of minutes, and demonstrates features of Clerk such as:
- Fully functional auth flow with sign-in, sign-up, and a protected page
- Customized Clerk components with Tailwind CSS
- Hooks for accessing user data and authentication state
- Organizations for multi-tenant applications
## Demo
A hosted demo of this example is available at https://clerk-nextjs-demo-app-router.clerkpreview.com/
## Deploy
Easily deploy the template to Vercel with the button below. You will need to set the required environment variables in the Vercel dashboard.
<!-- TODO: UPDATE THIS UTM -->
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fclerkinc%2Fclerk-nextjs-demo-app-router&env=NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY,CLERK_SECRET_KEY&envDescription=Clerk%20API%20keys&envLink=https%3A%2F%2Fclerk.com%2Fdocs%2Fquickstart%2Fnextjs&redirect-url=https%3A%2F%2Fclerk.com%2Fdocs%2Fquickstart%2Fnextjs)
## Running the template
```bash
git clone https://github.com/clerkinc/clerk-nextjs-demo-app-router
```
To run the example locally, you need to:
<!-- TODO: UPDATE THIS UTM -->
1. Sign up for a Clerk account at [https://clerk.com](https://dashboard.clerk.com/sign-up?utm_source=github&utm_medium=template_repos&utm_campaign=nextjs_template).
<!-- TODO: UPDATE THIS UTM -->
2. Go to the [Clerk dashboard](https://dashboard.clerk.com?utm_source=github&utm_medium=template_repos&utm_campaign=nextjs_template) and create an application.
3. Set the required Clerk environment variables as shown in [the example `env` file](./.env.template).
4. `npm install` the required dependencies.
5. `npm run dev` to launch the development server.
## Learn more
To learn more about Clerk and Next.js, check out the following resources:
<!-- TODO: UPDATE THIS UTM -->
- [Quickstart: Get started with Next.js and Clerk](https://clerk.com/docs/quickstarts/nextjs?utm_source=github&utm_medium=template_repos&utm_campaign=nextjs_template)
<!-- TODO: UPDATE THIS UTM -->
- [Clerk Documentation](https://clerk.com/docs?utm_source=github&utm_medium=template_repos&utm_campaign=nextjs_template)
- [Next.js Documentation](https://nextjs.org/docs)
## Found an issue?
If you have found an issue with our documentation, please create an [issue](https://github.com/clerkinc/clerk-nextjs-demo-app-router/issues).
If it's a quick fix, such as a misspelled word or a broken link, feel free to skip creating an issue.
Go ahead and create a [pull request](https://github.com/clerkinc/clerk-nextjs-demo-app-router/pulls) with the solution. :rocket:
## Want to leave feedback?
Feel free to create an [issue](https://github.com/clerkinc/clerk-nextjs-demo-app-router/issues) with the **feedback** label. Our team will take a look at it and get back to you as soon as we can!
## Connect with us
You can discuss ideas, ask questions, and meet others from the community in our [Discord](https://discord.com/invite/b5rXHjAg7A).
If you prefer, you can also find support through our [Twitter](https://twitter.com/ClerkDev), or you can [email](mailto:support@clerk.dev) us!

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>
);
}

10
middleware.ts Normal file
View File

@@ -0,0 +1,10 @@
import { authMiddleware } from "@clerk/nextjs";
export default authMiddleware({
publicRoutes: ["/"],
debug: true,
});
export const config = {
matcher: ["/((?!.*\\..*|_next).*)", "/"],
};

9
next.config.js Normal file
View File

@@ -0,0 +1,9 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
output: "standalone",
images: {
domains: ["img.clerk.com"],
},
};
module.exports = nextConfig;

2007
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

25
package.json Normal file
View File

@@ -0,0 +1,25 @@
{
"name": "nextjs-clerk-starter",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@clerk/nextjs": "^4.27.1",
"@types/node": "20.1.4",
"@types/react": "18.2.6",
"@types/react-dom": "18.2.4",
"next": "^14.0.3",
"autoprefixer": "10.4.14",
"classnames": "^2.3.2",
"postcss": "8.4.23",
"react": "18.2.0",
"react-dom": "18.2.0",
"tailwindcss": "3.3.2",
"typescript": "5.0.4"
}
}

6
postcss.config.js Normal file
View File

@@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

22
public/clerk.svg Normal file
View File

@@ -0,0 +1,22 @@
<svg width="322" height="100" viewBox="0 0 322 100" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_28400_184664)">
<path d="M147.281 69.7417C145.631 71.4522 143.649 72.8084 141.457 73.7276C139.266 74.6472 136.909 75.1101 134.533 75.0884C132.526 75.1492 130.527 74.7984 128.661 74.0576C126.795 73.3167 125.1 72.2017 123.681 70.7809C121.103 68.0917 119.619 64.2522 119.619 59.6751C119.619 50.5126 125.584 44.2455 134.533 44.2455C136.933 44.2117 139.311 44.7084 141.496 45.6997C143.682 46.6913 145.622 48.153 147.178 49.9809L154.92 43.1351C149.875 37.0109 141.688 33.8457 133.914 33.8457C118.699 33.8457 107.91 44.333 107.91 59.7622C107.91 67.3934 110.591 73.8192 115.113 78.3567C119.635 82.8942 126.076 85.5597 133.509 85.5597C143.163 85.5597 150.866 81.6409 155.19 76.7147L147.281 69.7417Z" fill="#1F0256"/>
<path d="M162.191 14.2822H173.718V84.7492H162.191V14.2822Z" fill="#1F0256"/>
<path d="M229.24 63.6787C229.43 62.1441 229.536 60.5999 229.557 59.0537C229.557 44.4416 219.99 33.8591 205.195 33.8591C201.932 33.7949 198.691 34.3995 195.67 35.6356C192.65 36.8716 189.914 38.713 187.632 41.0462C183.309 45.5837 180.715 52.0095 180.715 59.6724C180.715 75.292 191.725 85.5728 206.322 85.5728C216.119 85.5728 223.171 81.6066 227.399 76.2282L219.847 69.5407L219.474 69.2237C217.976 71.0978 216.066 72.6016 213.892 73.6182C211.718 74.6349 209.34 75.137 206.941 75.0857C199.531 75.0857 194.184 70.7624 192.662 63.6787H229.24ZM192.852 54.4287C193.367 52.1957 194.421 50.1232 195.922 48.392C197.171 47.0312 198.7 45.9557 200.403 45.2395C202.106 44.5237 203.944 44.1837 205.79 44.2432C212.374 44.2432 216.499 48.3603 217.919 54.4287H192.852Z" fill="#1F0256"/>
<path d="M265.185 33.708V46.5591C263.852 46.4562 262.512 46.3528 261.694 46.3528C252.968 46.3528 248.018 52.6199 248.018 60.8462V84.7478H236.508V34.422H248.018V42.0453H248.122C252.032 36.6987 257.641 33.7239 263.749 33.7239L265.185 33.708Z" fill="#1F0256"/>
<path d="M292.027 63.6717L283.706 72.9296V84.7492H272.188V14.2822H283.706V57.5079L304.26 34.5823H317.936L299.849 54.8346L318.253 84.7492H305.299L292.242 63.6717H292.027Z" fill="#1F0256"/>
<path d="M80.482 13.17L68.4795 25.1723C68.1024 25.549 67.6095 25.7883 67.0804 25.8515C66.5508 25.9148 66.0158 25.7985 65.5604 25.5214C60.8391 22.6731 55.3941 21.2526 49.8829 21.4314C44.3716 21.6102 39.0303 23.3806 34.5034 26.5288C31.7193 28.4668 29.3019 30.8842 27.3639 33.6683C24.2194 38.198 22.4507 43.5396 22.2705 49.0508C22.0904 54.5621 23.5065 60.0079 26.3485 64.7333C26.6237 65.1879 26.7389 65.7213 26.6756 66.2492C26.6124 66.7767 26.3743 67.2679 25.9994 67.6446L13.9971 79.6467C13.7554 79.89 13.464 80.0783 13.1428 80.1983C12.8217 80.3188 12.4783 80.3679 12.1363 80.3433C11.7942 80.3183 11.4616 80.22 11.1612 80.0546C10.8607 79.8892 10.5996 79.6608 10.3957 79.385C3.90163 70.4588 0.545536 59.6338 0.851207 48.5996C1.15688 37.5653 5.10711 26.9427 12.0853 18.3898C14.2157 15.7729 16.6079 13.3806 19.2249 11.2503C27.7772 4.27392 38.3982 0.324843 49.4308 0.0191805C60.4633 -0.286482 71.2866 3.06847 80.212 9.56054C80.4899 9.76383 80.7199 10.0249 80.8866 10.3258C81.0537 10.6266 81.1533 10.96 81.1791 11.3031C81.2049 11.6462 81.1558 11.9908 81.0354 12.313C80.9149 12.6353 80.7262 12.9277 80.482 13.17Z" fill="url(#paint0_linear_28400_184664)"/>
<path d="M80.4703 86.7874L68.4678 74.7849C68.0907 74.4082 67.5978 74.1691 67.0686 74.1057C66.5395 74.0424 66.0041 74.1586 65.5486 74.4357C61.097 77.1211 55.9966 78.5407 50.7978 78.5407C45.5986 78.5407 40.4983 77.1211 36.0466 74.4357C35.5912 74.1586 35.056 74.0424 34.5266 74.1057C33.9973 74.1691 33.5045 74.4082 33.1273 74.7849L21.125 86.7874C20.8733 87.0291 20.6779 87.3236 20.5528 87.6495C20.4276 87.9753 20.3756 88.3249 20.4006 88.6732C20.4256 89.0216 20.5269 89.3599 20.6973 89.6645C20.8678 89.9695 21.1031 90.2328 21.3868 90.4361C29.9303 96.6524 40.2241 100.001 50.7899 100.001C61.3553 100.001 71.6491 96.6524 80.1928 90.4361C80.4774 90.2341 80.7141 89.9716 80.8861 89.6674C81.0578 89.3632 81.1607 89.0253 81.1874 88.677C81.2136 88.3286 81.1632 87.9786 81.0395 87.6524C80.9153 87.3257 80.7211 87.0303 80.4703 86.7874Z" fill="#1F0256"/>
<path d="M50.8221 64.2593C58.7079 64.2593 65.1013 57.8663 65.1013 49.9801C65.1013 42.0943 58.7079 35.7012 50.8221 35.7012C42.9358 35.7012 36.543 42.0943 36.543 49.9801C36.543 57.8663 42.9358 64.2593 50.8221 64.2593Z" fill="#1F0256"/>
</g>
<defs>
<linearGradient id="paint0_linear_28400_184664" x1="69.2016" y1="-7.32838" x2="-32.021" y2="93.9021" gradientUnits="userSpaceOnUse">
<stop stop-color="#17CCFC"/>
<stop offset="0.5" stop-color="#5D31FF"/>
<stop offset="1" stop-color="#F35AFF"/>
</linearGradient>
<clipPath id="clip0_28400_184664">
<rect width="320.833" height="100" fill="white" transform="translate(0.832031)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 4.7 KiB

BIN
public/dark-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
public/light-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

1
public/next.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
public/og.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 KiB

1
public/vercel.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 283 64"><path fill="black" d="M141 16c-11 0-19 7-19 18s9 18 20 18c7 0 13-3 16-7l-7-5c-2 3-6 4-9 4-5 0-9-3-10-7h28v-3c0-11-8-18-19-18zm-9 15c1-4 4-7 9-7s8 3 9 7h-18zm117-15c-11 0-19 7-19 18s9 18 20 18c6 0 12-3 16-7l-8-5c-2 3-5 4-8 4-5 0-9-3-11-7h28l1-3c0-11-8-18-19-18zm-10 15c2-4 5-7 10-7s8 3 9 7h-19zm-39 3c0 6 4 10 10 10 4 0 7-2 9-5l8 5c-3 5-9 8-17 8-11 0-19-7-19-18s8-18 19-18c8 0 14 3 17 8l-8 5c-2-3-5-5-9-5-6 0-10 4-10 10zm83-29v46h-9V5h9zM37 0l37 64H0L37 0zm92 5-27 48L74 5h10l18 30 17-30h10zm59 12v10l-3-1c-6 0-10 4-10 10v15h-9V17h9v9c0-5 6-9 13-9z"/></svg>

After

Width:  |  Height:  |  Size: 629 B

20
tailwind.config.js Normal file
View File

@@ -0,0 +1,20 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
"./components/**/*.{js,ts,jsx,tsx,mdx}",
"./app/**/*.{js,ts,jsx,tsx,mdx}",
],
theme: {
extend: {
colors: {
"primary-600": "#6C47FF",
"primary-700": "#5639CC",
"primary-50": "#F4F2FF",
"success-700": "#027A48",
"success-50": "#ECFDF3",
},
},
},
plugins: [],
};

28
tsconfig.json Normal file
View File

@@ -0,0 +1,28 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}