parent
27a82e8c0f
commit
523d91f920
@ -0,0 +1,13 @@
|
|||||||
|
.DS_Store
|
||||||
|
node_modules
|
||||||
|
.turbo
|
||||||
|
*.log
|
||||||
|
.next
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
.env
|
||||||
|
.cache
|
||||||
|
server/dist
|
||||||
|
public/dist
|
||||||
|
.turbo
|
@ -0,0 +1,27 @@
|
|||||||
|
node_modules
|
||||||
|
.next
|
||||||
|
build
|
||||||
|
dist
|
||||||
|
*.tsbuildinfo
|
||||||
|
*.gitignore
|
||||||
|
*.svg
|
||||||
|
*.lock
|
||||||
|
*.npmignore
|
||||||
|
*.sql
|
||||||
|
*.png
|
||||||
|
*.jpg
|
||||||
|
*.jpeg
|
||||||
|
*.gif
|
||||||
|
*.ico
|
||||||
|
*.sh
|
||||||
|
Dockerfile
|
||||||
|
Dockerfile.*
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
LICENSE
|
||||||
|
*.log
|
||||||
|
.DS_Store
|
||||||
|
.dockerignore
|
||||||
|
*.patch
|
||||||
|
*.toml
|
||||||
|
*.prisma
|
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"files.trimFinalNewlines": true,
|
||||||
|
"files.trimTrailingWhitespace": true,
|
||||||
|
"cSpell.autoFormatConfigFile": true,
|
||||||
|
"editor.formatOnPaste": true,
|
||||||
|
"editor.formatOnSave": true,
|
||||||
|
"editor.formatOnSaveMode": "file",
|
||||||
|
"javascript.format.enable": true,
|
||||||
|
"json.format.enable": true,
|
||||||
|
"eslint.format.enable": false,
|
||||||
|
"css.format.enable": true,
|
||||||
|
"css.format.newlineBetweenRules": true,
|
||||||
|
"css.format.newlineBetweenSelectors": true,
|
||||||
|
"css.format.preserveNewLines": true,
|
||||||
|
"typescript.format.enable": true,
|
||||||
|
"editor.codeActionsOnSave": {
|
||||||
|
"source.fixAll": true
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
# Prisma
|
||||||
|
DATABASE_URL=postgresql://postgres:[PASSWORD]@localhost:5432/postgres
|
||||||
|
|
||||||
|
# Next Auth
|
||||||
|
NEXTAUTH_SECRET=any_string_you_wish
|
||||||
|
NEXTAUTH_URL=http://localhost:3000
|
||||||
|
|
||||||
|
# Next Auth GitHub Provider
|
||||||
|
GITHUB_CLIENT_ID=a5164b1943b5413ff2f5
|
||||||
|
GITHUB_CLIENT_SECRET=
|
@ -0,0 +1,8 @@
|
|||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
extends: ['tih', 'next/core-web-vitals'],
|
||||||
|
parserOptions: {
|
||||||
|
tsconfigRootDir: __dirname,
|
||||||
|
project: ['./tsconfig.json'],
|
||||||
|
},
|
||||||
|
};
|
@ -0,0 +1,40 @@
|
|||||||
|
# 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/
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
*.pem
|
||||||
|
|
||||||
|
# debug
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
|
||||||
|
# local env files
|
||||||
|
.env
|
||||||
|
.env*.local
|
||||||
|
|
||||||
|
# vercel
|
||||||
|
.vercel
|
||||||
|
|
||||||
|
# typescript
|
||||||
|
*.tsbuildinfo
|
@ -0,0 +1,50 @@
|
|||||||
|
# Create T3 App
|
||||||
|
|
||||||
|
This is an app bootstrapped according to the [init.tips](https://init.tips) stack, also known as the T3-Stack.
|
||||||
|
|
||||||
|
## Why are there `.js` files in here?
|
||||||
|
|
||||||
|
As per [T3-Axiom #3](https://github.com/t3-oss/create-t3-app/tree/next#3-typesafety-isnt-optional), we take typesafety as a first class citizen. Unfortunately, not all frameworks and plugins support TypeScript which means some of the configuration files have to be `.js` files.
|
||||||
|
|
||||||
|
We try to emphasize that these files are javascript for a reason, by explicitly declaring its type (`cjs` or `mjs`) depending on what's supported by the library it is used by. Also, all the `js` files in this project are still typechecked using a `@ts-check` comment at the top.
|
||||||
|
|
||||||
|
## What's next? How do I make an app with this?
|
||||||
|
|
||||||
|
We try to keep this project as simple as possible, so you can start with the most basic configuration and then move on to more advanced configuration.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
- [Next-Auth.js](https://next-auth.js.org)
|
||||||
|
- [Prisma](https://prisma.io)
|
||||||
|
- [TailwindCSS](https://tailwindcss.com)
|
||||||
|
- [tRPC](https://trpc.io) (using @next version? [see v10 docs here](https://trpc.io/docs/v10/))
|
||||||
|
|
||||||
|
Also checkout these awesome tutorials on `create-t3-app`.
|
||||||
|
|
||||||
|
- [Build a Blog With the T3 Stack - tRPC, TypeScript, Next.js, Prisma & Zod](https://www.youtube.com/watch?v=syEWlxVFUrY)
|
||||||
|
- [Build a Live Chat Application with the T3 Stack - TypeScript, Tailwind, tRPC](https://www.youtube.com/watch?v=dXRRY37MPuk)
|
||||||
|
- [Build a full stack app with create-t3-app](https://www.nexxel.dev/blog/ct3a-guestbook)
|
||||||
|
- [A first look at create-t3-app](https://dev.to/ajcwebdev/a-first-look-at-create-t3-app-1i8f)
|
||||||
|
|
||||||
|
## How do I deploy this?
|
||||||
|
|
||||||
|
### Vercel
|
||||||
|
|
||||||
|
We recommend deploying to [Vercel](https://vercel.com/?utm_source=t3-oss&utm_campaign=oss). It makes it super easy to deploy NextJs apps.
|
||||||
|
|
||||||
|
- Push your code to a GitHub repository.
|
||||||
|
- Go to [Vercel](https://vercel.com/?utm_source=t3-oss&utm_campaign=oss) and sign up with GitHub.
|
||||||
|
- Create a Project and import the repository you pushed your code to.
|
||||||
|
- Add your environment variables.
|
||||||
|
- Click **Deploy**
|
||||||
|
- Now whenever you push a change to your repository, Vercel will automatically redeploy your website!
|
||||||
|
|
||||||
|
### Docker
|
||||||
|
|
||||||
|
You can also dockerize this stack and deploy a container. See the [Docker deployment page](https://create-t3-app-nu.vercel.app/en/deployment/docker) for details.
|
||||||
|
|
||||||
|
## Useful resources
|
||||||
|
|
||||||
|
Here are some resources that we commonly refer to:
|
||||||
|
|
||||||
|
- [Protecting routes with Next-Auth.js](https://next-auth.js.org/configuration/nextjs#unstable_getserversession)
|
@ -0,0 +1,5 @@
|
|||||||
|
/// <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.
|
@ -0,0 +1,18 @@
|
|||||||
|
import { env } from './src/env/server.mjs';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Don't be scared of the generics here.
|
||||||
|
* All they do is to give us autocompletion when using this.
|
||||||
|
*
|
||||||
|
* @template {import('next').NextConfig} T
|
||||||
|
* @param {T} config - A generic parameter that flows through to the return type
|
||||||
|
* @constraint {{import('next').NextConfig}}
|
||||||
|
*/
|
||||||
|
function defineNextConfig(config) {
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineNextConfig({
|
||||||
|
reactStrictMode: true,
|
||||||
|
swcMinify: true,
|
||||||
|
});
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,44 @@
|
|||||||
|
{
|
||||||
|
"name": "@tih/portal",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "next dev",
|
||||||
|
"build": "next build",
|
||||||
|
"start": "next start",
|
||||||
|
"lint": "next lint",
|
||||||
|
"tsc": "tsc",
|
||||||
|
"postinstall": "prisma generate",
|
||||||
|
"prisma": "prisma"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@next-auth/prisma-adapter": "^1.0.4",
|
||||||
|
"@prisma/client": "^4.4.0",
|
||||||
|
"@tih/ui": "*",
|
||||||
|
"@trpc/client": "^9.27.2",
|
||||||
|
"@trpc/next": "^9.27.2",
|
||||||
|
"@trpc/react": "^9.27.2",
|
||||||
|
"@trpc/server": "^9.27.2",
|
||||||
|
"next": "12.3.1",
|
||||||
|
"next-auth": "~4.10.3",
|
||||||
|
"react": "18.2.0",
|
||||||
|
"react-dom": "18.2.0",
|
||||||
|
"react-query": "^3.39.2",
|
||||||
|
"superjson": "^1.10.0",
|
||||||
|
"zod": "^3.18.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@tih/tsconfig": "*",
|
||||||
|
"@types/node": "18.0.0",
|
||||||
|
"@types/react": "18.0.21",
|
||||||
|
"@types/react-dom": "18.0.6",
|
||||||
|
"autoprefixer": "^10.4.12",
|
||||||
|
"postcss": "^8.4.16",
|
||||||
|
"prisma": "^4.4.0",
|
||||||
|
"tailwindcss": "^3.1.8",
|
||||||
|
"typescript": "4.8.3"
|
||||||
|
},
|
||||||
|
"ct3aMetadata": {
|
||||||
|
"initVersion": "5.13.1"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
module.exports = {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
};
|
@ -0,0 +1,73 @@
|
|||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Example" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "Example_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Account" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"userId" TEXT NOT NULL,
|
||||||
|
"type" TEXT NOT NULL,
|
||||||
|
"provider" TEXT NOT NULL,
|
||||||
|
"providerAccountId" TEXT NOT NULL,
|
||||||
|
"refresh_token" TEXT,
|
||||||
|
"access_token" TEXT,
|
||||||
|
"expires_at" INTEGER,
|
||||||
|
"token_type" TEXT,
|
||||||
|
"scope" TEXT,
|
||||||
|
"id_token" TEXT,
|
||||||
|
"session_state" TEXT,
|
||||||
|
|
||||||
|
CONSTRAINT "Account_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Session" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"sessionToken" TEXT NOT NULL,
|
||||||
|
"userId" TEXT NOT NULL,
|
||||||
|
"expires" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "Session_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "User" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"name" TEXT,
|
||||||
|
"email" TEXT,
|
||||||
|
"emailVerified" TIMESTAMP(3),
|
||||||
|
"image" TEXT,
|
||||||
|
|
||||||
|
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "VerificationToken" (
|
||||||
|
"identifier" TEXT NOT NULL,
|
||||||
|
"token" TEXT NOT NULL,
|
||||||
|
"expires" TIMESTAMP(3) NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "Account_provider_providerAccountId_key" ON "Account"("provider", "providerAccountId");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "Session_sessionToken_key" ON "Session"("sessionToken");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "VerificationToken_token_key" ON "VerificationToken"("token");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "VerificationToken_identifier_token_key" ON "VerificationToken"("identifier", "token");
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Account" ADD CONSTRAINT "Account_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Session" ADD CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
@ -0,0 +1,3 @@
|
|||||||
|
# Please do not edit this file manually
|
||||||
|
# It should be added in your version-control system (i.e. Git)
|
||||||
|
provider = "postgresql"
|
@ -0,0 +1,64 @@
|
|||||||
|
// 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 postgresql, 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 Example {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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[]
|
||||||
|
}
|
||||||
|
|
||||||
|
model VerificationToken {
|
||||||
|
identifier String
|
||||||
|
token String @unique
|
||||||
|
expires DateTime
|
||||||
|
|
||||||
|
@@unique([identifier, token])
|
||||||
|
}
|
After Width: | Height: | Size: 9.4 KiB |
@ -0,0 +1,38 @@
|
|||||||
|
// @ts-check
|
||||||
|
import { clientEnv, clientSchema } from './schema.mjs';
|
||||||
|
|
||||||
|
const _clientEnv = clientSchema.safeParse(clientEnv);
|
||||||
|
|
||||||
|
export const formatErrors = (
|
||||||
|
/** @type {import('zod').ZodFormattedError<Map<string,string>,string>} */
|
||||||
|
errors,
|
||||||
|
) =>
|
||||||
|
Object.entries(errors)
|
||||||
|
.map(([name, value]) => {
|
||||||
|
if (value && '_errors' in value)
|
||||||
|
return `${name}: ${value._errors.join(', ')}\n`;
|
||||||
|
})
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
|
if (_clientEnv.success === false) {
|
||||||
|
console.error(
|
||||||
|
'❌ Invalid environment variables:\n',
|
||||||
|
...formatErrors(_clientEnv.error.format()),
|
||||||
|
);
|
||||||
|
throw new Error('Invalid environment variables');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate that client-side environment variables are exposed to the client.
|
||||||
|
*/
|
||||||
|
for (let key of Object.keys(_clientEnv.data)) {
|
||||||
|
if (!key.startsWith('NEXT_PUBLIC_')) {
|
||||||
|
console.warn(
|
||||||
|
`❌ Invalid public environment variable name: ${key}. It must begin with 'NEXT_PUBLIC_'`,
|
||||||
|
);
|
||||||
|
|
||||||
|
throw new Error('Invalid public environment variable name');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const env = _clientEnv.data;
|
@ -0,0 +1,34 @@
|
|||||||
|
// @ts-check
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify your server-side environment variables schema here.
|
||||||
|
* This way you can ensure the app isn't built with invalid env vars.
|
||||||
|
*/
|
||||||
|
export const serverSchema = z.object({
|
||||||
|
DATABASE_URL: z.string().url(),
|
||||||
|
NODE_ENV: z.enum(['development', 'test', 'production']),
|
||||||
|
NEXTAUTH_SECRET: z.string(),
|
||||||
|
NEXTAUTH_URL: z.string().url(),
|
||||||
|
GITHUB_CLIENT_ID: z.string(),
|
||||||
|
GITHUB_CLIENT_SECRET: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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_`.
|
||||||
|
*/
|
||||||
|
export const clientSchema = z.object({
|
||||||
|
// NEXT_PUBLIC_BAR: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* You can't destruct `process.env` as a regular object, so you have to do
|
||||||
|
* it manually here. This is because Next.js evaluates this at build time,
|
||||||
|
* and only used environment variables are included in the build.
|
||||||
|
* @type {{ [k in keyof z.infer<typeof clientSchema>]: z.infer<typeof clientSchema>[k] | undefined }}
|
||||||
|
*/
|
||||||
|
export const clientEnv = {
|
||||||
|
// NEXT_PUBLIC_BAR: process.env.NEXT_PUBLIC_BAR,
|
||||||
|
};
|
@ -0,0 +1,30 @@
|
|||||||
|
// @ts-check
|
||||||
|
/**
|
||||||
|
* This file is included in `/next.config.mjs` which ensures the app isn't built with invalid env vars.
|
||||||
|
* It has to be a `.mjs`-file to be imported there.
|
||||||
|
*/
|
||||||
|
import { serverSchema } from './schema.mjs';
|
||||||
|
import { env as clientEnv, formatErrors } from './client.mjs';
|
||||||
|
|
||||||
|
const _serverEnv = serverSchema.safeParse(process.env);
|
||||||
|
|
||||||
|
if (_serverEnv.success === false) {
|
||||||
|
console.error(
|
||||||
|
'❌ Invalid environment variables:\n',
|
||||||
|
...formatErrors(_serverEnv.error.format()),
|
||||||
|
);
|
||||||
|
throw new Error('Invalid environment variables');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate that server-side environment variables are not exposed to the client.
|
||||||
|
*/
|
||||||
|
for (let key of Object.keys(_serverEnv.data)) {
|
||||||
|
if (key.startsWith('NEXT_PUBLIC_')) {
|
||||||
|
console.warn('❌ You are exposing a server-side env-variable:', key);
|
||||||
|
|
||||||
|
throw new Error('You are exposing a server-side env-variable');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const env = { ..._serverEnv.data, ...clientEnv };
|
@ -0,0 +1,76 @@
|
|||||||
|
import type { AppType } from 'next/app';
|
||||||
|
import type { Session } from 'next-auth';
|
||||||
|
import { SessionProvider } from 'next-auth/react';
|
||||||
|
import superjson from 'superjson';
|
||||||
|
import { httpBatchLink } from '@trpc/client/links/httpBatchLink';
|
||||||
|
import { loggerLink } from '@trpc/client/links/loggerLink';
|
||||||
|
import { withTRPC } from '@trpc/next';
|
||||||
|
|
||||||
|
import type { AppRouter } from '~/server/router';
|
||||||
|
|
||||||
|
import '~/styles/globals.css';
|
||||||
|
|
||||||
|
const MyApp: AppType<{ session: Session | null }> = ({
|
||||||
|
Component,
|
||||||
|
pageProps: { session, ...pageProps },
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<SessionProvider session={session}>
|
||||||
|
<Component {...pageProps} />
|
||||||
|
</SessionProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getBaseUrl = () => {
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
return '';
|
||||||
|
} // Browser should use relative url
|
||||||
|
if (process.env.VERCEL_URL) {
|
||||||
|
return `https://${process.env.VERCEL_URL}`;
|
||||||
|
} // SSR should use vercel url
|
||||||
|
return `http://localhost:${process.env.PORT ?? 3000}`; // Dev SSR should use localhost
|
||||||
|
};
|
||||||
|
|
||||||
|
export default withTRPC<AppRouter>({
|
||||||
|
config({ ctx: _ctx }) {
|
||||||
|
/**
|
||||||
|
* If you want to use SSR, you need to use the server's full URL
|
||||||
|
* @link https://trpc.io/docs/ssr
|
||||||
|
*/
|
||||||
|
const url = `${getBaseUrl()}/api/trpc`;
|
||||||
|
|
||||||
|
return {
|
||||||
|
links: [
|
||||||
|
loggerLink({
|
||||||
|
enabled: (opts) =>
|
||||||
|
process.env.NODE_ENV === 'development' ||
|
||||||
|
(opts.direction === 'down' && opts.result instanceof Error),
|
||||||
|
}),
|
||||||
|
httpBatchLink({ url }),
|
||||||
|
],
|
||||||
|
transformer: superjson,
|
||||||
|
url,
|
||||||
|
/**
|
||||||
|
* @link https://react-query.tanstack.com/reference/QueryClient
|
||||||
|
*/
|
||||||
|
// queryClientConfig: { defaultOptions: { queries: { staleTime: 60 } } },
|
||||||
|
|
||||||
|
// To use SSR properly you need to forward the client's headers to the server
|
||||||
|
// headers: () => {
|
||||||
|
// if (ctx?.req) {
|
||||||
|
// const headers = ctx?.req?.headers;
|
||||||
|
// delete headers?.connection;
|
||||||
|
// return {
|
||||||
|
// ...headers,
|
||||||
|
// "x-ssr": "1",
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
// return {};
|
||||||
|
// }
|
||||||
|
};
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* @link https://trpc.io/docs/ssr
|
||||||
|
*/
|
||||||
|
ssr: false,
|
||||||
|
})(MyApp);
|
@ -0,0 +1,31 @@
|
|||||||
|
import NextAuth, { type NextAuthOptions } from 'next-auth';
|
||||||
|
import GitHubProvider from 'next-auth/providers/github';
|
||||||
|
// Prisma adapter for NextAuth, optional and can be removed
|
||||||
|
import { PrismaAdapter } from '@next-auth/prisma-adapter';
|
||||||
|
|
||||||
|
import { env } from '../../../env/server.mjs';
|
||||||
|
import { prisma } from '../../../server/db/client';
|
||||||
|
|
||||||
|
export const authOptions: NextAuthOptions = {
|
||||||
|
// Configure one or more authentication providers
|
||||||
|
adapter: PrismaAdapter(prisma),
|
||||||
|
|
||||||
|
// Include user.id on session
|
||||||
|
callbacks: {
|
||||||
|
session({ session, user }) {
|
||||||
|
if (session.user) {
|
||||||
|
session.user.id = user.id;
|
||||||
|
}
|
||||||
|
return session;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
providers: [
|
||||||
|
GitHubProvider({
|
||||||
|
clientId: env.GITHUB_CLIENT_ID,
|
||||||
|
clientSecret: env.GITHUB_CLIENT_SECRET,
|
||||||
|
}),
|
||||||
|
// ...add more providers here
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NextAuth(authOptions);
|
@ -0,0 +1,11 @@
|
|||||||
|
// Src/pages/api/examples.ts
|
||||||
|
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||||
|
|
||||||
|
import { prisma } from '../../server/db/client';
|
||||||
|
|
||||||
|
const examples = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
|
const examplesFromDb = await prisma.example.findMany();
|
||||||
|
res.status(200).json(examplesFromDb);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default examples;
|
@ -0,0 +1,23 @@
|
|||||||
|
// Example of a restricted endpoint that only authenticated users can access from https://next-auth.js.org/getting-started/example
|
||||||
|
|
||||||
|
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||||
|
|
||||||
|
import { getServerAuthSession } from '../../server/common/get-server-auth-session';
|
||||||
|
|
||||||
|
const restricted = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
|
const session = await getServerAuthSession({ req, res });
|
||||||
|
|
||||||
|
if (session) {
|
||||||
|
res.send({
|
||||||
|
content:
|
||||||
|
'This is protected content. You can access this content because you are signed in.',
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
res.send({
|
||||||
|
error:
|
||||||
|
'You must be signed in to view the protected content on this page.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default restricted;
|
@ -0,0 +1,18 @@
|
|||||||
|
// Src/pages/api/trpc/[trpc].ts
|
||||||
|
import { createNextApiHandler } from '@trpc/server/adapters/next';
|
||||||
|
|
||||||
|
import { env } from '../../../env/server.mjs';
|
||||||
|
import { appRouter } from '../../../server/router';
|
||||||
|
import { createContext } from '../../../server/router/context';
|
||||||
|
|
||||||
|
// Export API handler
|
||||||
|
export default createNextApiHandler({
|
||||||
|
createContext,
|
||||||
|
onError:
|
||||||
|
env.NODE_ENV === 'development'
|
||||||
|
? ({ path, error }) => {
|
||||||
|
console.error(`❌ tRPC failed on ${path}: ${error}`);
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
router: appRouter,
|
||||||
|
});
|
@ -0,0 +1,91 @@
|
|||||||
|
import type { NextPage } from 'next';
|
||||||
|
import Head from 'next/head';
|
||||||
|
import { CounterButton } from '@tih/ui';
|
||||||
|
|
||||||
|
import { trpc } from '~/utils/trpc';
|
||||||
|
|
||||||
|
const Home: NextPage = () => {
|
||||||
|
const hello = trpc.useQuery(['example.hello', { text: 'from tRPC!' }]);
|
||||||
|
const getAll = trpc.useQuery(['example.getAll']);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Head>
|
||||||
|
<title>Create T3 App</title>
|
||||||
|
<meta content="Generated by create-t3-app" name="description" />
|
||||||
|
<link href="/favicon.ico" rel="icon" />
|
||||||
|
</Head>
|
||||||
|
<main className="container mx-auto flex flex-col items-center justify-center min-h-screen p-4">
|
||||||
|
<h1 className="text-5xl md:text-[5rem] leading-normal font-extrabold text-gray-700">
|
||||||
|
Create <span className="text-purple-300">T3</span> App
|
||||||
|
</h1>
|
||||||
|
<CounterButton />
|
||||||
|
<p className="text-2xl text-gray-700">This stack uses:</p>
|
||||||
|
<div className="grid gap-3 pt-3 mt-3 text-center md:grid-cols-2 lg:w-2/3">
|
||||||
|
<TechnologyCard
|
||||||
|
description="The React framework for production"
|
||||||
|
documentation="https://nextjs.org/"
|
||||||
|
name="NextJS"
|
||||||
|
/>
|
||||||
|
<TechnologyCard
|
||||||
|
description="Strongly typed programming language that builds on JavaScript, giving you better tooling at any scale"
|
||||||
|
documentation="https://www.typescriptlang.org/"
|
||||||
|
name="TypeScript"
|
||||||
|
/>
|
||||||
|
<TechnologyCard
|
||||||
|
description="Rapidly build modern websites without ever leaving your HTML"
|
||||||
|
documentation="https://tailwindcss.com/"
|
||||||
|
name="TailwindCSS"
|
||||||
|
/>
|
||||||
|
<TechnologyCard
|
||||||
|
description="End-to-end typesafe APIs made easy"
|
||||||
|
documentation="https://trpc.io/"
|
||||||
|
name="tRPC"
|
||||||
|
/>
|
||||||
|
<TechnologyCard
|
||||||
|
description="Authentication for Next.js"
|
||||||
|
documentation="https://next-auth.js.org/"
|
||||||
|
name="Next-Auth"
|
||||||
|
/>
|
||||||
|
<TechnologyCard
|
||||||
|
description="Build data-driven JavaScript & TypeScript apps in less time"
|
||||||
|
documentation="https://www.prisma.io/docs/"
|
||||||
|
name="Prisma"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="pt-6 text-2xl text-blue-500 flex justify-center items-center w-full">
|
||||||
|
{hello.data ? <p>{hello.data.greeting}</p> : <p>Loading..</p>}
|
||||||
|
</div>
|
||||||
|
<pre className="w-1/2">{JSON.stringify(getAll.data, null, 2)}</pre>
|
||||||
|
</main>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Home;
|
||||||
|
|
||||||
|
type TechnologyCardProps = {
|
||||||
|
description: string;
|
||||||
|
documentation: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
function TechnologyCard({
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
documentation,
|
||||||
|
}: TechnologyCardProps) {
|
||||||
|
return (
|
||||||
|
<section className="flex flex-col justify-center p-6 duration-500 border-2 border-gray-500 rounded shadow-xl motion-safe:hover:scale-105">
|
||||||
|
<h2 className="text-lg text-gray-700">{name}</h2>
|
||||||
|
<p className="text-sm text-gray-600">{description}</p>
|
||||||
|
<a
|
||||||
|
className="mt-3 text-sm underline text-violet-500 decoration-dotted underline-offset-2"
|
||||||
|
href={documentation}
|
||||||
|
rel="noreferrer"
|
||||||
|
target="_blank">
|
||||||
|
Documentation
|
||||||
|
</a>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
// Wrapper for unstable_getServerSession https://next-auth.js.org/configuration/nextjs
|
||||||
|
|
||||||
|
import type { GetServerSidePropsContext } from 'next';
|
||||||
|
// eslint-disable-next-line camelcase
|
||||||
|
import { unstable_getServerSession } from 'next-auth';
|
||||||
|
|
||||||
|
import { authOptions as nextAuthOptions } from '~/pages/api/auth/[...nextauth]';
|
||||||
|
|
||||||
|
// Next API route example - /pages/api/restricted.ts
|
||||||
|
export const getServerAuthSession = async (ctx: {
|
||||||
|
req: GetServerSidePropsContext['req'];
|
||||||
|
res: GetServerSidePropsContext['res'];
|
||||||
|
}) => {
|
||||||
|
return await unstable_getServerSession(ctx.req, ctx.res, nextAuthOptions);
|
||||||
|
};
|
@ -0,0 +1,20 @@
|
|||||||
|
// Src/server/db/client.ts
|
||||||
|
import { PrismaClient } from '@prisma/client';
|
||||||
|
|
||||||
|
import { env } from '~/env/server.mjs';
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
// eslint-disable-next-line no-var, init-declarations
|
||||||
|
var prisma: PrismaClient | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const prisma =
|
||||||
|
global.prisma ||
|
||||||
|
new PrismaClient({
|
||||||
|
log:
|
||||||
|
env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (env.NODE_ENV !== 'production') {
|
||||||
|
global.prisma = prisma;
|
||||||
|
}
|
@ -0,0 +1,61 @@
|
|||||||
|
// Src/server/router/context.ts
|
||||||
|
import type { Session } from 'next-auth';
|
||||||
|
import * as trpc from '@trpc/server';
|
||||||
|
import type * as trpcNext from '@trpc/server/adapters/next';
|
||||||
|
|
||||||
|
import { getServerAuthSession } from '~/server/common/get-server-auth-session';
|
||||||
|
import { prisma } from '~/server/db/client';
|
||||||
|
|
||||||
|
type CreateContextOptions = {
|
||||||
|
session: Session | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Use this helper for:
|
||||||
|
* - testing, where we dont have to Mock Next.js' req/res
|
||||||
|
* - trpc's `createSSGHelpers` where we don't have req/res
|
||||||
|
**/
|
||||||
|
export const createContextInner = async (opts: CreateContextOptions) => {
|
||||||
|
return {
|
||||||
|
prisma,
|
||||||
|
session: opts.session,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the actual context you'll use in your router
|
||||||
|
* @link https://trpc.io/docs/context
|
||||||
|
**/
|
||||||
|
export const createContext = async (
|
||||||
|
opts: trpcNext.CreateNextContextOptions,
|
||||||
|
) => {
|
||||||
|
const { req, res } = opts;
|
||||||
|
|
||||||
|
// Get the session from the server using the unstable_getServerSession wrapper function
|
||||||
|
const session = await getServerAuthSession({ req, res });
|
||||||
|
|
||||||
|
return await createContextInner({
|
||||||
|
session,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
type Context = trpc.inferAsyncReturnType<typeof createContext>;
|
||||||
|
|
||||||
|
export const createRouter = () => trpc.router<Context>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a tRPC router that asserts all queries and mutations are from an authorized user. Will throw an unauthorized error if a user is not signed in.
|
||||||
|
**/
|
||||||
|
export function createProtectedRouter() {
|
||||||
|
return createRouter().middleware(({ ctx, next }) => {
|
||||||
|
if (!ctx.session || !ctx.session.user) {
|
||||||
|
throw new trpc.TRPCError({ code: 'UNAUTHORIZED' });
|
||||||
|
}
|
||||||
|
return next({
|
||||||
|
ctx: {
|
||||||
|
...ctx,
|
||||||
|
// Infers that `session` is non-nullable to downstream resolvers
|
||||||
|
session: { ...ctx.session, user: ctx.session.user },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import { createRouter } from './context';
|
||||||
|
|
||||||
|
export const exampleRouter = createRouter()
|
||||||
|
.query('hello', {
|
||||||
|
input: z
|
||||||
|
.object({
|
||||||
|
text: z.string().nullish(),
|
||||||
|
})
|
||||||
|
.nullish(),
|
||||||
|
resolve({ input }) {
|
||||||
|
return {
|
||||||
|
greeting: `Hello ${input?.text ?? 'world'}`,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.query('getAll', {
|
||||||
|
async resolve({ ctx }) {
|
||||||
|
const items = await ctx.prisma.example.findMany();
|
||||||
|
return items;
|
||||||
|
},
|
||||||
|
});
|
@ -0,0 +1,14 @@
|
|||||||
|
// Src/server/router/index.ts
|
||||||
|
import superjson from 'superjson';
|
||||||
|
|
||||||
|
import { createRouter } from './context';
|
||||||
|
import { exampleRouter } from './example';
|
||||||
|
import { protectedExampleRouter } from './protected-example-router';
|
||||||
|
|
||||||
|
export const appRouter = createRouter()
|
||||||
|
.transformer(superjson)
|
||||||
|
.merge('example.', exampleRouter)
|
||||||
|
.merge('auth.', protectedExampleRouter);
|
||||||
|
|
||||||
|
// Export type definition of API
|
||||||
|
export type AppRouter = typeof appRouter;
|
@ -0,0 +1,14 @@
|
|||||||
|
import { createProtectedRouter } from './context';
|
||||||
|
|
||||||
|
// Example router with queries that can only be hit if the user requesting is signed in
|
||||||
|
export const protectedExampleRouter = createProtectedRouter()
|
||||||
|
.query('getSession', {
|
||||||
|
resolve({ ctx }) {
|
||||||
|
return ctx.session;
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.query('getSecretMessage', {
|
||||||
|
resolve({ ctx: _ctx }) {
|
||||||
|
return 'He who asks a question is a fool for five minutes; he who does not ask a question remains a fool forever.';
|
||||||
|
},
|
||||||
|
});
|
@ -0,0 +1,3 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
@ -0,0 +1,12 @@
|
|||||||
|
import type { DefaultSession } from 'next-auth';
|
||||||
|
|
||||||
|
declare module 'next-auth' {
|
||||||
|
/**
|
||||||
|
* Returned by `useSession`, `getSession` and received as a prop on the `SessionProvider` React Context
|
||||||
|
*/
|
||||||
|
type Session = {
|
||||||
|
user?: DefaultSession['user'] & {
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
// Src/utils/trpc.ts
|
||||||
|
import { createReactQueryHooks } from '@trpc/react';
|
||||||
|
import type { inferProcedureInput, inferProcedureOutput } from '@trpc/server';
|
||||||
|
|
||||||
|
import type { AppRouter } from '~/server/router';
|
||||||
|
|
||||||
|
export const trpc = createReactQueryHooks<AppRouter>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* These are helper types to infer the input and output of query resolvers
|
||||||
|
* @example type HelloOutput = inferQueryOutput<'hello'>
|
||||||
|
*/
|
||||||
|
export type inferQueryOutput<
|
||||||
|
TRouteKey extends keyof AppRouter['_def']['queries'],
|
||||||
|
> = inferProcedureOutput<AppRouter['_def']['queries'][TRouteKey]>;
|
||||||
|
|
||||||
|
export type inferQueryInput<
|
||||||
|
TRouteKey extends keyof AppRouter['_def']['queries'],
|
||||||
|
> = inferProcedureInput<AppRouter['_def']['queries'][TRouteKey]>;
|
||||||
|
|
||||||
|
export type inferMutationOutput<
|
||||||
|
TRouteKey extends keyof AppRouter['_def']['mutations'],
|
||||||
|
> = inferProcedureOutput<AppRouter['_def']['mutations'][TRouteKey]>;
|
||||||
|
|
||||||
|
export type inferMutationInput<
|
||||||
|
TRouteKey extends keyof AppRouter['_def']['mutations'],
|
||||||
|
> = inferProcedureInput<AppRouter['_def']['mutations'][TRouteKey]>;
|
@ -0,0 +1,8 @@
|
|||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
module.exports = {
|
||||||
|
content: ['./src/**/*.{js,ts,jsx,tsx}'],
|
||||||
|
theme: {
|
||||||
|
extend: {},
|
||||||
|
},
|
||||||
|
plugins: [],
|
||||||
|
};
|
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"exclude": ["node_modules"],
|
||||||
|
"extends": "@tih/tsconfig/nextjs.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"rootDir": "src",
|
||||||
|
"outDir": "dist",
|
||||||
|
"baseUrl": "./src",
|
||||||
|
"paths": {
|
||||||
|
"~/*": ["*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": ["src", "next-env.d.ts"]
|
||||||
|
}
|
@ -0,0 +1,4 @@
|
|||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
extends: ['tih'],
|
||||||
|
};
|
@ -0,0 +1,24 @@
|
|||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
stories: ['../stories/**/*.stories.mdx', '../stories/**/*.stories.tsx'],
|
||||||
|
addons: ['@storybook/addon-links', '@storybook/addon-essentials'],
|
||||||
|
framework: '@storybook/react',
|
||||||
|
core: {
|
||||||
|
builder: '@storybook/builder-vite',
|
||||||
|
},
|
||||||
|
async viteFinal(config, { configType }) {
|
||||||
|
// customize the Vite config here
|
||||||
|
return {
|
||||||
|
...config,
|
||||||
|
resolve: {
|
||||||
|
alias: [
|
||||||
|
{
|
||||||
|
find: '@tih/ui',
|
||||||
|
replacement: path.resolve(__dirname, '../../../packages/ui/'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"name": "@tih/storybook",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "start-storybook -p 6001",
|
||||||
|
"build": "build-storybook --docs",
|
||||||
|
"preview-storybook": "serve storybook-static",
|
||||||
|
"clean": "rm -rf .turbo && rm -rf node_modules"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@tih/ui": "*",
|
||||||
|
"react": "^18.2.0",
|
||||||
|
"react-dom": "^18.2.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@storybook/addon-actions": "^6.4.18",
|
||||||
|
"@storybook/addon-docs": "^6.4.22",
|
||||||
|
"@storybook/addon-essentials": "^6.4.18",
|
||||||
|
"@storybook/addon-links": "^6.4.18",
|
||||||
|
"@storybook/builder-vite": "^0.1.33",
|
||||||
|
"@storybook/react": "^6.4.18",
|
||||||
|
"@tih/tsconfig": "*",
|
||||||
|
"@vitejs/plugin-react": "^1.3.2",
|
||||||
|
"eslint-config-tih": "*",
|
||||||
|
"serve": "^13.0.2",
|
||||||
|
"typescript": "^4.8.3",
|
||||||
|
"vite": "^2.9.9"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
import { CounterButton } from '@tih/ui';
|
||||||
|
import { Meta, Story, Canvas, ArgsTable } from '@storybook/addon-docs';
|
||||||
|
|
||||||
|
<Meta title="Components/Button" component={CounterButton} />
|
||||||
|
|
||||||
|
# Button
|
||||||
|
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec euismod, nisl eget consectetur tempor, nisl nunc egestas nisi, euismod aliquam nisl nunc euismod.
|
||||||
|
|
||||||
|
## Props
|
||||||
|
|
||||||
|
<ArgsTable of={CounterButton} />
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
<Canvas>
|
||||||
|
<Story name="Default">
|
||||||
|
<CounterButton>Hello</CounterButton>
|
||||||
|
</Story>
|
||||||
|
</Canvas>
|
File diff suppressed because it is too large
Load Diff
@ -1,12 +1,33 @@
|
|||||||
function matrixTranspose(matrix) {
|
function matrixTranspose(matrix) {
|
||||||
return matrix[0].map((col, i) => matrix.map(row => row[i]));
|
return matrix[0].map((col, i) => matrix.map((row) => row[i]));
|
||||||
}
|
}
|
||||||
|
|
||||||
const deepEqual = require('./deepEqual');
|
const deepEqual = require('./deepEqual');
|
||||||
|
|
||||||
console.log(deepEqual(matrixTranspose([[1]]), [[1]]));
|
console.log(deepEqual(matrixTranspose([[1]]), [[1]]));
|
||||||
console.log(deepEqual(matrixTranspose([[1, 2]]), [[1], [2]]));
|
console.log(deepEqual(matrixTranspose([[1, 2]]), [[1], [2]]));
|
||||||
console.log(deepEqual(matrixTranspose([[1, 2], [1, 4]]), [[1, 1], [2, 4]]));
|
|
||||||
console.log(
|
console.log(
|
||||||
deepEqual(matrixTranspose([[1, 2, 3], [4, 5, 6]]), [[1, 4], [2, 5], [3, 6]]),
|
deepEqual(
|
||||||
|
matrixTranspose([
|
||||||
|
[1, 2],
|
||||||
|
[1, 4],
|
||||||
|
]),
|
||||||
|
[
|
||||||
|
[1, 1],
|
||||||
|
[2, 4],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
deepEqual(
|
||||||
|
matrixTranspose([
|
||||||
|
[1, 2, 3],
|
||||||
|
[4, 5, 6],
|
||||||
|
]),
|
||||||
|
[
|
||||||
|
[1, 4],
|
||||||
|
[2, 5],
|
||||||
|
[3, 6],
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"private": true,
|
||||||
|
"workspaces": [
|
||||||
|
"packages/*",
|
||||||
|
"apps/*"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"build": "turbo run build",
|
||||||
|
"clean": "turbo run clean",
|
||||||
|
"dev": "turbo run dev --no-cache --parallel --continue",
|
||||||
|
"format": "prettier --write \"**/*.{ts,tsx,md}\"",
|
||||||
|
"lint": "turbo run lint",
|
||||||
|
"test": "turbo run test",
|
||||||
|
"tsc": "turbo run tsc"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"prettier": "^2.7.1",
|
||||||
|
"turbo": "latest"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,147 @@
|
|||||||
|
/* eslint-disable sort-keys-fix/sort-keys-fix */
|
||||||
|
|
||||||
|
const OFF = 0;
|
||||||
|
const WARN = 1;
|
||||||
|
const ERROR = 2;
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
plugins: [
|
||||||
|
'@typescript-eslint',
|
||||||
|
'simple-import-sort',
|
||||||
|
'sort-keys-fix',
|
||||||
|
'typescript-sort-keys',
|
||||||
|
],
|
||||||
|
extends: [
|
||||||
|
'next',
|
||||||
|
'eslint:recommended',
|
||||||
|
'plugin:@typescript-eslint/recommended',
|
||||||
|
'prettier',
|
||||||
|
],
|
||||||
|
settings: {
|
||||||
|
react: {
|
||||||
|
version: 'detect',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
camelcase: [ERROR, { properties: 'never', ignoreDestructuring: true }],
|
||||||
|
'capitalized-comments': [
|
||||||
|
ERROR,
|
||||||
|
'always',
|
||||||
|
{ ignoreConsecutiveComments: true },
|
||||||
|
],
|
||||||
|
'consistent-this': ERROR,
|
||||||
|
curly: ERROR,
|
||||||
|
'dot-notation': ERROR,
|
||||||
|
eqeqeq: [ERROR, 'smart'],
|
||||||
|
'func-name-matching': ERROR,
|
||||||
|
'func-names': [ERROR, 'as-needed'],
|
||||||
|
'func-style': [ERROR, 'declaration', { allowArrowFunctions: true }],
|
||||||
|
'guard-for-in': ERROR,
|
||||||
|
'init-declarations': ERROR,
|
||||||
|
'no-console': [ERROR, { allow: ['warn', 'error', 'info'] }],
|
||||||
|
'no-else-return': [ERROR, { allowElseIf: false }],
|
||||||
|
'no-extra-boolean-cast': ERROR,
|
||||||
|
'no-lonely-if': ERROR,
|
||||||
|
'no-shadow': ERROR,
|
||||||
|
'no-unused-vars': OFF, // Use @typescript-eslint/no-unused-vars instead.
|
||||||
|
'object-shorthand': ERROR,
|
||||||
|
'one-var': [ERROR, 'never'],
|
||||||
|
'operator-assignment': ERROR,
|
||||||
|
'prefer-arrow-callback': ERROR,
|
||||||
|
'prefer-const': ERROR,
|
||||||
|
'prefer-destructuring': [
|
||||||
|
ERROR,
|
||||||
|
{
|
||||||
|
object: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
radix: ERROR,
|
||||||
|
'spaced-comment': ERROR,
|
||||||
|
|
||||||
|
'react/button-has-type': ERROR,
|
||||||
|
'react/display-name': OFF,
|
||||||
|
'react/destructuring-assignment': [ERROR, 'always'],
|
||||||
|
// 'react/hook-use-state': ERROR,
|
||||||
|
'react/no-array-index-key': ERROR,
|
||||||
|
'react/no-unescaped-entities': OFF,
|
||||||
|
'react/void-dom-elements-no-children': ERROR,
|
||||||
|
|
||||||
|
'react/jsx-boolean-value': [ERROR, 'always'],
|
||||||
|
'react/jsx-curly-brace-presence': [
|
||||||
|
ERROR,
|
||||||
|
{ props: 'never', children: 'never' },
|
||||||
|
],
|
||||||
|
'react/jsx-no-useless-fragment': ERROR,
|
||||||
|
'react/jsx-sort-props': [
|
||||||
|
ERROR,
|
||||||
|
{
|
||||||
|
callbacksLast: true,
|
||||||
|
shorthandFirst: true,
|
||||||
|
reservedFirst: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
'@next/next/no-img-element': OFF,
|
||||||
|
'@next/next/no-html-link-for-pages': OFF,
|
||||||
|
|
||||||
|
'@typescript-eslint/array-type': [
|
||||||
|
ERROR,
|
||||||
|
{ default: 'generic', readonly: 'generic' },
|
||||||
|
],
|
||||||
|
'@typescript-eslint/consistent-generic-constructors': [
|
||||||
|
ERROR,
|
||||||
|
'constructor',
|
||||||
|
],
|
||||||
|
'@typescript-eslint/consistent-indexed-object-style': [ERROR, 'record'],
|
||||||
|
'@typescript-eslint/consistent-type-definitions': [ERROR, 'type'],
|
||||||
|
'@typescript-eslint/consistent-type-imports': ERROR,
|
||||||
|
'@typescript-eslint/no-duplicate-enum-values': ERROR,
|
||||||
|
'@typescript-eslint/no-for-in-array': ERROR,
|
||||||
|
'@typescript-eslint/no-non-null-assertion': OFF,
|
||||||
|
'@typescript-eslint/no-unused-vars': [ERROR, { argsIgnorePattern: '^_' }],
|
||||||
|
'@typescript-eslint/prefer-optional-chain': ERROR,
|
||||||
|
'@typescript-eslint/require-array-sort-compare': ERROR,
|
||||||
|
'@typescript-eslint/restrict-plus-operands': ERROR,
|
||||||
|
'@typescript-eslint/sort-type-union-intersection-members': ERROR,
|
||||||
|
|
||||||
|
// Sorting
|
||||||
|
'typescript-sort-keys/interface': ERROR,
|
||||||
|
'typescript-sort-keys/string-enum': ERROR,
|
||||||
|
'sort-keys-fix/sort-keys-fix': ERROR,
|
||||||
|
'simple-import-sort/exports': WARN,
|
||||||
|
'simple-import-sort/imports': [
|
||||||
|
WARN,
|
||||||
|
{
|
||||||
|
groups: [
|
||||||
|
// Ext library & side effect imports.
|
||||||
|
['^~?\\w', '^\\u0000', '^@'],
|
||||||
|
// Lib and hooks.
|
||||||
|
['^~/lib', '^~/hooks'],
|
||||||
|
// Static data.
|
||||||
|
['^~/data'],
|
||||||
|
// Components.
|
||||||
|
['^~/components'],
|
||||||
|
// Other imports.
|
||||||
|
['^~/'],
|
||||||
|
// Relative paths up until 3 level.
|
||||||
|
[
|
||||||
|
'^\\./?$',
|
||||||
|
'^\\.(?!/?$)',
|
||||||
|
'^\\.\\./?$',
|
||||||
|
'^\\.\\.(?!/?$)',
|
||||||
|
'^\\.\\./\\.\\./?$',
|
||||||
|
'^\\.\\./\\.\\.(?!/?$)',
|
||||||
|
'^\\.\\./\\.\\./\\.\\./?$',
|
||||||
|
'^\\.\\./\\.\\./\\.\\.(?!/?$)',
|
||||||
|
],
|
||||||
|
['^~/types'],
|
||||||
|
// {s}css files
|
||||||
|
['^.+\\.s?css$'],
|
||||||
|
// Others that don't fit in.
|
||||||
|
['^'],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"name": "eslint-config-tih",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"main": "index.js",
|
||||||
|
"dependencies": {
|
||||||
|
"@typescript-eslint/eslint-plugin": "^5.33.0",
|
||||||
|
"@typescript-eslint/parser": "^5.33.0",
|
||||||
|
"eslint-config-next": "^12.3.1",
|
||||||
|
"eslint-config-prettier": "^8.5.0",
|
||||||
|
"eslint-plugin-react": "7.28.0",
|
||||||
|
"eslint-plugin-simple-import-sort": "^7.0.0",
|
||||||
|
"eslint-plugin-sort-keys-fix": "^1.1.2",
|
||||||
|
"eslint-plugin-typescript-sort-keys": "^2.1.0",
|
||||||
|
"eslint-config-turbo": "latest"
|
||||||
|
},
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/tsconfig",
|
||||||
|
"display": "Default",
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": false,
|
||||||
|
"declaration": true,
|
||||||
|
"declarationMap": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"inlineSources": false,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"noUnusedLocals": false,
|
||||||
|
"noUnusedParameters": false,
|
||||||
|
"preserveWatchOutput": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"strict": true
|
||||||
|
},
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/tsconfig",
|
||||||
|
"display": "Next.js",
|
||||||
|
"extends": "./base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"allowJs": true,
|
||||||
|
"declaration": false,
|
||||||
|
"declarationMap": false,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"incremental": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"lib": ["dom", "dom.iterable", "esnext"],
|
||||||
|
"module": "esnext",
|
||||||
|
"noEmit": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"target": "es5",
|
||||||
|
"strict": true
|
||||||
|
},
|
||||||
|
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "**/*.cjs", "**/*.mjs"]
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"name": "@tih/tsconfig",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/tsconfig",
|
||||||
|
"display": "React Library",
|
||||||
|
"extends": "./base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"lib": ["ES2015"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"target": "es6"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
extends: ['tih'],
|
||||||
|
parserOptions: {
|
||||||
|
tsconfigRootDir: __dirname,
|
||||||
|
project: ['./tsconfig.json'],
|
||||||
|
},
|
||||||
|
};
|
@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"name": "@tih/ui",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"sideEffects": false,
|
||||||
|
"main": "./dist/index.js",
|
||||||
|
"module": "./dist/index.mjs",
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"files": [
|
||||||
|
"dist/**"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsup src/index.tsx --format esm,cjs --dts --external react",
|
||||||
|
"clean": "rm -rf dist",
|
||||||
|
"tsc": "tsc",
|
||||||
|
"dev": "tsup src/index.tsx --format esm,cjs --watch --dts --external react",
|
||||||
|
"lint": "eslint src/**/*.ts* --fix"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@tih/tsconfig": "*",
|
||||||
|
"@types/react": "^18.0.21",
|
||||||
|
"@types/react-dom": "^18.0.6",
|
||||||
|
"eslint": "^8.24.0",
|
||||||
|
"eslint-config-tih": "*",
|
||||||
|
"react": "^18.2.0",
|
||||||
|
"tsup": "^6.2.3",
|
||||||
|
"typescript": "^4.8.3"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
export function CounterButton() {
|
||||||
|
const [count, setCount] = React.useState(0);
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
background: `rgba(0,0,0,0.05)`,
|
||||||
|
borderRadius: `8px`,
|
||||||
|
fontWeight: 500,
|
||||||
|
padding: '1.5rem',
|
||||||
|
}}>
|
||||||
|
<p style={{ margin: '0 0 1.5rem 0' }}>
|
||||||
|
This component is from{' '}
|
||||||
|
<code
|
||||||
|
style={{
|
||||||
|
background: `rgba(0,0,0,0.1)`,
|
||||||
|
borderRadius: '0.25rem',
|
||||||
|
padding: '0.2rem 0.3rem',
|
||||||
|
}}>
|
||||||
|
@tih/ui
|
||||||
|
</code>
|
||||||
|
</p>
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
style={{
|
||||||
|
background: 'black',
|
||||||
|
border: 'none',
|
||||||
|
borderRadius: '0.25rem',
|
||||||
|
color: 'white',
|
||||||
|
cursor: 'pointer',
|
||||||
|
display: 'inline-block',
|
||||||
|
padding: '0.5rem 1rem',
|
||||||
|
}}
|
||||||
|
type="button"
|
||||||
|
onClick={() => setCount((c) => c + 1)}>
|
||||||
|
Count: {count}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
export function NewTabLink({
|
||||||
|
children,
|
||||||
|
href,
|
||||||
|
...other
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
href: string;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<a href={href} rel="noreferrer" target="_blank" {...other}>
|
||||||
|
{children}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,2 @@
|
|||||||
|
export { CounterButton } from './CounterButton';
|
||||||
|
export { NewTabLink } from './NewTabLink';
|
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"lib": ["dom", "ES2015"]
|
||||||
|
},
|
||||||
|
"extends": "@tih/tsconfig/react-library.json",
|
||||||
|
"include": ["."],
|
||||||
|
"exclude": ["dist", "build", "node_modules"]
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://turborepo.org/schema.json",
|
||||||
|
"pipeline": {
|
||||||
|
"build": {
|
||||||
|
"outputs": [
|
||||||
|
"dist/**",
|
||||||
|
".next/**",
|
||||||
|
"build/**",
|
||||||
|
"api/**",
|
||||||
|
"public/build/**"
|
||||||
|
],
|
||||||
|
"dependsOn": ["^build"]
|
||||||
|
},
|
||||||
|
"test": {
|
||||||
|
"outputs": ["coverage/**"],
|
||||||
|
"dependsOn": []
|
||||||
|
},
|
||||||
|
"lint": {
|
||||||
|
"dependsOn": ["^build"],
|
||||||
|
"outputs": []
|
||||||
|
},
|
||||||
|
"tsc": {
|
||||||
|
"cache": true
|
||||||
|
},
|
||||||
|
"dev": {
|
||||||
|
"cache": false
|
||||||
|
},
|
||||||
|
"clean": {
|
||||||
|
"cache": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in new issue