Setup app
@@ -1,9 +0,0 @@
|
||||
/** @type {import("eslint").Linter.Config} */
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: ["@unsend/eslint-config/next.js"],
|
||||
parser: "@typescript-eslint/parser",
|
||||
parserOptions: {
|
||||
project: true,
|
||||
},
|
||||
};
|
@@ -1,28 +0,0 @@
|
||||
## Getting Started
|
||||
|
||||
First, run the development server:
|
||||
|
||||
```bash
|
||||
yarn dev
|
||||
```
|
||||
|
||||
Open [http://localhost:3001](http://localhost:3001) with your browser to see the result.
|
||||
|
||||
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
||||
|
||||
To create [API routes](https://nextjs.org/docs/app/building-your-application/routing/router-handlers) add an `api/` directory to the `app/` directory with a `route.ts` file. For individual endpoints, create a subfolder in the `api` directory, like `api/hello/route.ts` would map to [http://localhost:3001/api/hello](http://localhost:3001/api/hello).
|
||||
|
||||
## Learn More
|
||||
|
||||
To learn more about Next.js, take a look at the following resources:
|
||||
|
||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||
- [Learn Next.js](https://nextjs.org/learn/foundations/about-nextjs) - an interactive Next.js tutorial.
|
||||
|
||||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
|
||||
|
||||
## Deploy on Vercel
|
||||
|
||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_source=github.com&utm_medium=referral&utm_campaign=turborepo-readme) from the creators of Next.js.
|
||||
|
||||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
|
Before Width: | Height: | Size: 8.1 KiB |
@@ -1,50 +0,0 @@
|
||||
:root {
|
||||
--max-width: 1100px;
|
||||
--border-radius: 12px;
|
||||
--font-mono: ui-monospace, Menlo, Monaco, "Cascadia Mono", "Segoe UI Mono",
|
||||
"Roboto Mono", "Oxygen Mono", "Ubuntu Monospace", "Source Code Pro",
|
||||
"Fira Mono", "Droid Sans Mono", "Courier New", monospace;
|
||||
|
||||
--foreground-rgb: 255, 255, 255;
|
||||
--background-start-rgb: 0, 0, 0;
|
||||
--background-end-rgb: 0, 0, 0;
|
||||
|
||||
--callout-rgb: 20, 20, 20;
|
||||
--callout-border-rgb: 108, 108, 108;
|
||||
--card-rgb: 100, 100, 100;
|
||||
--card-border-rgb: 200, 200, 200;
|
||||
|
||||
--glow-conic: conic-gradient(
|
||||
from 180deg at 50% 50%,
|
||||
#2a8af6 0deg,
|
||||
#a853ba 180deg,
|
||||
#e92a67 360deg
|
||||
);
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
max-width: 100vw;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
body {
|
||||
color: rgb(var(--foreground-rgb));
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
transparent,
|
||||
rgb(var(--background-end-rgb))
|
||||
)
|
||||
rgb(var(--background-start-rgb));
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
@@ -1,22 +0,0 @@
|
||||
import "./globals.css";
|
||||
import type { Metadata } from "next";
|
||||
import { Inter } from "next/font/google";
|
||||
|
||||
const inter = Inter({ subsets: ["latin"] });
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Create Turborepo",
|
||||
description: "Generated by create turbo",
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}): JSX.Element {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className={inter.className}>{children}</body>
|
||||
</html>
|
||||
);
|
||||
}
|
@@ -1,335 +0,0 @@
|
||||
.main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 6rem;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.vercelLogo {
|
||||
filter: invert(1);
|
||||
}
|
||||
|
||||
.description {
|
||||
display: inherit;
|
||||
justify-content: inherit;
|
||||
align-items: inherit;
|
||||
font-size: 0.85rem;
|
||||
max-width: var(--max-width);
|
||||
width: 100%;
|
||||
z-index: 2;
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
.description a {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.description p {
|
||||
position: relative;
|
||||
margin: 0;
|
||||
padding: 1rem;
|
||||
background-color: rgba(var(--callout-rgb), 0.5);
|
||||
border: 1px solid rgba(var(--callout-border-rgb), 0.3);
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
.code {
|
||||
font-weight: 700;
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
.hero {
|
||||
display: flex;
|
||||
position: relative;
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
.heroContent {
|
||||
display: flex;
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
padding-bottom: 4rem;
|
||||
flex-direction: column;
|
||||
gap: 2rem;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: auto;
|
||||
font-family: system-ui, "Segoe UI", Roboto, "Helvetica Neue", Arial,
|
||||
"Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji",
|
||||
"Segoe UI Symbol", "Noto Color Emoji";
|
||||
padding-top: 48px;
|
||||
|
||||
@media (min-width: 768px) {
|
||||
padding-top: 4rem;
|
||||
padding-bottom: 6rem;
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
padding-top: 5rem;
|
||||
padding-bottom: 8rem;
|
||||
}
|
||||
}
|
||||
|
||||
.logos {
|
||||
display: flex;
|
||||
z-index: 50;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(25%, auto));
|
||||
max-width: 100%;
|
||||
width: var(--max-width);
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 1rem 1.2rem;
|
||||
border-radius: var(--border-radius);
|
||||
background: rgba(var(--card-rgb), 0);
|
||||
border: 1px solid rgba(var(--card-border-rgb), 0);
|
||||
transition: background 200ms, border 200ms;
|
||||
}
|
||||
|
||||
.card span {
|
||||
display: inline-block;
|
||||
transition: transform 200ms;
|
||||
}
|
||||
|
||||
.card h2 {
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.7rem;
|
||||
}
|
||||
|
||||
.card p {
|
||||
margin: 0;
|
||||
opacity: 0.6;
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.5;
|
||||
max-width: 30ch;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion) {
|
||||
.card:hover span {
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Mobile */
|
||||
@media (max-width: 700px) {
|
||||
.content {
|
||||
padding: 4rem;
|
||||
}
|
||||
|
||||
.grid {
|
||||
grid-template-columns: 1fr;
|
||||
margin-bottom: 120px;
|
||||
max-width: 320px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 1rem 2.5rem;
|
||||
}
|
||||
|
||||
.card h2 {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.center {
|
||||
padding: 8rem 0 6rem;
|
||||
}
|
||||
|
||||
.center::before {
|
||||
transform: none;
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
.description {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.description a {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.description p,
|
||||
.description div {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.description p {
|
||||
align-items: center;
|
||||
inset: 0 0 auto;
|
||||
padding: 2rem 1rem 1.4rem;
|
||||
border-radius: 0;
|
||||
border: none;
|
||||
border-bottom: 1px solid rgba(var(--callout-border-rgb), 0.25);
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
rgba(var(--background-start-rgb), 1),
|
||||
rgba(var(--callout-rgb), 0.5)
|
||||
);
|
||||
background-clip: padding-box;
|
||||
backdrop-filter: blur(24px);
|
||||
}
|
||||
|
||||
.description div {
|
||||
align-items: flex-end;
|
||||
pointer-events: none;
|
||||
inset: auto 0 0;
|
||||
padding: 2rem;
|
||||
height: 200px;
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
transparent 0%,
|
||||
rgb(var(--background-end-rgb)) 40%
|
||||
);
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Enable hover only on non-touch devices */
|
||||
@media (hover: hover) and (pointer: fine) {
|
||||
.card:hover {
|
||||
background: rgba(var(--card-rgb), 0.1);
|
||||
border: 1px solid rgba(var(--card-border-rgb), 0.15);
|
||||
}
|
||||
|
||||
.card:hover span {
|
||||
transform: translateX(4px);
|
||||
}
|
||||
}
|
||||
|
||||
.circles {
|
||||
position: absolute;
|
||||
min-width: 614px;
|
||||
min-height: 614px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.logo {
|
||||
z-index: 50;
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
}
|
||||
|
||||
.logoGradientContainer {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
z-index: 50;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 16rem;
|
||||
height: 16rem;
|
||||
}
|
||||
|
||||
.turborepoWordmarkContainer {
|
||||
display: flex;
|
||||
z-index: 50;
|
||||
padding-left: 1.5rem;
|
||||
padding-right: 1.5rem;
|
||||
flex-direction: column;
|
||||
gap: 1.25rem;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
gap: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.turborepoWordmark {
|
||||
width: 160px;
|
||||
fill: white;
|
||||
|
||||
@media (min-width: 768px) {
|
||||
width: 200px;
|
||||
}
|
||||
}
|
||||
|
||||
.code {
|
||||
font-family: Menlo, Monaco, Consolas, "Liberation Mono", "Courier New",
|
||||
monospace;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* Tablet and Smaller Desktop */
|
||||
@media (min-width: 701px) and (max-width: 1120px) {
|
||||
.grid {
|
||||
grid-template-columns: repeat(2, 50%);
|
||||
}
|
||||
}
|
||||
|
||||
/* Gradients */
|
||||
.gradient {
|
||||
position: absolute;
|
||||
mix-blend-mode: normal;
|
||||
will-change: filter;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.gradientSmall {
|
||||
filter: blur(32px);
|
||||
}
|
||||
|
||||
.gradientLarge {
|
||||
filter: blur(75px);
|
||||
}
|
||||
|
||||
.glowConic {
|
||||
background-image: var(--glow-conic);
|
||||
}
|
||||
|
||||
.logoGradient {
|
||||
opacity: 0.9;
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
}
|
||||
|
||||
.backgroundGradient {
|
||||
top: -500px;
|
||||
width: 1000px;
|
||||
height: 1000px;
|
||||
opacity: 0.15;
|
||||
}
|
||||
|
||||
.button {
|
||||
background-color: #ffffff;
|
||||
border-radius: 8px;
|
||||
border-style: none;
|
||||
box-sizing: border-box;
|
||||
color: #000000;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
font-size: 16px;
|
||||
height: 40px;
|
||||
line-height: 20px;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
outline: none;
|
||||
padding: 10px 16px;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
transition: color 100ms;
|
||||
vertical-align: baseline;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
touch-action: manipulation;
|
||||
}
|
||||
|
||||
.button:hover,
|
||||
.button:focus {
|
||||
background-color: #e5e4e2;
|
||||
}
|
@@ -1,144 +0,0 @@
|
||||
import Image from "next/image";
|
||||
import { Card } from "@unsend/ui/card";
|
||||
import { Code } from "@unsend/ui/code";
|
||||
import styles from "./page.module.css";
|
||||
import { Button } from "@unsend/ui/button";
|
||||
|
||||
function Gradient({
|
||||
conic,
|
||||
className,
|
||||
small,
|
||||
}: {
|
||||
small?: boolean;
|
||||
conic?: boolean;
|
||||
className?: string;
|
||||
}): JSX.Element {
|
||||
return (
|
||||
<span
|
||||
className={[
|
||||
styles.gradient,
|
||||
conic ? styles.glowConic : undefined,
|
||||
small ? styles.gradientSmall : styles.gradientLarge,
|
||||
className,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" ")}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const LINKS = [
|
||||
{
|
||||
title: "Docs",
|
||||
href: "https://turbo.build/repo/docs",
|
||||
description: "Find in-depth information about Turborepo features and API.",
|
||||
},
|
||||
{
|
||||
title: "Learn",
|
||||
href: "https://turbo.build/repo/docs/handbook",
|
||||
description: "Learn more about monorepos with our handbook.",
|
||||
},
|
||||
{
|
||||
title: "Templates",
|
||||
href: "https://turbo.build/repo/docs/getting-started/from-example",
|
||||
description: "Choose from over 15 examples and deploy with a single click.",
|
||||
},
|
||||
{
|
||||
title: "Deploy",
|
||||
href: "https://vercel.com/new",
|
||||
description:
|
||||
"Instantly deploy your Turborepo to a shareable URL with Vercel.",
|
||||
},
|
||||
];
|
||||
|
||||
export default function Page(): JSX.Element {
|
||||
return (
|
||||
<main className={styles.main}>
|
||||
<div className={styles.description}>
|
||||
<p>
|
||||
examples/basic
|
||||
<Code className={styles.code}>docs</Code>
|
||||
</p>
|
||||
<div>
|
||||
<a
|
||||
href="https://vercel.com?utm_source=create-turbo&utm_medium=basic&utm_campaign=create-turbo"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
By{" "}
|
||||
<Image
|
||||
alt="Vercel Logo"
|
||||
className={styles.vercelLogo}
|
||||
height={24}
|
||||
priority
|
||||
src="/vercel.svg"
|
||||
width={100}
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button appName="docs" className={styles.button}>
|
||||
Click me!
|
||||
</Button>
|
||||
|
||||
<div className={styles.hero}>
|
||||
<div className={styles.heroContent}>
|
||||
<div className={styles.logos}>
|
||||
<div className={styles.circles}>
|
||||
<Image
|
||||
alt="Turborepo"
|
||||
height={614}
|
||||
src="circles.svg"
|
||||
width={614}
|
||||
style={{ pointerEvents: "none" }}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.logoGradientContainer}>
|
||||
<Gradient className={styles.logoGradient} conic small />
|
||||
</div>
|
||||
|
||||
<div className={styles.logo}>
|
||||
<Image
|
||||
alt=""
|
||||
height={120}
|
||||
priority
|
||||
src="turborepo.svg"
|
||||
width={120}
|
||||
style={{ pointerEvents: "none" }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Gradient className={styles.backgroundGradient} conic />
|
||||
<div className={styles.turborepoWordmarkContainer}>
|
||||
<svg
|
||||
className={styles.turborepoWordmark}
|
||||
viewBox="0 0 506 50"
|
||||
width={200}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<title>Turborepo logo</title>
|
||||
<path d="M53.7187 12.0038V1.05332H0.945312V12.0038H20.8673V48.4175H33.7968V12.0038H53.7187Z" />
|
||||
<path d="M83.5362 49.1431C99.764 49.1431 108.67 40.8972 108.67 27.3081V1.05332H95.7401V26.0547C95.7401 33.6409 91.7821 37.9287 83.5362 37.9287C75.2904 37.9287 71.3324 33.6409 71.3324 26.0547V1.05332H58.4029V27.3081C58.4029 40.8972 67.3084 49.1431 83.5362 49.1431Z" />
|
||||
<path d="M128.462 32.7174H141.325L151.484 48.4175H166.327L154.848 31.3321C161.313 29.0232 165.271 23.8778 165.271 16.8853C165.271 6.72646 157.685 1.05332 146.141 1.05332H115.532V48.4175H128.462V32.7174ZM128.462 22.4925V11.8719H145.481C150.033 11.8719 152.54 13.8509 152.54 17.2152C152.54 20.3816 150.033 22.4925 145.481 22.4925H128.462Z" />
|
||||
<path d="M171.287 48.4175H205.128C215.683 48.4175 221.752 43.404 221.752 35.0262C221.752 29.419 218.189 25.593 213.967 23.8778C216.87 22.4925 220.432 19.1942 220.432 13.9828C220.432 5.60502 214.495 1.05332 204.006 1.05332H171.287V48.4175ZM183.689 19.59V11.542H202.687C206.249 11.542 208.228 12.9273 208.228 15.566C208.228 18.2047 206.249 19.59 202.687 19.59H183.689ZM183.689 29.2871H203.875C207.371 29.2871 209.284 31.0022 209.284 33.5749C209.284 36.1476 207.371 37.8628 203.875 37.8628H183.689V29.2871Z" />
|
||||
<path d="M253.364 0.261719C236.806 0.261719 224.866 10.6185 224.866 24.7354C224.866 38.8523 236.806 49.2091 253.364 49.2091C269.922 49.2091 281.796 38.8523 281.796 24.7354C281.796 10.6185 269.922 0.261719 253.364 0.261719ZM253.364 11.4761C262.072 11.4761 268.602 16.6215 268.602 24.7354C268.602 32.8493 262.072 37.9947 253.364 37.9947C244.656 37.9947 238.126 32.8493 238.126 24.7354C238.126 16.6215 244.656 11.4761 253.364 11.4761Z" />
|
||||
<path d="M300.429 32.7174H313.292L323.451 48.4175H338.294L326.815 31.3321C333.28 29.0232 337.238 23.8778 337.238 16.8853C337.238 6.72646 329.652 1.05332 318.108 1.05332H287.499V48.4175H300.429V32.7174ZM300.429 22.4925V11.8719H317.448C322 11.8719 324.507 13.8509 324.507 17.2152C324.507 20.3816 322 22.4925 317.448 22.4925H300.429Z" />
|
||||
<path d="M343.254 1.05332V48.4175H389.299V37.467H355.92V29.7489H385.539V19.0622H355.92V12.0038H389.299V1.05332H343.254Z" />
|
||||
<path d="M408.46 33.3111H425.677C437.221 33.3111 444.807 27.7699 444.807 17.2152C444.807 6.59453 437.221 1.05332 425.677 1.05332H395.53V48.4175H408.46V33.3111ZM408.46 22.5585V11.8719H424.951C429.569 11.8719 432.076 13.8509 432.076 17.2152C432.076 20.5135 429.569 22.5585 424.951 22.5585H408.46Z" />
|
||||
<path d="M476.899 0.261719C460.341 0.261719 448.401 10.6185 448.401 24.7354C448.401 38.8523 460.341 49.2091 476.899 49.2091C493.456 49.2091 505.33 38.8523 505.33 24.7354C505.33 10.6185 493.456 0.261719 476.899 0.261719ZM476.899 11.4761C485.606 11.4761 492.137 16.6215 492.137 24.7354C492.137 32.8493 485.606 37.9947 476.899 37.9947C468.191 37.9947 461.66 32.8493 461.66 24.7354C461.66 16.6215 468.191 11.4761 476.899 11.4761Z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.grid}>
|
||||
{LINKS.map(({ title, href, description }) => (
|
||||
<Card className={styles.card} href={href} key={title} title={title}>
|
||||
{description}
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
5
apps/docs/next-env.d.ts
vendored
@@ -1,5 +0,0 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/basic-features/typescript for more information.
|
@@ -1,4 +0,0 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
module.exports = {
|
||||
transpilePackages: ["@unsend/ui"],
|
||||
};
|
@@ -1,28 +0,0 @@
|
||||
{
|
||||
"name": "docs",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev --port 3001",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "eslint . --max-warnings 0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@unsend/ui": "workspace:*",
|
||||
"next": "^14.1.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@next/eslint-plugin-next": "^14.1.1",
|
||||
"@unsend/eslint-config": "workspace:*",
|
||||
"@unsend/typescript-config": "workspace:*",
|
||||
"@types/eslint": "^8.56.5",
|
||||
"@types/node": "^20.11.24",
|
||||
"@types/react": "^18.2.61",
|
||||
"@types/react-dom": "^18.2.19",
|
||||
"eslint": "^8.57.0",
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
}
|
@@ -1,17 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 100 100" width="614" height="614">
|
||||
<defs xmlns="http://www.w3.org/2000/svg">
|
||||
<radialGradient id="radial" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#fff"></stop>
|
||||
<stop offset="60%" stop-color="#fff" stop-opacity="0"></stop>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
<circle cx="50" cy="50" r="25" stroke-width=".2" style="fill:none; stroke:rgba(255,255,255,.1);">
|
||||
<animate attributeName="opacity" values="1;0.1;0.1;1" dur="3s" begin="0.2s" repeatCount="indefinite"></animate>
|
||||
</circle>
|
||||
<circle cx="50" cy="50" r="25" stroke-width=".2" style="fill:url(#radial); fill-opacity:.1;">
|
||||
<animate attributeName="opacity" values="1;0.5;0.5;1" dur="3s" repeatCount="indefinite"></animate>
|
||||
</circle><circle cx="50" cy="50" r="45" stroke-width=".2" style="fill:none; stroke:rgba(255,255,255,.1);">
|
||||
<animate attributeName="opacity" values="1;0.1;0.1;1" dur="3s" begin="0.4s" repeatCount="indefinite"></animate>
|
||||
</circle>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.1 KiB |
@@ -1 +0,0 @@
|
||||
<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>
|
Before Width: | Height: | Size: 1.3 KiB |
@@ -1,32 +0,0 @@
|
||||
<svg width="104" height="104" viewBox="0 0 104 104" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1_17)">
|
||||
<path d="M26.0192 7C42.0962 -2.28203 61.9038 -2.28203 77.9808 7C94.0577 16.282 103.962 33.4359 103.962 52C103.962 70.5641 94.0577 87.718 77.9808 97C61.9038 106.282 42.0962 106.282 26.0192 97C9.94229 87.718 0.038475 70.5641 0.038475 52C0.038475 33.4359 9.94229 16.282 26.0192 7Z" fill="black" fill-opacity="0.64"/>
|
||||
<path d="M26.0192 7C42.0962 -2.28203 61.9038 -2.28203 77.9808 7C94.0577 16.282 103.962 33.4359 103.962 52C103.962 70.5641 94.0577 87.718 77.9808 97C61.9038 106.282 42.0962 106.282 26.0192 97C9.94229 87.718 0.038475 70.5641 0.038475 52C0.038475 33.4359 9.94229 16.282 26.0192 7Z" fill="url(#paint0_linear_1_17)" fill-opacity="0.15"/>
|
||||
<path d="M26.0192 7C42.0962 -2.28203 61.9038 -2.28203 77.9808 7C94.0577 16.282 103.962 33.4359 103.962 52C103.962 70.5641 94.0577 87.718 77.9808 97C61.9038 106.282 42.0962 106.282 26.0192 97C9.94229 87.718 0.038475 70.5641 0.038475 52C0.038475 33.4359 9.94229 16.282 26.0192 7Z" fill="black" fill-opacity="0.5"/>
|
||||
<path d="M0.538475 52C0.538475 33.6146 10.347 16.6257 26.2692 7.43301C42.1915 -1.7597 61.8085 -1.7597 77.7308 7.43301C93.653 16.6257 103.462 33.6146 103.462 52C103.462 70.3854 93.653 87.3743 77.7308 96.567C61.8085 105.76 42.1915 105.76 26.2692 96.567C10.347 87.3743 0.538475 70.3854 0.538475 52Z" stroke="url(#paint1_radial_1_17)" stroke-opacity="0.15"/>
|
||||
<path d="M0.538475 52C0.538475 33.6146 10.347 16.6257 26.2692 7.43301C42.1915 -1.7597 61.8085 -1.7597 77.7308 7.43301C93.653 16.6257 103.462 33.6146 103.462 52C103.462 70.3854 93.653 87.3743 77.7308 96.567C61.8085 105.76 42.1915 105.76 26.2692 96.567C10.347 87.3743 0.538475 70.3854 0.538475 52Z" stroke="url(#paint2_linear_1_17)" stroke-opacity="0.5"/>
|
||||
<path d="M51.8878 37.9262C44.1892 37.9262 37.9258 44.1896 37.9258 51.8882C37.9258 59.5868 44.1892 65.8502 51.8878 65.8502C59.5864 65.8502 65.8498 59.5868 65.8498 51.8882C65.8498 44.1896 59.5864 37.9262 51.8878 37.9262ZM51.8878 59.1136C47.8968 59.1136 44.6624 55.8792 44.6624 51.8882C44.6624 47.8972 47.8968 44.6628 51.8878 44.6628C55.8788 44.6628 59.1132 47.8972 59.1132 51.8882C59.1132 55.8792 55.8788 59.1136 51.8878 59.1136Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M53.0581 35.633V30.42C64.3889 31.0258 73.3901 40.4066 73.3901 51.8882C73.3901 63.3698 64.3889 72.748 53.0581 73.3564V68.1434C61.5029 67.5402 68.1901 60.4838 68.1901 51.8882C68.1901 43.2926 61.5029 36.2362 53.0581 35.633ZM39.5745 62.5482C37.3359 59.9638 35.8929 56.6722 35.6355 53.0582H30.4199C30.6903 58.1152 32.7131 62.7042 35.8825 66.2376L39.5719 62.5482H39.5745ZM50.7182 73.3564V68.1434C47.1016 67.886 43.81 66.4456 41.2256 64.2044L37.5362 67.8938C41.0722 71.0658 45.6612 73.086 50.7156 73.3564H50.7182Z" fill="url(#paint3_linear_1_17)"/>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_1_17" x1="52" y1="-8" x2="52" y2="112" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#3286F1"/>
|
||||
<stop offset="1" stop-color="#C43AC4"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="paint1_radial_1_17" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(52 -7.99999) rotate(90) scale(154.286 154.286)">
|
||||
<stop stop-color="white"/>
|
||||
<stop offset="1" stop-color="white"/>
|
||||
</radialGradient>
|
||||
<linearGradient id="paint2_linear_1_17" x1="-8" y1="-8" x2="18.25" y2="40.75" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="white"/>
|
||||
<stop offset="1" stop-color="white" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint3_linear_1_17" x1="53.9007" y1="33.4389" x2="32.7679" y2="54.5717" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#0096FF"/>
|
||||
<stop offset="1" stop-color="#FF1E56"/>
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_1_17">
|
||||
<rect width="104" height="104" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
Before Width: | Height: | Size: 3.7 KiB |
@@ -1 +0,0 @@
|
||||
<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>
|
Before Width: | Height: | Size: 629 B |
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"extends": "@unsend/typescript-config/nextjs.json",
|
||||
"compilerOptions": {
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"next.config.js",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts"
|
||||
],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
25
apps/web/.env.example
Normal file
@@ -0,0 +1,25 @@
|
||||
# Since the ".env" file is gitignored, you can use the ".env.example" file to
|
||||
# build a new ".env" file when you clone the repo. Keep this file up-to-date
|
||||
# when you add new variables to `.env`.
|
||||
|
||||
# This file will be committed to version control, so make sure not to have any
|
||||
# secrets in it. If you are cloning this repo, create a copy of this file named
|
||||
# ".env" and populate it with your secrets.
|
||||
|
||||
# When adding additional environment variables, the schema in "/src/env.js"
|
||||
# should be updated accordingly.
|
||||
|
||||
# Prisma
|
||||
# https://www.prisma.io/docs/reference/database-reference/connection-urls#env
|
||||
DATABASE_URL="postgresql://postgres:password@localhost:5432/web"
|
||||
|
||||
# Next Auth
|
||||
# You can generate a new secret on the command line with:
|
||||
# openssl rand -base64 32
|
||||
# https://next-auth.js.org/configuration/options#secret
|
||||
# NEXTAUTH_SECRET=""
|
||||
NEXTAUTH_URL="http://localhost:3000"
|
||||
|
||||
# Next Auth Discord Provider
|
||||
DISCORD_CLIENT_ID=""
|
||||
DISCORD_CLIENT_SECRET=""
|
42
apps/web/.gitignore
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# database
|
||||
/prisma/db.sqlite
|
||||
/prisma/db.sqlite-journal
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
next-env.d.ts
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# local env files
|
||||
# do not commit any .env files to git, except for the .env.example file. https://create.t3.gg/en/usage/env-variables#using-environment-variables
|
||||
.env
|
||||
.env*.local
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
@@ -1,28 +1,29 @@
|
||||
## Getting Started
|
||||
# Create T3 App
|
||||
|
||||
First, run the development server:
|
||||
This is a [T3 Stack](https://create.t3.gg/) project bootstrapped with `create-t3-app`.
|
||||
|
||||
```bash
|
||||
yarn dev
|
||||
```
|
||||
## What's next? How do I make an app with this?
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
We try to keep this project as simple as possible, so you can start with just the scaffolding we set up for you, and add additional things later when they become necessary.
|
||||
|
||||
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
||||
If you are not familiar with the different technologies used in this project, please refer to the respective docs. If you still are in the wind, please join our [Discord](https://t3.gg/discord) and ask for help.
|
||||
|
||||
To create [API routes](https://nextjs.org/docs/app/building-your-application/routing/router-handlers) add an `api/` directory to the `app/` directory with a `route.ts` file. For individual endpoints, create a subfolder in the `api` directory, like `api/hello/route.ts` would map to [http://localhost:3000/api/hello](http://localhost:3000/api/hello).
|
||||
- [Next.js](https://nextjs.org)
|
||||
- [NextAuth.js](https://next-auth.js.org)
|
||||
- [Prisma](https://prisma.io)
|
||||
- [Drizzle](https://orm.drizzle.team)
|
||||
- [Tailwind CSS](https://tailwindcss.com)
|
||||
- [tRPC](https://trpc.io)
|
||||
|
||||
## Learn More
|
||||
|
||||
To learn more about Next.js, take a look at the following resources:
|
||||
To learn more about the [T3 Stack](https://create.t3.gg/), take a look at the following resources:
|
||||
|
||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||
- [Learn Next.js](https://nextjs.org/learn/foundations/about-nextjs) - an interactive Next.js tutorial.
|
||||
- [Documentation](https://create.t3.gg/)
|
||||
- [Learn the T3 Stack](https://create.t3.gg/en/faq#what-learning-resources-are-currently-available) — Check out these awesome tutorials
|
||||
|
||||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
|
||||
You can check out the [create-t3-app GitHub repository](https://github.com/t3-oss/create-t3-app) — your feedback and contributions are welcome!
|
||||
|
||||
## Deploy on Vercel
|
||||
## How do I deploy this?
|
||||
|
||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_source=github.com&utm_medium=referral&utm_campaign=turborepo-readme) from the creators of Next.js.
|
||||
|
||||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
|
||||
Follow our deployment guides for [Vercel](https://create.t3.gg/en/deployment/vercel), [Netlify](https://create.t3.gg/en/deployment/netlify) and [Docker](https://create.t3.gg/en/deployment/docker) for more information.
|
||||
|
Before Width: | Height: | Size: 8.1 KiB |
@@ -1,50 +0,0 @@
|
||||
:root {
|
||||
--max-width: 1100px;
|
||||
--border-radius: 12px;
|
||||
--font-mono: ui-monospace, Menlo, Monaco, "Cascadia Mono", "Segoe UI Mono",
|
||||
"Roboto Mono", "Oxygen Mono", "Ubuntu Monospace", "Source Code Pro",
|
||||
"Fira Mono", "Droid Sans Mono", "Courier New", monospace;
|
||||
|
||||
--foreground-rgb: 255, 255, 255;
|
||||
--background-start-rgb: 0, 0, 0;
|
||||
--background-end-rgb: 0, 0, 0;
|
||||
|
||||
--callout-rgb: 20, 20, 20;
|
||||
--callout-border-rgb: 108, 108, 108;
|
||||
--card-rgb: 100, 100, 100;
|
||||
--card-border-rgb: 200, 200, 200;
|
||||
|
||||
--glow-conic: conic-gradient(
|
||||
from 180deg at 50% 50%,
|
||||
#2a8af6 0deg,
|
||||
#a853ba 180deg,
|
||||
#e92a67 360deg
|
||||
);
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
max-width: 100vw;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
body {
|
||||
color: rgb(var(--foreground-rgb));
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
transparent,
|
||||
rgb(var(--background-end-rgb))
|
||||
)
|
||||
rgb(var(--background-start-rgb));
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
@@ -1,22 +0,0 @@
|
||||
import "./globals.css";
|
||||
import type { Metadata } from "next";
|
||||
import { Inter } from "next/font/google";
|
||||
|
||||
const inter = Inter({ subsets: ["latin"] });
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Create Turborepo",
|
||||
description: "Generated by create turbo",
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}): JSX.Element {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className={inter.className}>{children}</body>
|
||||
</html>
|
||||
);
|
||||
}
|
@@ -1,335 +0,0 @@
|
||||
.main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 6rem;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.vercelLogo {
|
||||
filter: invert(1);
|
||||
}
|
||||
|
||||
.description {
|
||||
display: inherit;
|
||||
justify-content: inherit;
|
||||
align-items: inherit;
|
||||
font-size: 0.85rem;
|
||||
max-width: var(--max-width);
|
||||
width: 100%;
|
||||
z-index: 2;
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
.description a {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.description p {
|
||||
position: relative;
|
||||
margin: 0;
|
||||
padding: 1rem;
|
||||
background-color: rgba(var(--callout-rgb), 0.5);
|
||||
border: 1px solid rgba(var(--callout-border-rgb), 0.3);
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
.code {
|
||||
font-weight: 700;
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
.hero {
|
||||
display: flex;
|
||||
position: relative;
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
.heroContent {
|
||||
display: flex;
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
padding-bottom: 4rem;
|
||||
flex-direction: column;
|
||||
gap: 2rem;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: auto;
|
||||
font-family: system-ui, "Segoe UI", Roboto, "Helvetica Neue", Arial,
|
||||
"Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji",
|
||||
"Segoe UI Symbol", "Noto Color Emoji";
|
||||
padding-top: 48px;
|
||||
|
||||
@media (min-width: 768px) {
|
||||
padding-top: 4rem;
|
||||
padding-bottom: 6rem;
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
padding-top: 5rem;
|
||||
padding-bottom: 8rem;
|
||||
}
|
||||
}
|
||||
|
||||
.logos {
|
||||
display: flex;
|
||||
z-index: 50;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(25%, auto));
|
||||
max-width: 100%;
|
||||
width: var(--max-width);
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 1rem 1.2rem;
|
||||
border-radius: var(--border-radius);
|
||||
background: rgba(var(--card-rgb), 0);
|
||||
border: 1px solid rgba(var(--card-border-rgb), 0);
|
||||
transition: background 200ms, border 200ms;
|
||||
}
|
||||
|
||||
.card span {
|
||||
display: inline-block;
|
||||
transition: transform 200ms;
|
||||
}
|
||||
|
||||
.card h2 {
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.7rem;
|
||||
}
|
||||
|
||||
.card p {
|
||||
margin: 0;
|
||||
opacity: 0.6;
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.5;
|
||||
max-width: 30ch;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion) {
|
||||
.card:hover span {
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Mobile */
|
||||
@media (max-width: 700px) {
|
||||
.content {
|
||||
padding: 4rem;
|
||||
}
|
||||
|
||||
.grid {
|
||||
grid-template-columns: 1fr;
|
||||
margin-bottom: 120px;
|
||||
max-width: 320px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 1rem 2.5rem;
|
||||
}
|
||||
|
||||
.card h2 {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.center {
|
||||
padding: 8rem 0 6rem;
|
||||
}
|
||||
|
||||
.center::before {
|
||||
transform: none;
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
.description {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.description a {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.description p,
|
||||
.description div {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.description p {
|
||||
align-items: center;
|
||||
inset: 0 0 auto;
|
||||
padding: 2rem 1rem 1.4rem;
|
||||
border-radius: 0;
|
||||
border: none;
|
||||
border-bottom: 1px solid rgba(var(--callout-border-rgb), 0.25);
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
rgba(var(--background-start-rgb), 1),
|
||||
rgba(var(--callout-rgb), 0.5)
|
||||
);
|
||||
background-clip: padding-box;
|
||||
backdrop-filter: blur(24px);
|
||||
}
|
||||
|
||||
.description div {
|
||||
align-items: flex-end;
|
||||
pointer-events: none;
|
||||
inset: auto 0 0;
|
||||
padding: 2rem;
|
||||
height: 200px;
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
transparent 0%,
|
||||
rgb(var(--background-end-rgb)) 40%
|
||||
);
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Enable hover only on non-touch devices */
|
||||
@media (hover: hover) and (pointer: fine) {
|
||||
.card:hover {
|
||||
background: rgba(var(--card-rgb), 0.1);
|
||||
border: 1px solid rgba(var(--card-border-rgb), 0.15);
|
||||
}
|
||||
|
||||
.card:hover span {
|
||||
transform: translateX(4px);
|
||||
}
|
||||
}
|
||||
|
||||
.circles {
|
||||
position: absolute;
|
||||
min-width: 614px;
|
||||
min-height: 614px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.logo {
|
||||
z-index: 50;
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
}
|
||||
|
||||
.logoGradientContainer {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
z-index: 50;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 16rem;
|
||||
height: 16rem;
|
||||
}
|
||||
|
||||
.turborepoWordmarkContainer {
|
||||
display: flex;
|
||||
z-index: 50;
|
||||
padding-left: 1.5rem;
|
||||
padding-right: 1.5rem;
|
||||
flex-direction: column;
|
||||
gap: 1.25rem;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
gap: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.turborepoWordmark {
|
||||
width: 160px;
|
||||
fill: white;
|
||||
|
||||
@media (min-width: 768px) {
|
||||
width: 200px;
|
||||
}
|
||||
}
|
||||
|
||||
.code {
|
||||
font-family: Menlo, Monaco, Consolas, "Liberation Mono", "Courier New",
|
||||
monospace;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* Tablet and Smaller Desktop */
|
||||
@media (min-width: 701px) and (max-width: 1120px) {
|
||||
.grid {
|
||||
grid-template-columns: repeat(2, 50%);
|
||||
}
|
||||
}
|
||||
|
||||
/* Gradients */
|
||||
.gradient {
|
||||
position: absolute;
|
||||
mix-blend-mode: normal;
|
||||
will-change: filter;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.gradientSmall {
|
||||
filter: blur(32px);
|
||||
}
|
||||
|
||||
.gradientLarge {
|
||||
filter: blur(75px);
|
||||
}
|
||||
|
||||
.glowConic {
|
||||
background-image: var(--glow-conic);
|
||||
}
|
||||
|
||||
.logoGradient {
|
||||
opacity: 0.9;
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
}
|
||||
|
||||
.backgroundGradient {
|
||||
top: -500px;
|
||||
width: 1000px;
|
||||
height: 1000px;
|
||||
opacity: 0.15;
|
||||
}
|
||||
|
||||
.button {
|
||||
background-color: #ffffff;
|
||||
border-radius: 8px;
|
||||
border-style: none;
|
||||
box-sizing: border-box;
|
||||
color: #000000;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
font-size: 16px;
|
||||
height: 40px;
|
||||
line-height: 20px;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
outline: none;
|
||||
padding: 10px 16px;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
transition: color 100ms;
|
||||
vertical-align: baseline;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
touch-action: manipulation;
|
||||
}
|
||||
|
||||
.button:hover,
|
||||
.button:focus {
|
||||
background-color: #e5e4e2;
|
||||
}
|
@@ -1,144 +0,0 @@
|
||||
import Image from "next/image";
|
||||
import { Card } from "@unsend/ui/card";
|
||||
import { Code } from "@unsend/ui/code";
|
||||
import styles from "./page.module.css";
|
||||
import { Button } from "@unsend/ui/button";
|
||||
|
||||
function Gradient({
|
||||
conic,
|
||||
className,
|
||||
small,
|
||||
}: {
|
||||
small?: boolean;
|
||||
conic?: boolean;
|
||||
className?: string;
|
||||
}): JSX.Element {
|
||||
return (
|
||||
<span
|
||||
className={[
|
||||
styles.gradient,
|
||||
conic ? styles.glowConic : undefined,
|
||||
small ? styles.gradientSmall : styles.gradientLarge,
|
||||
className,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" ")}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const LINKS = [
|
||||
{
|
||||
title: "Docs",
|
||||
href: "https://turbo.build/repo/docs",
|
||||
description: "Find in-depth information about Turborepo features and API.",
|
||||
},
|
||||
{
|
||||
title: "Learn",
|
||||
href: "https://turbo.build/repo/docs/handbook",
|
||||
description: "Learn more about monorepos with our handbook.",
|
||||
},
|
||||
{
|
||||
title: "Templates",
|
||||
href: "https://turbo.build/repo/docs/getting-started/from-example",
|
||||
description: "Choose from over 15 examples and deploy with a single click.",
|
||||
},
|
||||
{
|
||||
title: "Deploy",
|
||||
href: "https://vercel.com/new",
|
||||
description:
|
||||
"Instantly deploy your Turborepo to a shareable URL with Vercel.",
|
||||
},
|
||||
];
|
||||
|
||||
export default function Page(): JSX.Element {
|
||||
return (
|
||||
<main className={styles.main}>
|
||||
<div className={styles.description}>
|
||||
<p>
|
||||
examples/basic
|
||||
<Code className={styles.code}>web</Code>
|
||||
</p>
|
||||
<div>
|
||||
<a
|
||||
href="https://vercel.com?utm_source=create-turbo&utm_medium=basic&utm_campaign=create-turbo"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
By{" "}
|
||||
<Image
|
||||
alt="Vercel Logo"
|
||||
className={styles.vercelLogo}
|
||||
height={24}
|
||||
priority
|
||||
src="/vercel.svg"
|
||||
width={100}
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button appName="web" className={styles.button}>
|
||||
Click me!
|
||||
</Button>
|
||||
|
||||
<div className={styles.hero}>
|
||||
<div className={styles.heroContent}>
|
||||
<div className={styles.logos}>
|
||||
<div className={styles.circles}>
|
||||
<Image
|
||||
alt=""
|
||||
height={614}
|
||||
src="circles.svg"
|
||||
width={614}
|
||||
style={{ pointerEvents: "none" }}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.logoGradientContainer}>
|
||||
<Gradient className={styles.logoGradient} conic small />
|
||||
</div>
|
||||
|
||||
<div className={styles.logo}>
|
||||
<Image
|
||||
alt="Turborepo"
|
||||
height={120}
|
||||
priority
|
||||
src="turborepo.svg"
|
||||
width={120}
|
||||
style={{ pointerEvents: "none" }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Gradient className={styles.backgroundGradient} conic />
|
||||
<div className={styles.turborepoWordmarkContainer}>
|
||||
<svg
|
||||
className={styles.turborepoWordmark}
|
||||
viewBox="0 0 506 50"
|
||||
width={200}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<title>Turborepo logo</title>
|
||||
<path d="M53.7187 12.0038V1.05332H0.945312V12.0038H20.8673V48.4175H33.7968V12.0038H53.7187Z" />
|
||||
<path d="M83.5362 49.1431C99.764 49.1431 108.67 40.8972 108.67 27.3081V1.05332H95.7401V26.0547C95.7401 33.6409 91.7821 37.9287 83.5362 37.9287C75.2904 37.9287 71.3324 33.6409 71.3324 26.0547V1.05332H58.4029V27.3081C58.4029 40.8972 67.3084 49.1431 83.5362 49.1431Z" />
|
||||
<path d="M128.462 32.7174H141.325L151.484 48.4175H166.327L154.848 31.3321C161.313 29.0232 165.271 23.8778 165.271 16.8853C165.271 6.72646 157.685 1.05332 146.141 1.05332H115.532V48.4175H128.462V32.7174ZM128.462 22.4925V11.8719H145.481C150.033 11.8719 152.54 13.8509 152.54 17.2152C152.54 20.3816 150.033 22.4925 145.481 22.4925H128.462Z" />
|
||||
<path d="M171.287 48.4175H205.128C215.683 48.4175 221.752 43.404 221.752 35.0262C221.752 29.419 218.189 25.593 213.967 23.8778C216.87 22.4925 220.432 19.1942 220.432 13.9828C220.432 5.60502 214.495 1.05332 204.006 1.05332H171.287V48.4175ZM183.689 19.59V11.542H202.687C206.249 11.542 208.228 12.9273 208.228 15.566C208.228 18.2047 206.249 19.59 202.687 19.59H183.689ZM183.689 29.2871H203.875C207.371 29.2871 209.284 31.0022 209.284 33.5749C209.284 36.1476 207.371 37.8628 203.875 37.8628H183.689V29.2871Z" />
|
||||
<path d="M253.364 0.261719C236.806 0.261719 224.866 10.6185 224.866 24.7354C224.866 38.8523 236.806 49.2091 253.364 49.2091C269.922 49.2091 281.796 38.8523 281.796 24.7354C281.796 10.6185 269.922 0.261719 253.364 0.261719ZM253.364 11.4761C262.072 11.4761 268.602 16.6215 268.602 24.7354C268.602 32.8493 262.072 37.9947 253.364 37.9947C244.656 37.9947 238.126 32.8493 238.126 24.7354C238.126 16.6215 244.656 11.4761 253.364 11.4761Z" />
|
||||
<path d="M300.429 32.7174H313.292L323.451 48.4175H338.294L326.815 31.3321C333.28 29.0232 337.238 23.8778 337.238 16.8853C337.238 6.72646 329.652 1.05332 318.108 1.05332H287.499V48.4175H300.429V32.7174ZM300.429 22.4925V11.8719H317.448C322 11.8719 324.507 13.8509 324.507 17.2152C324.507 20.3816 322 22.4925 317.448 22.4925H300.429Z" />
|
||||
<path d="M343.254 1.05332V48.4175H389.299V37.467H355.92V29.7489H385.539V19.0622H355.92V12.0038H389.299V1.05332H343.254Z" />
|
||||
<path d="M408.46 33.3111H425.677C437.221 33.3111 444.807 27.7699 444.807 17.2152C444.807 6.59453 437.221 1.05332 425.677 1.05332H395.53V48.4175H408.46V33.3111ZM408.46 22.5585V11.8719H424.951C429.569 11.8719 432.076 13.8509 432.076 17.2152C432.076 20.5135 429.569 22.5585 424.951 22.5585H408.46Z" />
|
||||
<path d="M476.899 0.261719C460.341 0.261719 448.401 10.6185 448.401 24.7354C448.401 38.8523 460.341 49.2091 476.899 49.2091C493.456 49.2091 505.33 38.8523 505.33 24.7354C505.33 10.6185 493.456 0.261719 476.899 0.261719ZM476.899 11.4761C485.606 11.4761 492.137 16.6215 492.137 24.7354C492.137 32.8493 485.606 37.9947 476.899 37.9947C468.191 37.9947 461.66 32.8493 461.66 24.7354C461.66 16.6215 468.191 11.4761 476.899 11.4761Z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.grid}>
|
||||
{LINKS.map(({ title, href, description }) => (
|
||||
<Card className={styles.card} href={href} key={title} title={title}>
|
||||
{description}
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
@@ -1,4 +1,10 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
module.exports = {
|
||||
transpilePackages: ["@unsend/ui"],
|
||||
};
|
||||
/**
|
||||
* Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially useful
|
||||
* for Docker builds.
|
||||
*/
|
||||
await import("./src/env.js");
|
||||
|
||||
/** @type {import("next").NextConfig} */
|
||||
const config = {};
|
||||
|
||||
export default config;
|
||||
|
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"name": "web",
|
||||
"version": "1.0.0",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
@@ -9,20 +10,43 @@
|
||||
"lint": "eslint . --max-warnings 0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@auth/prisma-adapter": "^1.4.0",
|
||||
"@unsend/db": "workspace:*",
|
||||
"@unsend/ui": "workspace:*",
|
||||
"next": "^14.1.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
"@t3-oss/env-nextjs": "^0.9.2",
|
||||
"@tanstack/react-query": "^5.25.0",
|
||||
"@trpc/client": "next",
|
||||
"@trpc/next": "next",
|
||||
"@trpc/react-query": "next",
|
||||
"@trpc/server": "next",
|
||||
"next": "^14.1.3",
|
||||
"next-auth": "^4.24.6",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"server-only": "^0.0.1",
|
||||
"superjson": "^2.2.1",
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@next/eslint-plugin-next": "^14.1.1",
|
||||
"@unsend/eslint-config": "workspace:*",
|
||||
"@unsend/typescript-config": "workspace:*",
|
||||
"@types/eslint": "^8.56.5",
|
||||
"@types/node": "^20.11.24",
|
||||
"@types/react": "^18.2.61",
|
||||
"@types/eslint": "^8.56.2",
|
||||
"@types/node": "^20.11.20",
|
||||
"@types/react": "^18.2.57",
|
||||
"@types/react-dom": "^18.2.19",
|
||||
"@typescript-eslint/eslint-plugin": "^7.1.1",
|
||||
"@typescript-eslint/parser": "^7.1.1",
|
||||
"@unsend/eslint-config": "workspace:*",
|
||||
"@unsend/tailwind-config": "workspace:*",
|
||||
"@unsend/typescript-config": "workspace:*",
|
||||
"eslint": "^8.57.0",
|
||||
"typescript": "^5.3.3"
|
||||
}
|
||||
"eslint-config-next": "^14.1.3",
|
||||
"postcss": "^8.4.34",
|
||||
"prettier": "^3.2.5",
|
||||
"prettier-plugin-tailwindcss": "^0.5.11",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "^5.4.2"
|
||||
},
|
||||
"ct3aMetadata": {
|
||||
"initVersion": "7.30.0"
|
||||
},
|
||||
"packageManager": "pnpm@8.9.2"
|
||||
}
|
7
apps/web/postcss.config.cjs
Normal file
@@ -0,0 +1,7 @@
|
||||
const config = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = config;
|
@@ -1,17 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 100 100" width="614" height="614">
|
||||
<defs xmlns="http://www.w3.org/2000/svg">
|
||||
<radialGradient id="radial" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#fff"></stop>
|
||||
<stop offset="60%" stop-color="#fff" stop-opacity="0"></stop>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
<circle cx="50" cy="50" r="25" stroke-width=".2" style="fill:none; stroke:rgba(255,255,255,.1);">
|
||||
<animate attributeName="opacity" values="1;0.1;0.1;1" dur="3s" begin="0.2s" repeatCount="indefinite"></animate>
|
||||
</circle>
|
||||
<circle cx="50" cy="50" r="25" stroke-width=".2" style="fill:url(#radial); fill-opacity:.1;">
|
||||
<animate attributeName="opacity" values="1;0.5;0.5;1" dur="3s" repeatCount="indefinite"></animate>
|
||||
</circle><circle cx="50" cy="50" r="45" stroke-width=".2" style="fill:none; stroke:rgba(255,255,255,.1);">
|
||||
<animate attributeName="opacity" values="1;0.1;0.1;1" dur="3s" begin="0.4s" repeatCount="indefinite"></animate>
|
||||
</circle>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.1 KiB |
BIN
apps/web/public/favicon.ico
Normal file
After Width: | Height: | Size: 15 KiB |
@@ -1 +0,0 @@
|
||||
<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>
|
Before Width: | Height: | Size: 1.3 KiB |
@@ -1,32 +0,0 @@
|
||||
<svg width="104" height="104" viewBox="0 0 104 104" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1_17)">
|
||||
<path d="M26.0192 7C42.0962 -2.28203 61.9038 -2.28203 77.9808 7C94.0577 16.282 103.962 33.4359 103.962 52C103.962 70.5641 94.0577 87.718 77.9808 97C61.9038 106.282 42.0962 106.282 26.0192 97C9.94229 87.718 0.038475 70.5641 0.038475 52C0.038475 33.4359 9.94229 16.282 26.0192 7Z" fill="black" fill-opacity="0.64"/>
|
||||
<path d="M26.0192 7C42.0962 -2.28203 61.9038 -2.28203 77.9808 7C94.0577 16.282 103.962 33.4359 103.962 52C103.962 70.5641 94.0577 87.718 77.9808 97C61.9038 106.282 42.0962 106.282 26.0192 97C9.94229 87.718 0.038475 70.5641 0.038475 52C0.038475 33.4359 9.94229 16.282 26.0192 7Z" fill="url(#paint0_linear_1_17)" fill-opacity="0.15"/>
|
||||
<path d="M26.0192 7C42.0962 -2.28203 61.9038 -2.28203 77.9808 7C94.0577 16.282 103.962 33.4359 103.962 52C103.962 70.5641 94.0577 87.718 77.9808 97C61.9038 106.282 42.0962 106.282 26.0192 97C9.94229 87.718 0.038475 70.5641 0.038475 52C0.038475 33.4359 9.94229 16.282 26.0192 7Z" fill="black" fill-opacity="0.5"/>
|
||||
<path d="M0.538475 52C0.538475 33.6146 10.347 16.6257 26.2692 7.43301C42.1915 -1.7597 61.8085 -1.7597 77.7308 7.43301C93.653 16.6257 103.462 33.6146 103.462 52C103.462 70.3854 93.653 87.3743 77.7308 96.567C61.8085 105.76 42.1915 105.76 26.2692 96.567C10.347 87.3743 0.538475 70.3854 0.538475 52Z" stroke="url(#paint1_radial_1_17)" stroke-opacity="0.15"/>
|
||||
<path d="M0.538475 52C0.538475 33.6146 10.347 16.6257 26.2692 7.43301C42.1915 -1.7597 61.8085 -1.7597 77.7308 7.43301C93.653 16.6257 103.462 33.6146 103.462 52C103.462 70.3854 93.653 87.3743 77.7308 96.567C61.8085 105.76 42.1915 105.76 26.2692 96.567C10.347 87.3743 0.538475 70.3854 0.538475 52Z" stroke="url(#paint2_linear_1_17)" stroke-opacity="0.5"/>
|
||||
<path d="M51.8878 37.9262C44.1892 37.9262 37.9258 44.1896 37.9258 51.8882C37.9258 59.5868 44.1892 65.8502 51.8878 65.8502C59.5864 65.8502 65.8498 59.5868 65.8498 51.8882C65.8498 44.1896 59.5864 37.9262 51.8878 37.9262ZM51.8878 59.1136C47.8968 59.1136 44.6624 55.8792 44.6624 51.8882C44.6624 47.8972 47.8968 44.6628 51.8878 44.6628C55.8788 44.6628 59.1132 47.8972 59.1132 51.8882C59.1132 55.8792 55.8788 59.1136 51.8878 59.1136Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M53.0581 35.633V30.42C64.3889 31.0258 73.3901 40.4066 73.3901 51.8882C73.3901 63.3698 64.3889 72.748 53.0581 73.3564V68.1434C61.5029 67.5402 68.1901 60.4838 68.1901 51.8882C68.1901 43.2926 61.5029 36.2362 53.0581 35.633ZM39.5745 62.5482C37.3359 59.9638 35.8929 56.6722 35.6355 53.0582H30.4199C30.6903 58.1152 32.7131 62.7042 35.8825 66.2376L39.5719 62.5482H39.5745ZM50.7182 73.3564V68.1434C47.1016 67.886 43.81 66.4456 41.2256 64.2044L37.5362 67.8938C41.0722 71.0658 45.6612 73.086 50.7156 73.3564H50.7182Z" fill="url(#paint3_linear_1_17)"/>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_1_17" x1="52" y1="-8" x2="52" y2="112" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#3286F1"/>
|
||||
<stop offset="1" stop-color="#C43AC4"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="paint1_radial_1_17" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(52 -7.99999) rotate(90) scale(154.286 154.286)">
|
||||
<stop stop-color="white"/>
|
||||
<stop offset="1" stop-color="white"/>
|
||||
</radialGradient>
|
||||
<linearGradient id="paint2_linear_1_17" x1="-8" y1="-8" x2="18.25" y2="40.75" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="white"/>
|
||||
<stop offset="1" stop-color="white" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint3_linear_1_17" x1="53.9007" y1="33.4389" x2="32.7679" y2="54.5717" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#0096FF"/>
|
||||
<stop offset="1" stop-color="#FF1E56"/>
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_1_17">
|
||||
<rect width="104" height="104" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
Before Width: | Height: | Size: 3.7 KiB |
@@ -1 +0,0 @@
|
||||
<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>
|
Before Width: | Height: | Size: 629 B |
43
apps/web/src/app/_components/create-post.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
"use client";
|
||||
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
|
||||
import { api } from "~/trpc/react";
|
||||
|
||||
export function CreatePost() {
|
||||
const router = useRouter();
|
||||
const [name, setName] = useState("");
|
||||
|
||||
const createPost = api.post.create.useMutation({
|
||||
onSuccess: () => {
|
||||
router.refresh();
|
||||
setName("");
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
createPost.mutate({ name });
|
||||
}}
|
||||
className="flex flex-col gap-2"
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Title"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
className="w-full rounded-full px-4 py-2 text-black"
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
className="rounded-full bg-white/10 px-10 py-3 font-semibold transition hover:bg-white/20"
|
||||
disabled={createPost.isPending}
|
||||
>
|
||||
{createPost.isPending ? "Submitting..." : "Submit"}
|
||||
</button>
|
||||
</form>
|
||||
);
|
||||
}
|
7
apps/web/src/app/api/auth/[...nextauth]/route.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import NextAuth from "next-auth";
|
||||
|
||||
import { authOptions } from "~/server/auth";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const handler = NextAuth(authOptions);
|
||||
export { handler as GET, handler as POST };
|
34
apps/web/src/app/api/trpc/[trpc]/route.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
|
||||
import { type NextRequest } from "next/server";
|
||||
|
||||
import { env } from "~/env";
|
||||
import { appRouter } from "~/server/api/root";
|
||||
import { createTRPCContext } from "~/server/api/trpc";
|
||||
|
||||
/**
|
||||
* This wraps the `createTRPCContext` helper and provides the required context for the tRPC API when
|
||||
* handling a HTTP request (e.g. when you make requests from Client Components).
|
||||
*/
|
||||
const createContext = async (req: NextRequest) => {
|
||||
return createTRPCContext({
|
||||
headers: req.headers,
|
||||
});
|
||||
};
|
||||
|
||||
const handler = (req: NextRequest) =>
|
||||
fetchRequestHandler({
|
||||
endpoint: "/api/trpc",
|
||||
req,
|
||||
router: appRouter,
|
||||
createContext: () => createContext(req),
|
||||
onError:
|
||||
env.NODE_ENV === "development"
|
||||
? ({ path, error }) => {
|
||||
console.error(
|
||||
`❌ tRPC failed on ${path ?? "<no-path>"}: ${error.message}`
|
||||
);
|
||||
}
|
||||
: undefined,
|
||||
});
|
||||
|
||||
export { handler as GET, handler as POST };
|
33
apps/web/src/app/layout.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import "@unsend/ui/styles/globals.css";
|
||||
|
||||
import { Inter } from "next/font/google";
|
||||
import { ThemeProvider } from "@unsend/ui/theme-provider";
|
||||
|
||||
import { TRPCReactProvider } from "~/trpc/react";
|
||||
|
||||
const inter = Inter({
|
||||
subsets: ["latin"],
|
||||
variable: "--font-sans",
|
||||
});
|
||||
|
||||
export const metadata = {
|
||||
title: "Create T3 App",
|
||||
description: "Generated by create-t3-app",
|
||||
icons: [{ rel: "icon", url: "/favicon.ico" }],
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className={`font-sans ${inter.variable}`}>
|
||||
<ThemeProvider attribute="class" defaultTheme="light">
|
||||
<TRPCReactProvider>{children}</TRPCReactProvider>
|
||||
</ThemeProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
84
apps/web/src/app/page.tsx
Normal file
@@ -0,0 +1,84 @@
|
||||
import Link from "next/link";
|
||||
|
||||
import { CreatePost } from "~/app/_components/create-post";
|
||||
import { getServerAuthSession } from "~/server/auth";
|
||||
import { api } from "~/trpc/server";
|
||||
import { Button } from "@unsend/ui/src/button";
|
||||
|
||||
export default async function Home() {
|
||||
const hello = await api.post.hello({ text: "from tRPC" });
|
||||
const session = await getServerAuthSession();
|
||||
|
||||
return (
|
||||
<main className="flex min-h-screen flex-col items-center justify-center bg-gradient-to-b from-[#2e026d] to-[#15162c] text-white">
|
||||
<div className="container flex flex-col items-center justify-center gap-12 px-4 py-16 ">
|
||||
<h1 className="text-5xl font-extrabold tracking-tight sm:text-[5rem]">
|
||||
Create <span className="text-[hsl(280,100%,70%)]">T3</span> App
|
||||
</h1>
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 md:gap-8">
|
||||
<Link
|
||||
className="flex max-w-xs flex-col gap-4 rounded-xl bg-white/10 p-4 hover:bg-white/20"
|
||||
href="https://create.t3.gg/en/usage/first-steps"
|
||||
target="_blank"
|
||||
>
|
||||
<h3 className="text-2xl font-bold">First Steps →</h3>
|
||||
<div className="text-lg">
|
||||
Just the basics - Everything you need to know to set up your
|
||||
database and authentication.
|
||||
</div>
|
||||
</Link>
|
||||
<Link
|
||||
className="flex max-w-xs flex-col gap-4 rounded-xl bg-white/10 p-4 hover:bg-white/20"
|
||||
href="https://create.t3.gg/en/introduction"
|
||||
target="_blank"
|
||||
>
|
||||
<h3 className="text-2xl font-bold">Documentation →</h3>
|
||||
<div className="text-lg">
|
||||
Learn more about Create T3 App, the libraries it uses, and how to
|
||||
deploy it.
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
<Button>Hello</Button>
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
<p className="text-2xl text-white">
|
||||
{hello ? hello.greeting : "Loading tRPC query..."}
|
||||
</p>
|
||||
|
||||
<div className="flex flex-col items-center justify-center gap-4">
|
||||
<p className="text-center text-2xl text-white">
|
||||
{session && <span>Logged in as {session.user?.name}</span>}
|
||||
</p>
|
||||
<Link
|
||||
href={session ? "/api/auth/signout" : "/api/auth/signin"}
|
||||
className="rounded-full bg-white/10 px-10 py-3 font-semibold no-underline transition hover:bg-white/20"
|
||||
>
|
||||
{session ? "Sign out" : "Sign in"}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CrudShowcase />
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
async function CrudShowcase() {
|
||||
const session = await getServerAuthSession();
|
||||
if (!session?.user) return null;
|
||||
|
||||
const latestPost = await api.post.getLatest();
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-xs">
|
||||
{latestPost ? (
|
||||
<p className="truncate">Your most recent post: {latestPost.name}</p>
|
||||
) : (
|
||||
<p>You have no posts yet.</p>
|
||||
)}
|
||||
|
||||
<CreatePost />
|
||||
</div>
|
||||
);
|
||||
}
|
62
apps/web/src/env.js
Normal file
@@ -0,0 +1,62 @@
|
||||
import { createEnv } from "@t3-oss/env-nextjs";
|
||||
import { z } from "zod";
|
||||
|
||||
export const env = createEnv({
|
||||
/**
|
||||
* Specify your server-side environment variables schema here. This way you can ensure the app
|
||||
* isn't built with invalid env vars.
|
||||
*/
|
||||
server: {
|
||||
DATABASE_URL: z
|
||||
.string()
|
||||
.url()
|
||||
.refine(
|
||||
(str) => !str.includes("YOUR_MYSQL_URL_HERE"),
|
||||
"You forgot to change the default URL"
|
||||
),
|
||||
NODE_ENV: z
|
||||
.enum(["development", "test", "production"])
|
||||
.default("development"),
|
||||
NEXTAUTH_SECRET:
|
||||
process.env.NODE_ENV === "production"
|
||||
? z.string()
|
||||
: z.string().optional(),
|
||||
NEXTAUTH_URL: z.preprocess(
|
||||
// This makes Vercel deployments not fail if you don't set NEXTAUTH_URL
|
||||
// Since NextAuth.js automatically uses the VERCEL_URL if present.
|
||||
(str) => process.env.VERCEL_URL ?? str,
|
||||
// VERCEL_URL doesn't include `https` so it cant be validated as a URL
|
||||
process.env.VERCEL ? z.string() : z.string().url()
|
||||
),
|
||||
},
|
||||
|
||||
/**
|
||||
* Specify your client-side environment variables schema here. This way you can ensure the app
|
||||
* isn't built with invalid env vars. To expose them to the client, prefix them with
|
||||
* `NEXT_PUBLIC_`.
|
||||
*/
|
||||
client: {
|
||||
// NEXT_PUBLIC_CLIENTVAR: z.string(),
|
||||
},
|
||||
|
||||
/**
|
||||
* You can't destruct `process.env` as a regular object in the Next.js edge runtimes (e.g.
|
||||
* middlewares) or client-side so we need to destruct manually.
|
||||
*/
|
||||
runtimeEnv: {
|
||||
DATABASE_URL: process.env.DATABASE_URL,
|
||||
NODE_ENV: process.env.NODE_ENV,
|
||||
NEXTAUTH_SECRET: process.env.NEXTAUTH_SECRET,
|
||||
NEXTAUTH_URL: process.env.NEXTAUTH_URL,
|
||||
},
|
||||
/**
|
||||
* Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially
|
||||
* useful for Docker builds.
|
||||
*/
|
||||
skipValidation: !!process.env.SKIP_ENV_VALIDATION,
|
||||
/**
|
||||
* Makes it so that empty strings are treated as undefined. `SOME_VAR: z.string()` and
|
||||
* `SOME_VAR=''` will throw an error.
|
||||
*/
|
||||
emptyStringAsUndefined: true,
|
||||
});
|
23
apps/web/src/server/api/root.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { postRouter } from "~/server/api/routers/post";
|
||||
import { createCallerFactory, createTRPCRouter } from "~/server/api/trpc";
|
||||
|
||||
/**
|
||||
* This is the primary router for your server.
|
||||
*
|
||||
* All routers added in /api/routers should be manually added here.
|
||||
*/
|
||||
export const appRouter = createTRPCRouter({
|
||||
post: postRouter,
|
||||
});
|
||||
|
||||
// export type definition of API
|
||||
export type AppRouter = typeof appRouter;
|
||||
|
||||
/**
|
||||
* Create a server-side caller for the tRPC API.
|
||||
* @example
|
||||
* const trpc = createCaller(createContext);
|
||||
* const res = await trpc.post.all();
|
||||
* ^? Post[]
|
||||
*/
|
||||
export const createCaller = createCallerFactory(appRouter);
|
42
apps/web/src/server/api/routers/post.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import {
|
||||
createTRPCRouter,
|
||||
protectedProcedure,
|
||||
publicProcedure,
|
||||
} from "~/server/api/trpc";
|
||||
|
||||
export const postRouter = createTRPCRouter({
|
||||
hello: publicProcedure
|
||||
.input(z.object({ text: z.string() }))
|
||||
.query(({ input }) => {
|
||||
return {
|
||||
greeting: `Hello ${input.text}`,
|
||||
};
|
||||
}),
|
||||
|
||||
create: protectedProcedure
|
||||
.input(z.object({ name: z.string().min(1) }))
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
// simulate a slow db call
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
|
||||
return ctx.db.post.create({
|
||||
data: {
|
||||
name: input.name,
|
||||
createdBy: { connect: { id: ctx.session.user.id } },
|
||||
},
|
||||
});
|
||||
}),
|
||||
|
||||
getLatest: protectedProcedure.query(({ ctx }) => {
|
||||
return ctx.db.post.findFirst({
|
||||
orderBy: { createdAt: "desc" },
|
||||
where: { createdBy: { id: ctx.session.user.id } },
|
||||
});
|
||||
}),
|
||||
|
||||
getSecretMessage: protectedProcedure.query(() => {
|
||||
return "you can now see this secret message!";
|
||||
}),
|
||||
});
|
108
apps/web/src/server/api/trpc.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* YOU PROBABLY DON'T NEED TO EDIT THIS FILE, UNLESS:
|
||||
* 1. You want to modify request context (see Part 1).
|
||||
* 2. You want to create a new middleware or type of procedure (see Part 3).
|
||||
*
|
||||
* TL;DR - This is where all the tRPC server stuff is created and plugged in. The pieces you will
|
||||
* need to use are documented accordingly near the end.
|
||||
*/
|
||||
|
||||
import { initTRPC, TRPCError } from "@trpc/server";
|
||||
import superjson from "superjson";
|
||||
import { ZodError } from "zod";
|
||||
|
||||
import { getServerAuthSession } from "~/server/auth";
|
||||
import { db } from "~/server/db";
|
||||
|
||||
/**
|
||||
* 1. CONTEXT
|
||||
*
|
||||
* This section defines the "contexts" that are available in the backend API.
|
||||
*
|
||||
* These allow you to access things when processing a request, like the database, the session, etc.
|
||||
*
|
||||
* This helper generates the "internals" for a tRPC context. The API handler and RSC clients each
|
||||
* wrap this and provides the required context.
|
||||
*
|
||||
* @see https://trpc.io/docs/server/context
|
||||
*/
|
||||
export const createTRPCContext = async (opts: { headers: Headers }) => {
|
||||
const session = await getServerAuthSession();
|
||||
|
||||
return {
|
||||
db,
|
||||
session,
|
||||
...opts,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 2. INITIALIZATION
|
||||
*
|
||||
* This is where the tRPC API is initialized, connecting the context and transformer. We also parse
|
||||
* ZodErrors so that you get typesafety on the frontend if your procedure fails due to validation
|
||||
* errors on the backend.
|
||||
*/
|
||||
const t = initTRPC.context<typeof createTRPCContext>().create({
|
||||
transformer: superjson,
|
||||
errorFormatter({ shape, error }) {
|
||||
return {
|
||||
...shape,
|
||||
data: {
|
||||
...shape.data,
|
||||
zodError:
|
||||
error.cause instanceof ZodError ? error.cause.flatten() : null,
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Create a server-side caller.
|
||||
*
|
||||
* @see https://trpc.io/docs/server/server-side-calls
|
||||
*/
|
||||
export const createCallerFactory = t.createCallerFactory;
|
||||
|
||||
/**
|
||||
* 3. ROUTER & PROCEDURE (THE IMPORTANT BIT)
|
||||
*
|
||||
* These are the pieces you use to build your tRPC API. You should import these a lot in the
|
||||
* "/src/server/api/routers" directory.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This is how you create new routers and sub-routers in your tRPC API.
|
||||
*
|
||||
* @see https://trpc.io/docs/router
|
||||
*/
|
||||
export const createTRPCRouter = t.router;
|
||||
|
||||
/**
|
||||
* Public (unauthenticated) procedure
|
||||
*
|
||||
* This is the base piece you use to build new queries and mutations on your tRPC API. It does not
|
||||
* guarantee that a user querying is authorized, but you can still access user session data if they
|
||||
* are logged in.
|
||||
*/
|
||||
export const publicProcedure = t.procedure;
|
||||
|
||||
/**
|
||||
* Protected (authenticated) procedure
|
||||
*
|
||||
* If you want a query or mutation to ONLY be accessible to logged in users, use this. It verifies
|
||||
* the session is valid and guarantees `ctx.session.user` is not null.
|
||||
*
|
||||
* @see https://trpc.io/docs/procedures
|
||||
*/
|
||||
export const protectedProcedure = t.procedure.use(({ ctx, next }) => {
|
||||
if (!ctx.session || !ctx.session.user) {
|
||||
throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||
}
|
||||
return next({
|
||||
ctx: {
|
||||
// infers the `session` as non-nullable
|
||||
session: { ...ctx.session, user: ctx.session.user },
|
||||
},
|
||||
});
|
||||
});
|
68
apps/web/src/server/auth.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { PrismaAdapter } from "@auth/prisma-adapter";
|
||||
import {
|
||||
getServerSession,
|
||||
type DefaultSession,
|
||||
type NextAuthOptions,
|
||||
} from "next-auth";
|
||||
import { type Adapter } from "next-auth/adapters";
|
||||
import DiscordProvider from "next-auth/providers/discord";
|
||||
|
||||
import { env } from "~/env";
|
||||
import { db } from "~/server/db";
|
||||
|
||||
/**
|
||||
* Module augmentation for `next-auth` types. Allows us to add custom properties to the `session`
|
||||
* object and keep type safety.
|
||||
*
|
||||
* @see https://next-auth.js.org/getting-started/typescript#module-augmentation
|
||||
*/
|
||||
declare module "next-auth" {
|
||||
interface Session extends DefaultSession {
|
||||
user: {
|
||||
id: string;
|
||||
// ...other properties
|
||||
// role: UserRole;
|
||||
} & DefaultSession["user"];
|
||||
}
|
||||
|
||||
// interface User {
|
||||
// // ...other properties
|
||||
// // role: UserRole;
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for NextAuth.js used to configure adapters, providers, callbacks, etc.
|
||||
*
|
||||
* @see https://next-auth.js.org/configuration/options
|
||||
*/
|
||||
export const authOptions: NextAuthOptions = {
|
||||
callbacks: {
|
||||
session: ({ session, user }) => ({
|
||||
...session,
|
||||
user: {
|
||||
...session.user,
|
||||
id: user.id,
|
||||
},
|
||||
}),
|
||||
},
|
||||
adapter: PrismaAdapter(db) as Adapter,
|
||||
providers: [
|
||||
/**
|
||||
* ...add more providers here.
|
||||
*
|
||||
* Most other providers require a bit more work than the Discord provider. For example, the
|
||||
* GitHub provider requires you to add the `refresh_token_expires_in` field to the Account
|
||||
* model. Refer to the NextAuth.js docs for the provider you want to use. Example:
|
||||
*
|
||||
* @see https://next-auth.js.org/providers/github
|
||||
*/
|
||||
],
|
||||
};
|
||||
|
||||
/**
|
||||
* Wrapper for `getServerSession` so that you don't need to import the `authOptions` in every file.
|
||||
*
|
||||
* @see https://next-auth.js.org/configuration/nextjs
|
||||
*/
|
||||
export const getServerAuthSession = () => getServerSession(authOptions);
|
17
apps/web/src/server/db.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
|
||||
import { env } from "~/env";
|
||||
|
||||
const createPrismaClient = () =>
|
||||
new PrismaClient({
|
||||
log:
|
||||
env.NODE_ENV === "development" ? ["query", "error", "warn"] : ["error"],
|
||||
});
|
||||
|
||||
const globalForPrisma = globalThis as unknown as {
|
||||
prisma: ReturnType<typeof createPrismaClient> | undefined;
|
||||
};
|
||||
|
||||
export const db = globalForPrisma.prisma ?? createPrismaClient();
|
||||
|
||||
if (env.NODE_ENV !== "production") globalForPrisma.prisma = db;
|
62
apps/web/src/trpc/react.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
"use client";
|
||||
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { loggerLink, unstable_httpBatchStreamLink } from "@trpc/client";
|
||||
import { createTRPCReact } from "@trpc/react-query";
|
||||
import { useState } from "react";
|
||||
import SuperJSON from "superjson";
|
||||
|
||||
import { type AppRouter } from "~/server/api/root";
|
||||
|
||||
const createQueryClient = () => new QueryClient();
|
||||
|
||||
let clientQueryClientSingleton: QueryClient | undefined = undefined;
|
||||
const getQueryClient = () => {
|
||||
if (typeof window === "undefined") {
|
||||
// Server: always make a new query client
|
||||
return createQueryClient();
|
||||
}
|
||||
// Browser: use singleton pattern to keep the same query client
|
||||
return (clientQueryClientSingleton ??= createQueryClient());
|
||||
};
|
||||
|
||||
export const api = createTRPCReact<AppRouter>();
|
||||
|
||||
export function TRPCReactProvider(props: { children: React.ReactNode }) {
|
||||
const queryClient = getQueryClient();
|
||||
|
||||
const [trpcClient] = useState(() =>
|
||||
api.createClient({
|
||||
links: [
|
||||
loggerLink({
|
||||
enabled: (op) =>
|
||||
process.env.NODE_ENV === "development" ||
|
||||
(op.direction === "down" && op.result instanceof Error),
|
||||
}),
|
||||
unstable_httpBatchStreamLink({
|
||||
transformer: SuperJSON,
|
||||
url: getBaseUrl() + "/api/trpc",
|
||||
headers: () => {
|
||||
const headers = new Headers();
|
||||
headers.set("x-trpc-source", "nextjs-react");
|
||||
return headers;
|
||||
},
|
||||
}),
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<api.Provider client={trpcClient} queryClient={queryClient}>
|
||||
{props.children}
|
||||
</api.Provider>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
}
|
||||
|
||||
function getBaseUrl() {
|
||||
if (typeof window !== "undefined") return window.location.origin;
|
||||
if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`;
|
||||
return `http://localhost:${process.env.PORT ?? 3000}`;
|
||||
}
|
22
apps/web/src/trpc/server.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import "server-only";
|
||||
|
||||
import { headers } from "next/headers";
|
||||
import { cache } from "react";
|
||||
|
||||
import { createCaller } from "~/server/api/root";
|
||||
import { createTRPCContext } from "~/server/api/trpc";
|
||||
|
||||
/**
|
||||
* This wraps the `createTRPCContext` helper and provides the required context for the tRPC API when
|
||||
* handling a tRPC call from a React Server Component.
|
||||
*/
|
||||
const createContext = cache(() => {
|
||||
const heads = new Headers(headers());
|
||||
heads.set("x-trpc-source", "rsc");
|
||||
|
||||
return createTRPCContext({
|
||||
headers: heads,
|
||||
});
|
||||
});
|
||||
|
||||
export const api = createCaller(createContext);
|
45
apps/web/start-database.sh
Executable file
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env bash
|
||||
# Use this script to start a docker container for a local development database
|
||||
|
||||
# TO RUN ON WINDOWS:
|
||||
# 1. Install WSL (Windows Subsystem for Linux) - https://learn.microsoft.com/en-us/windows/wsl/install
|
||||
# 2. Install Docker Desktop for Windows - https://docs.docker.com/docker-for-windows/install/
|
||||
# 3. Open WSL - `wsl`
|
||||
# 4. Run this script - `./start-database.sh`
|
||||
|
||||
# On Linux and macOS you can run this script directly - `./start-database.sh`
|
||||
|
||||
DB_CONTAINER_NAME="web-postgres"
|
||||
|
||||
if ! [ -x "$(command -v docker)" ]; then
|
||||
echo "Docker is not installed. Please install docker and try again.\nDocker install guide: https://docs.docker.com/engine/install/"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$(docker ps -q -f name=$DB_CONTAINER_NAME)" ]; then
|
||||
docker start $DB_CONTAINER_NAME
|
||||
echo "Database container started"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# import env variables from .env
|
||||
set -a
|
||||
source .env
|
||||
|
||||
DB_PASSWORD=$(echo $DATABASE_URL | awk -F':' '{print $3}' | awk -F'@' '{print $1}')
|
||||
|
||||
if [ "$DB_PASSWORD" = "password" ]; then
|
||||
echo "You are using the default database password"
|
||||
read -p "Should we generate a random password for you? [y/N]: " -r REPLY
|
||||
if ! [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||
echo "Please set a password in the .env file and try again"
|
||||
exit 1
|
||||
fi
|
||||
# Generate a random URL-safe password
|
||||
DB_PASSWORD=$(openssl rand -base64 12 | tr '+/' '-_')
|
||||
sed -i -e "s#:password@#:$DB_PASSWORD@#" .env
|
||||
fi
|
||||
|
||||
docker run --name $DB_CONTAINER_NAME -e POSTGRES_PASSWORD=$DB_PASSWORD -e POSTGRES_DB=web -d -p 5432:5432 docker.io/postgres
|
||||
|
||||
echo "Database container was successfully created"
|
11
apps/web/tailwind.config.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { type Config } from "tailwindcss";
|
||||
import sharedConfig from "@unsend/tailwind-config/tailwind.config";
|
||||
import path from "path";
|
||||
|
||||
export default {
|
||||
...sharedConfig,
|
||||
content: [
|
||||
"./src/**/*.tsx",
|
||||
`${path.join(require.resolve("@unsend/ui"), "..")}/**/*.{ts,tsx}`,
|
||||
],
|
||||
} satisfies Config;
|
@@ -1,17 +1,23 @@
|
||||
{
|
||||
"extends": "@unsend/typescript-config/nextjs.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"~/*": ["./src/*"]
|
||||
},
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"next.config.js",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
"**/*.cjs",
|
||||
"**/*.js",
|
||||
".next/types/**/*.ts"
|
||||
],
|
||||
"exclude": ["node_modules"]
|
||||
|
18
package.json
@@ -3,18 +3,28 @@
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "turbo build",
|
||||
"dev": "turbo dev",
|
||||
"dev": "turbo dev --filter=web",
|
||||
"lint": "turbo lint",
|
||||
"format": "prettier --write \"**/*.{ts,tsx,md}\""
|
||||
"format": "prettier --write \"**/*.{ts,tsx,md}\"",
|
||||
"db:generate": "pnpm db generate",
|
||||
"db:push": "pnpm db push",
|
||||
"db:migrate-dev": "pnpm db migrate-dev",
|
||||
"db:migrate-deploy": "pnpm db migrate-deploy",
|
||||
"db:studio": "pnpm db studio",
|
||||
"db": "pnpm load-env -- pnpm --filter @unsend/db",
|
||||
"load-env": "dotenv -e .env"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@unsend/eslint-config": "workspace:*",
|
||||
"@unsend/typescript-config": "workspace:*",
|
||||
"prettier": "^3.2.5",
|
||||
"turbo": "latest"
|
||||
"dotenv-cli": "^7.4.1",
|
||||
"prettier": "^3.2.5"
|
||||
},
|
||||
"packageManager": "pnpm@8.9.0",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"dependencies": {
|
||||
"turbo": "^1.12.5"
|
||||
}
|
||||
}
|
3
packages/db/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
node_modules
|
||||
# Keep environment variables out of version control
|
||||
.env
|
1
packages/db/client.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "@prisma/client";
|
21
packages/db/index.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
|
||||
declare global {
|
||||
// We need `var` to declare a global variable in TypeScript
|
||||
// eslint-disable-next-line no-var
|
||||
var prisma: PrismaClient | undefined;
|
||||
}
|
||||
|
||||
if (!globalThis.prisma) {
|
||||
globalThis.prisma = new PrismaClient({
|
||||
datasourceUrl: process.env.DATABASE_URL,
|
||||
});
|
||||
}
|
||||
|
||||
export const prisma =
|
||||
globalThis.prisma ||
|
||||
new PrismaClient({
|
||||
datasourceUrl: process.env.DATABASE_URL,
|
||||
});
|
||||
|
||||
export const getPrismaClient = () => prisma;
|
18
packages/db/package.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "@unsend/db",
|
||||
"version": "0.0.0",
|
||||
"main": "./index.ts",
|
||||
"types": "./index.ts",
|
||||
"dependencies": {
|
||||
"@prisma/client": "^5.11.0",
|
||||
"prisma": "^5.11.0"
|
||||
},
|
||||
"scripts": {
|
||||
"post-install": "prisma generate",
|
||||
"generate": "prisma generate",
|
||||
"push": "prisma db push --skip-generate",
|
||||
"migrate-dev": "prisma migrate dev",
|
||||
"migrate-deploy": "prisma migrate deploy",
|
||||
"studio": "prisma studio"
|
||||
}
|
||||
}
|
73
packages/db/prisma/schema.prisma
Normal file
@@ -0,0 +1,73 @@
|
||||
// This is your Prisma schema file,
|
||||
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
// NOTE: When using mysql or sqlserver, uncomment the @db.Text annotations in model Account below
|
||||
// Further reading:
|
||||
// https://next-auth.js.org/adapters/prisma#create-the-prisma-schema
|
||||
// https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference#string
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
model Post {
|
||||
id Int @id @default(autoincrement())
|
||||
name String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
createdBy User @relation(fields: [createdById], references: [id])
|
||||
createdById String
|
||||
|
||||
@@index([name])
|
||||
}
|
||||
|
||||
// Necessary for Next auth
|
||||
model Account {
|
||||
id String @id @default(cuid())
|
||||
userId String
|
||||
type String
|
||||
provider String
|
||||
providerAccountId String
|
||||
refresh_token String? // @db.Text
|
||||
access_token String? // @db.Text
|
||||
expires_at Int?
|
||||
token_type String?
|
||||
scope String?
|
||||
id_token String? // @db.Text
|
||||
session_state String?
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([provider, providerAccountId])
|
||||
}
|
||||
|
||||
model Session {
|
||||
id String @id @default(cuid())
|
||||
sessionToken String @unique
|
||||
userId String
|
||||
expires DateTime
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
}
|
||||
|
||||
model User {
|
||||
id String @id @default(cuid())
|
||||
name String?
|
||||
email String? @unique
|
||||
emailVerified DateTime?
|
||||
image String?
|
||||
accounts Account[]
|
||||
sessions Session[]
|
||||
posts Post[]
|
||||
}
|
||||
|
||||
model VerificationToken {
|
||||
identifier String
|
||||
token String @unique
|
||||
expires DateTime
|
||||
|
||||
@@unique([identifier, token])
|
||||
}
|
21
packages/tailwind-config/package.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "@unsend/tailwind-config",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"tailwindcss-animate": "^1.0.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.11.24",
|
||||
"autoprefixer": "^10.4.18",
|
||||
"postcss": "^8.4.35",
|
||||
"tailwindcss": "^3.4.1"
|
||||
}
|
||||
}
|
79
packages/tailwind-config/tailwind.config.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import type { Config } from "tailwindcss";
|
||||
|
||||
const config = {
|
||||
darkMode: ["class"],
|
||||
content: ["./src/**/*.{ts,tsx}"],
|
||||
prefix: "",
|
||||
theme: {
|
||||
container: {
|
||||
center: true,
|
||||
padding: "2rem",
|
||||
screens: {
|
||||
"2xl": "1400px",
|
||||
},
|
||||
},
|
||||
extend: {
|
||||
colors: {
|
||||
border: "hsl(var(--border))",
|
||||
input: "hsl(var(--input))",
|
||||
ring: "hsl(var(--ring))",
|
||||
background: "hsl(var(--background))",
|
||||
foreground: "hsl(var(--foreground))",
|
||||
primary: {
|
||||
DEFAULT: "hsl(var(--primary))",
|
||||
foreground: "hsl(var(--primary-foreground))",
|
||||
},
|
||||
secondary: {
|
||||
DEFAULT: "hsl(var(--secondary))",
|
||||
foreground: "hsl(var(--secondary-foreground))",
|
||||
},
|
||||
destructive: {
|
||||
DEFAULT: "hsl(var(--destructive))",
|
||||
foreground: "hsl(var(--destructive-foreground))",
|
||||
},
|
||||
muted: {
|
||||
DEFAULT: "hsl(var(--muted))",
|
||||
foreground: "hsl(var(--muted-foreground))",
|
||||
},
|
||||
accent: {
|
||||
DEFAULT: "hsl(var(--accent))",
|
||||
foreground: "hsl(var(--accent-foreground))",
|
||||
},
|
||||
popover: {
|
||||
DEFAULT: "hsl(var(--popover))",
|
||||
foreground: "hsl(var(--popover-foreground))",
|
||||
},
|
||||
card: {
|
||||
DEFAULT: "hsl(var(--card))",
|
||||
foreground: "hsl(var(--card-foreground))",
|
||||
},
|
||||
},
|
||||
borderRadius: {
|
||||
lg: "var(--radius)",
|
||||
md: "calc(var(--radius) - 2px)",
|
||||
sm: "calc(var(--radius) - 4px)",
|
||||
},
|
||||
keyframes: {
|
||||
"accordion-down": {
|
||||
from: { height: "0" },
|
||||
to: { height: "var(--radix-accordion-content-height)" },
|
||||
},
|
||||
"accordion-up": {
|
||||
from: { height: "var(--radix-accordion-content-height)" },
|
||||
to: { height: "0" },
|
||||
},
|
||||
},
|
||||
animation: {
|
||||
"accordion-down": "accordion-down 0.2s ease-out",
|
||||
"accordion-up": "accordion-up 0.2s ease-out",
|
||||
},
|
||||
// fontFamily: {
|
||||
// sans: ["var(--font-geist-sans)"],
|
||||
// mono: ["var(--font-geist-mono)"],
|
||||
// },
|
||||
},
|
||||
},
|
||||
plugins: [require("tailwindcss-animate")],
|
||||
} satisfies Config;
|
||||
|
||||
export default config;
|
9
packages/tailwind-config/tsconfig.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "@unsend/typescript-config/base.json",
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"noEmit": true
|
||||
},
|
||||
"include": ["tailwind.config.ts"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
1
packages/ui/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export {};
|
6
packages/ui/lib/utils.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { type ClassValue, clsx } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
@@ -2,25 +2,39 @@
|
||||
"name": "@unsend/ui",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"exports": {
|
||||
"./button": "./src/button.tsx",
|
||||
"./card": "./src/card.tsx",
|
||||
"./code": "./src/code.tsx"
|
||||
},
|
||||
"main": "./index.ts",
|
||||
"types": "./index.ts",
|
||||
"files": [
|
||||
"src"
|
||||
],
|
||||
"scripts": {
|
||||
"lint": "eslint . --max-warnings 0",
|
||||
"generate:component": "turbo gen react-component"
|
||||
"lint": "eslint . --max-warnings 0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@unsend/eslint-config": "workspace:*",
|
||||
"@unsend/typescript-config": "workspace:*",
|
||||
"@turbo/gen": "^1.12.4",
|
||||
"@types/node": "^20.11.24",
|
||||
"@types/eslint": "^8.56.5",
|
||||
"@types/node": "^20.11.24",
|
||||
"@types/react": "^18.2.61",
|
||||
"@types/react-dom": "^18.2.19",
|
||||
"@unsend/eslint-config": "workspace:*",
|
||||
"@unsend/tailwind-config": "workspace:*",
|
||||
"@unsend/typescript-config": "workspace:*",
|
||||
"eslint": "^8.57.0",
|
||||
"postcss": "^8.4.36",
|
||||
"prettier": "^3.2.5",
|
||||
"prettier-plugin-tailwindcss": "^0.5.12",
|
||||
"react": "^18.2.0",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@radix-ui/react-slot": "^1.0.2",
|
||||
"add": "^2.0.6",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.0",
|
||||
"lucide-react": "^0.359.0",
|
||||
"next-themes": "^0.3.0",
|
||||
"pnpm": "^8.15.5",
|
||||
"tailwind-merge": "^2.2.2",
|
||||
"tailwindcss-animate": "^1.0.7"
|
||||
}
|
||||
}
|
@@ -1,20 +1,56 @@
|
||||
"use client";
|
||||
import * as React from "react";
|
||||
import { Slot } from "@radix-ui/react-slot";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
|
||||
import { ReactNode } from "react";
|
||||
import { cn } from "../lib/utils";
|
||||
|
||||
interface ButtonProps {
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
appName: string;
|
||||
const buttonVariants = cva(
|
||||
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
||||
destructive:
|
||||
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
||||
outline:
|
||||
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
||||
secondary:
|
||||
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||
link: "text-primary underline-offset-4 hover:underline",
|
||||
},
|
||||
size: {
|
||||
default: "h-10 px-4 py-2",
|
||||
sm: "h-9 rounded-md px-3",
|
||||
lg: "h-11 rounded-md px-8",
|
||||
icon: "h-10 w-10",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export interface ButtonProps
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
VariantProps<typeof buttonVariants> {
|
||||
asChild?: boolean;
|
||||
}
|
||||
|
||||
export const Button = ({ children, className, appName }: ButtonProps) => {
|
||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
({ className, variant, size, asChild = false, ...props }, ref) => {
|
||||
const Comp = asChild ? Slot : "button";
|
||||
return (
|
||||
<button
|
||||
className={className}
|
||||
onClick={() => alert(`Hello from your ${appName} app!`)}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
<Comp
|
||||
className={cn(buttonVariants({ variant, size, className }))}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
}
|
||||
);
|
||||
Button.displayName = "Button";
|
||||
|
||||
export { Button, buttonVariants };
|
||||
|
@@ -1,25 +0,0 @@
|
||||
export function Card({
|
||||
className,
|
||||
title,
|
||||
children,
|
||||
href,
|
||||
}: {
|
||||
className?: string;
|
||||
title: string;
|
||||
children: React.ReactNode;
|
||||
href: string;
|
||||
}): JSX.Element {
|
||||
return (
|
||||
<a
|
||||
className={className}
|
||||
href={`${href}?utm_source=create-turbo&utm_medium=basic&utm_campaign=create-turbo"`}
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<h2>
|
||||
{title} <span>-></span>
|
||||
</h2>
|
||||
<p>{children}</p>
|
||||
</a>
|
||||
);
|
||||
}
|
@@ -1,9 +0,0 @@
|
||||
export function Code({
|
||||
children,
|
||||
className,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}): JSX.Element {
|
||||
return <code className={className}>{children}</code>;
|
||||
}
|
76
packages/ui/styles/globals.css
Normal file
@@ -0,0 +1,76 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 222.2 84% 4.9%;
|
||||
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 222.2 84% 4.9%;
|
||||
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 222.2 84% 4.9%;
|
||||
|
||||
--primary: 222.2 47.4% 11.2%;
|
||||
--primary-foreground: 210 40% 98%;
|
||||
|
||||
--secondary: 210 40% 96.1%;
|
||||
--secondary-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
--muted: 210 40% 96.1%;
|
||||
--muted-foreground: 215.4 16.3% 46.9%;
|
||||
|
||||
--accent: 210 40% 96.1%;
|
||||
--accent-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
|
||||
--border: 214.3 31.8% 91.4%;
|
||||
--input: 214.3 31.8% 91.4%;
|
||||
--ring: 222.2 84% 4.9%;
|
||||
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: 222.2 84% 4.9%;
|
||||
--foreground: 210 40% 98%;
|
||||
|
||||
--card: 222.2 84% 4.9%;
|
||||
--card-foreground: 210 40% 98%;
|
||||
|
||||
--popover: 222.2 84% 4.9%;
|
||||
--popover-foreground: 210 40% 98%;
|
||||
|
||||
--primary: 210 40% 98%;
|
||||
--primary-foreground: 222.2 47.4% 11.2%;
|
||||
|
||||
--secondary: 217.2 32.6% 17.5%;
|
||||
--secondary-foreground: 210 40% 98%;
|
||||
|
||||
--muted: 217.2 32.6% 17.5%;
|
||||
--muted-foreground: 215 20.2% 65.1%;
|
||||
|
||||
--accent: 217.2 32.6% 17.5%;
|
||||
--accent-foreground: 210 40% 98%;
|
||||
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
|
||||
--border: 217.2 32.6% 17.5%;
|
||||
--input: 217.2 32.6% 17.5%;
|
||||
--ring: 212.7 26.8% 83.9%;
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
7
packages/ui/tailwind.config.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { type Config } from "tailwindcss";
|
||||
import sharedConfig from "@unsend/tailwind-config/tailwind.config";
|
||||
|
||||
export default {
|
||||
...sharedConfig,
|
||||
content: ["./src/**/*.tsx"],
|
||||
} satisfies Config;
|
9
packages/ui/theme-provider.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import { ThemeProvider as NextThemesProvider } from "next-themes";
|
||||
import { type ThemeProviderProps } from "next-themes/dist/types";
|
||||
|
||||
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
|
||||
return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
|
||||
}
|
@@ -3,6 +3,6 @@
|
||||
"compilerOptions": {
|
||||
"outDir": "dist"
|
||||
},
|
||||
"include": ["src"],
|
||||
"include": ["src", "index.ts"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
|
@@ -1,30 +0,0 @@
|
||||
import type { PlopTypes } from "@turbo/gen";
|
||||
|
||||
// Learn more about Turborepo Generators at https://turbo.build/repo/docs/core-concepts/monorepos/code-generation
|
||||
|
||||
export default function generator(plop: PlopTypes.NodePlopAPI): void {
|
||||
// A simple generator to add a new React component to the internal UI library
|
||||
plop.setGenerator("react-component", {
|
||||
description: "Adds a new react component",
|
||||
prompts: [
|
||||
{
|
||||
type: "input",
|
||||
name: "name",
|
||||
message: "What is the name of the component?",
|
||||
},
|
||||
],
|
||||
actions: [
|
||||
{
|
||||
type: "add",
|
||||
path: "src/{{kebabCase name}}.tsx",
|
||||
templateFile: "templates/component.hbs",
|
||||
},
|
||||
{
|
||||
type: "append",
|
||||
path: "package.json",
|
||||
pattern: /"exports": {(?<insertion>)/g,
|
||||
template: '"./{{kebabCase name}}": "./src/{{kebabCase name}}.tsx",',
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
@@ -1,8 +0,0 @@
|
||||
export const {{ pascalCase name }} = ({ children }: { children: React.ReactNode }) => {
|
||||
return (
|
||||
<div>
|
||||
<h1>{{ pascalCase name }} Component</h1>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
5943
pnpm-lock.yaml
generated
20
turbo.json
@@ -3,10 +3,14 @@
|
||||
"globalDependencies": [
|
||||
"**/.env.*local"
|
||||
],
|
||||
"globalDotEnv": [
|
||||
".env"
|
||||
],
|
||||
"pipeline": {
|
||||
"build": {
|
||||
"dependsOn": [
|
||||
"^build"
|
||||
"^build",
|
||||
"db:generate"
|
||||
],
|
||||
"outputs": [
|
||||
".next/**",
|
||||
@@ -20,7 +24,19 @@
|
||||
},
|
||||
"dev": {
|
||||
"cache": false,
|
||||
"persistent": true
|
||||
"persistent": true,
|
||||
"dotEnv": [
|
||||
".env"
|
||||
],
|
||||
"dependsOn": [
|
||||
"db:generate"
|
||||
]
|
||||
},
|
||||
"db:generate": {
|
||||
"cache": false
|
||||
},
|
||||
"db:push": {
|
||||
"cache": false
|
||||
}
|
||||
}
|
||||
}
|