Repository: Basedash/dockhunt Branch: main Commit: ec885c65225f Files: 54 Total size: 74.6 KB Directory structure: gitextract_llewg0mk/ ├── .eslintrc.json ├── .github/ │ ├── FUNDING.yml │ └── ISSUE_TEMPLATE/ │ ├── bug_report.md │ ├── feature_request.md │ └── incorrect-app-icon.md ├── .gitignore ├── .vscode/ │ └── settings.json ├── README.md ├── next.config.mjs ├── package.json ├── postcss.config.cjs ├── prettier.config.cjs ├── prisma/ │ ├── migrations/ │ │ ├── 20230126170225_init/ │ │ │ └── migration.sql │ │ ├── 20230126181934_user_email_fields/ │ │ │ └── migration.sql │ │ ├── 20230126215401_remove_user_stuff/ │ │ │ └── migration.sql │ │ ├── 20230126233521_update_user_id_to_username/ │ │ │ └── migration.sql │ │ ├── 20230127051217_add_user_description/ │ │ │ └── migration.sql │ │ ├── 20230129204228_add_index_on_dock_featured/ │ │ │ └── migration.sql │ │ ├── 20230130042222_add_user_url/ │ │ │ └── migration.sql │ │ └── migration_lock.toml │ └── schema.prisma ├── src/ │ ├── components/ │ │ ├── AddDockCard.tsx │ │ ├── BouncingLoader.tsx │ │ ├── Dock.tsx │ │ ├── DockCard.tsx │ │ └── MenuBar.tsx │ ├── env/ │ │ ├── client.mjs │ │ ├── schema.mjs │ │ └── server.mjs │ ├── pages/ │ │ ├── _app.tsx │ │ ├── add-dock.tsx │ │ ├── api/ │ │ │ ├── auth/ │ │ │ │ └── [...nextauth].ts │ │ │ ├── cli/ │ │ │ │ ├── check-apps.ts │ │ │ │ └── icon-upload.ts │ │ │ ├── og.tsx │ │ │ └── trpc/ │ │ │ └── [trpc].ts │ │ ├── apps/ │ │ │ └── [appName].tsx │ │ ├── apps.tsx │ │ ├── index.tsx │ │ ├── new-dock.tsx │ │ └── users/ │ │ └── [username].tsx │ ├── server/ │ │ ├── api/ │ │ │ ├── root.ts │ │ │ ├── routers/ │ │ │ │ ├── apps.ts │ │ │ │ ├── docks.ts │ │ │ │ └── users.ts │ │ │ └── trpc.ts │ │ ├── auth.ts │ │ └── db.ts │ ├── styles/ │ │ └── globals.css │ ├── types/ │ │ └── next-auth.d.ts │ └── utils/ │ ├── api.ts │ └── constants.ts ├── tailwind.config.cjs └── tsconfig.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .eslintrc.json ================================================ { "overrides": [ { "extends": [ "plugin:@typescript-eslint/recommended-requiring-type-checking" ], "files": ["*.ts", "*.tsx"], "parserOptions": { "project": "tsconfig.json" } } ], "parser": "@typescript-eslint/parser", "parserOptions": { "project": "./tsconfig.json" }, "plugins": ["@typescript-eslint"], "extends": ["next/core-web-vitals", "plugin:@typescript-eslint/recommended"], "rules": { "@typescript-eslint/consistent-type-imports": "warn" } } ================================================ FILE: .github/FUNDING.yml ================================================ # These are supported funding model platforms github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] patreon: # Replace with a single Patreon username open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry liberapay: # Replace with a single Liberapay username issuehunt: # Replace with a single IssueHunt username otechie: # Replace with a single Otechie username lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry custom: ['https://www.basedash.com'] ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: Create a report to help us improve title: '' labels: bug assignees: '' --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error **Expected behavior** A clear and concise description of what you expected to happen. **Screenshots** If applicable, add screenshots to help explain your problem. **Additional context** Add any other context about the problem here. ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: Feature request about: Suggest an idea for this project title: '' labels: enhancement assignees: '' --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. ================================================ FILE: .github/ISSUE_TEMPLATE/incorrect-app-icon.md ================================================ --- name: Incorrect app icon about: An app isn't using the correct icon on the website title: Incorrect app icon for labels: incorrect app icon assignees: '' --- App name: Link to app on Dockhunt: I've included the correct `.icns` file below, found in my Applications directory by right-clicking the app, selecting "Show Package Contents", and navigating to Contents > Resources. ================================================ FILE: .gitignore ================================================ # 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 .idea ================================================ FILE: .vscode/settings.json ================================================ { "editor.formatOnSave": true } ================================================ FILE: README.md ================================================ # Dockhunt [![Dockhunt - Discover the apps everyone is docking about](https://user-images.githubusercontent.com/15393239/215352336-3a2e63e2-b474-45a9-9721-160cecb83325.png)](https://www.dockhunt.com) [Website](https://www.dockhunt.com) ⋅ [Twitter](https://twitter.com/dockhuntapp) ⋅ [npm](https://www.npmjs.com/package/dockhunt) [CLI tool code](https://github.com/Basedash/dockhunt-cli) ================================================ FILE: next.config.mjs ================================================ // @ts-check /** * Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. * This is especially useful for Docker builds. */ !process.env.SKIP_ENV_VALIDATION && (await import("./src/env/server.mjs")); /** @type {import("next").NextConfig} */ const config = { reactStrictMode: true, /* If trying out the experimental appDir, comment the i18n config out * @see https://github.com/vercel/next.js/issues/41980 */ i18n: { locales: ["en"], defaultLocale: "en", }, images: { remotePatterns: [ // Twitter profile images { protocol: "https", hostname: "pbs.twimg.com", pathname: "/profile_images/**", }, // Twitter default profile image { protocol: "https", hostname: "abs.twimg.com", pathname: "/sticky/**", }, // App icons in DigitalOcean bucket { protocol: "https", hostname: "dockhunt-images.nyc3.cdn.digitaloceanspaces.com", pathname: "/*", }, ], }, eslint: { ignoreDuringBuilds: true, }, }; export default config; ================================================ FILE: package.json ================================================ { "name": "dockhunt", "version": "0.1.0", "private": true, "scripts": { "build": "next build", "dev": "next dev", "postinstall": "prisma generate", "generate": "prisma generate", "lint": "next lint", "start": "next start", "migrate": "prisma migrate dev" }, "dependencies": { "@aws-sdk/abort-controller": "^3.257.0", "@next-auth/prisma-adapter": "^1.0.5", "@prisma/client": "4.9.0", "@radix-ui/react-scroll-area": "^1.0.2", "@radix-ui/react-tooltip": "^1.0.3", "@tanstack/react-query": "^4.20.0", "@trpc/client": "^10.8.1", "@trpc/next": "^10.8.1", "@trpc/react-query": "^10.8.1", "@trpc/server": "^10.8.1", "@vercel/og": "^0.0.27", "aws-sdk": "^2.1303.0", "date-fns": "^2.29.3", "formidable": "^2.1.1", "framer-motion": "^8.5.3", "next": "13.1.2", "next-auth": "^4.23.1", "next-connect": "^0.13.0", "react": "18.2.0", "react-dom": "18.2.0", "superjson": "1.9.1", "uuid": "^9.0.0", "zod": "^3.20.2" }, "devDependencies": { "@types/formidable": "^2.0.5", "@types/multer": "^1.4.7", "@types/multer-s3": "^3.0.0", "@types/node": "^18.11.18", "@types/prettier": "^2.7.2", "@types/react": "^18.0.26", "@types/react-dom": "^18.0.10", "@types/uuid": "^9.0.0", "@typescript-eslint/eslint-plugin": "^5.47.1", "@typescript-eslint/parser": "^5.47.1", "autoprefixer": "^10.4.7", "eslint": "^8.30.0", "eslint-config-next": "13.1.2", "postcss": "^8.4.14", "prettier": "^2.8.1", "prettier-plugin-tailwindcss": "^0.2.1", "prisma": "4.9.0", "tailwindcss": "^3.2.0", "typescript": "^4.9.4" }, "ct3aMetadata": { "initVersion": "7.3.2" } } ================================================ FILE: postcss.config.cjs ================================================ module.exports = { plugins: { tailwindcss: {}, autoprefixer: {}, }, }; ================================================ FILE: prettier.config.cjs ================================================ /** @type {import("prettier").Config} */ module.exports = { plugins: [require.resolve("prettier-plugin-tailwindcss")], }; ================================================ FILE: prisma/migrations/20230126170225_init/migration.sql ================================================ -- CreateTable CREATE TABLE "App" ( "name" TEXT NOT NULL, "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, "updatedAt" TIMESTAMP(3) NOT NULL, "iconUrl" TEXT, "description" TEXT, "websiteUrl" TEXT, "twitterUrl" TEXT, CONSTRAINT "App_pkey" PRIMARY KEY ("name") ); -- CreateTable CREATE TABLE "Dock" ( "id" TEXT NOT NULL, "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, "updatedAt" TIMESTAMP(3) NOT NULL, "featured" BOOLEAN NOT NULL DEFAULT false, "userId" TEXT NOT NULL, CONSTRAINT "Dock_pkey" PRIMARY KEY ("id") ); -- CreateTable CREATE TABLE "DockItem" ( "id" TEXT NOT NULL, "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, "updatedAt" TIMESTAMP(3) NOT NULL, "appId" TEXT NOT NULL, "position" INTEGER NOT NULL, "dockId" TEXT NOT NULL, CONSTRAINT "DockItem_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, "image" TEXT, "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, "updatedAt" TIMESTAMP(3) NOT NULL, "avatarUrl" TEXT, "twitterHandle" TEXT, "twitterFollowerCount" INTEGER, 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 "Dock_userId_key" ON "Dock"("userId"); -- 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 "VerificationToken_token_key" ON "VerificationToken"("token"); -- CreateIndex CREATE UNIQUE INDEX "VerificationToken_identifier_token_key" ON "VerificationToken"("identifier", "token"); -- AddForeignKey ALTER TABLE "Dock" ADD CONSTRAINT "Dock_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; -- AddForeignKey ALTER TABLE "DockItem" ADD CONSTRAINT "DockItem_appId_fkey" FOREIGN KEY ("appId") REFERENCES "App"("name") ON DELETE CASCADE ON UPDATE CASCADE; -- AddForeignKey ALTER TABLE "DockItem" ADD CONSTRAINT "DockItem_dockId_fkey" FOREIGN KEY ("dockId") REFERENCES "Dock"("id") ON DELETE CASCADE ON UPDATE CASCADE; -- 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; ================================================ FILE: prisma/migrations/20230126181934_user_email_fields/migration.sql ================================================ /* Warnings: - A unique constraint covering the columns `[email]` on the table `User` will be added. If there are existing duplicate values, this will fail. */ -- AlterTable ALTER TABLE "User" ADD COLUMN "email" TEXT, ADD COLUMN "emailVerified" TIMESTAMP(3); -- CreateIndex CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); ================================================ FILE: prisma/migrations/20230126215401_remove_user_stuff/migration.sql ================================================ /* Warnings: - You are about to drop the column `image` on the `User` table. All the data in the column will be lost. */ -- AlterTable ALTER TABLE "User" DROP COLUMN "image"; ================================================ FILE: prisma/migrations/20230126233521_update_user_id_to_username/migration.sql ================================================ BEGIN; -- Default values for updatedAt ALTER TABLE "App" ALTER COLUMN "updatedAt" SET DEFAULT CURRENT_TIMESTAMP; ALTER TABLE "Dock" ALTER COLUMN "updatedAt" SET DEFAULT CURRENT_TIMESTAMP; ALTER TABLE "DockItem" ALTER COLUMN "updatedAt" SET DEFAULT CURRENT_TIMESTAMP; ALTER TABLE "User" ALTER COLUMN "updatedAt" SET DEFAULT CURRENT_TIMESTAMP; -- Update User columns ALTER TABLE "User" RENAME COLUMN "twitterHandle" TO "username"; ALTER TABLE "User" ALTER COLUMN "name" SET NOT NULL; COMMIT; ================================================ FILE: prisma/migrations/20230127051217_add_user_description/migration.sql ================================================ /* Warnings: - A unique constraint covering the columns `[username]` on the table `User` will be added. If there are existing duplicate values, this will fail. - Made the column `username` on table `User` required. This step will fail if there are existing NULL values in that column. */ -- AlterTable ALTER TABLE "User" ADD COLUMN "description" TEXT, ALTER COLUMN "username" SET NOT NULL; -- CreateIndex CREATE UNIQUE INDEX "User_username_key" ON "User"("username"); ================================================ FILE: prisma/migrations/20230129204228_add_index_on_dock_featured/migration.sql ================================================ -- CreateIndex CREATE INDEX "Dock_featured_idx" ON "Dock"("featured"); ================================================ FILE: prisma/migrations/20230130042222_add_user_url/migration.sql ================================================ -- AlterTable ALTER TABLE "User" ADD COLUMN "url" TEXT; ================================================ FILE: prisma/migrations/migration_lock.toml ================================================ # Please do not edit this file manually # It should be added in your version-control system (i.e. Git) provider = "postgresql" ================================================ FILE: prisma/schema.prisma ================================================ generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } model App { name String @id createdAt DateTime @default(now()) updatedAt DateTime @default(now()) @updatedAt iconUrl String? // png of the app icon description String? websiteUrl String? twitterUrl String? dockItems DockItem[] } model Dock { id String @id @default(cuid()) createdAt DateTime @default(now()) updatedAt DateTime @default(now()) @updatedAt featured Boolean @default(false) userId String @unique user User @relation(fields: [userId], references: [id], onDelete: Cascade) dockItems DockItem[] @@index([featured]) } model DockItem { id String @id @default(cuid()) createdAt DateTime @default(now()) updatedAt DateTime @default(now()) @updatedAt appId String app App @relation(fields: [appId], references: [name], onDelete: Cascade) position Int dockId String dock Dock @relation(fields: [dockId], references: [id], onDelete: Cascade) } // 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()) username String @unique // From Twitter name String // From Twitter description String? // From Twitter url String? // From Twitter accounts Account[] sessions Session[] email String? @unique emailVerified DateTime? createdAt DateTime @default(now()) updatedAt DateTime @default(now()) @updatedAt avatarUrl String? // From Twitter twitterFollowerCount Int? // From Twitter dock Dock? } model VerificationToken { identifier String token String @unique expires DateTime @@unique([identifier, token]) } ================================================ FILE: src/components/AddDockCard.tsx ================================================ import { useSession } from "next-auth/react"; import Link from "next/link"; import { api } from "utils/api"; import { desktopAppDownloadLink } from "utils/constants"; export function AddDockCard() { const { data: sessionData } = useSession(); const user = api.users.getOne.useQuery({ id: sessionData?.user?.id ?? "" }); if (user.data?.dock) { return null; } return (

Want to add your own dock? Run this command in your terminal:

Want to add your own dock? Run this command on a macOS device:

npx dockhunt

Or install the desktop app.

More details →
); } ================================================ FILE: src/components/BouncingLoader.tsx ================================================ import Image from "next/image"; import { motion } from "framer-motion"; const NextImage = motion(Image); export const BouncingLoader = () => { return (
); } ================================================ FILE: src/components/Dock.tsx ================================================ import { motion } from "framer-motion"; import type { App } from "@prisma/client"; import Link from "next/link"; import * as Tooltip from "@radix-ui/react-tooltip"; import { placeholderImageUrl } from "utils/constants"; import Image from "next/image"; import * as ScrollArea from '@radix-ui/react-scroll-area'; const DockImage = motion(Image); const DockItem = ({ app }: { app: App }) => { const variants = { hover: { width: 92, height: 80, }, initial: { width: 80, height: 80, }, }; return (
{app.name}
); }; export function Dock({ apps }: { apps: App[] }) { return (
{/* Dock background */}
{/* Scrollable container */}
{apps.map((app) => ( ))}
{/* TODO: Style the scrollbar: https://www.radix-ui.com/docs/primitives/components/scroll-area */}
); } ================================================ FILE: src/components/DockCard.tsx ================================================ import * as Tooltip from "@radix-ui/react-tooltip"; import type { inferRouterOutputs } from "@trpc/server"; import Image from "next/image"; import Link from "next/link"; import type { AppRouter } from "server/api/root"; import { Dock as DockComponent } from "./Dock"; export function DockCard({ dock, }: { dock: inferRouterOutputs["docks"]["getFeatured"][0]; }) { return (

{dock.user.name} @{dock.user.username}

{/* TODO: Use placeholder image for null values */} {`${dock.user.name}'s

{dock.user.name}

{dock.user.description && (

{dock.user.description}

)}
dockItem.app)} />
); } ================================================ FILE: src/components/MenuBar.tsx ================================================ import { format } from "date-fns"; import basedash from "images/basedash.svg"; import dockhunt from "images/dockhunt.svg"; import github from "images/github.svg"; import npm from "images/npm.svg"; import twitter from "images/twitter.svg"; import { signIn, signOut, useSession } from "next-auth/react"; import Image from "next/image"; import Link from "next/link"; import { useEffect, useState } from "react"; import { api } from "../utils/api"; export const MenuBar = () => { const { data: sessionData } = useSession(); const user = api.users.getOne.useQuery({ id: sessionData?.user?.id ?? "" }); const [date, setDate] = useState(new Date()); useEffect(() => { const timer = setInterval(() => { setDate(new Date()); }, 5000); // Update every 5 seconds so we don't get too out of sync return () => clearInterval(timer); }, []); return (
Dockhunt Dockhunt Top apps {user.data?.dock ? "Update your dock" : "Add your dock"} Made by Basedash
Basedash Twitter GitHub npm
{format(date, "eee MMM d p")}
{sessionData && sessionData.user && ( {sessionData.user.name} )}
); }; ================================================ FILE: src/env/client.mjs ================================================ // @ts-check import { clientEnv, clientSchema } from "./schema.mjs"; const _clientEnv = clientSchema.safeParse(clientEnv); export const formatErrors = ( /** @type {import('zod').ZodFormattedError,string>} */ errors, ) => Object.entries(errors) .map(([name, value]) => { if (value && "_errors" in value) return `${name}: ${value._errors.join(", ")}\n`; }) .filter(Boolean); if (!_clientEnv.success) { console.error( "❌ Invalid environment variables:\n", ...formatErrors(_clientEnv.error.format()), ); throw new Error("Invalid environment variables"); } 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; ================================================ FILE: src/env/schema.mjs ================================================ // @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: process.env.NODE_ENV === "production" ? z.string().min(1) : z.string().min(1).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(), ), TWITTER_CLIENT_ID: z.string(), TWITTER_CLIENT_SECRET: z.string(), BUCKET_ENDPOINT: z.string().url(), S3_ACCESS_KEY_ID: z.string(), S3_SECRET_ACCESS_KEY: z.string(), }); /** * You can't destruct `process.env` as a regular object in the Next.js * middleware, so you have to do it manually here. * @type {{ [k in keyof z.infer]: z.infer[k] | undefined }} */ export const serverEnv = { DATABASE_URL: process.env.DATABASE_URL, NODE_ENV: process.env.NODE_ENV, NEXTAUTH_SECRET: process.env.NEXTAUTH_SECRET, NEXTAUTH_URL: process.env.NEXTAUTH_URL, TWITTER_CLIENT_ID: process.env.TWITTER_CLIENT_ID, TWITTER_CLIENT_SECRET: process.env.TWITTER_CLIENT_SECRET, BUCKET_ENDPOINT: process.env.BUCKET_ENDPOINT, S3_ACCESS_KEY_ID: process.env.S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY: process.env.S3_SECRET_ACCESS_KEY, }; /** * 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_URL: 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]: z.infer[k] | undefined }} */ export const clientEnv = { NEXT_PUBLIC_URL: process.env.NEXT_PUBLIC_URL, }; ================================================ FILE: src/env/server.mjs ================================================ // @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, serverEnv } from "./schema.mjs"; import { env as clientEnv, formatErrors } from "./client.mjs"; const _serverEnv = serverSchema.safeParse(serverEnv); if (!_serverEnv.success) { console.error( "❌ Invalid environment variables:\n", ...formatErrors(_serverEnv.error.format()), ); throw new Error("Invalid environment variables"); } 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 }; ================================================ FILE: src/pages/_app.tsx ================================================ import Head from "next/head"; import { type AppType } from "next/app"; import { type Session } from "next-auth"; import * as Tooltip from "@radix-ui/react-tooltip"; import { SessionProvider } from "next-auth/react"; import Script from "next/script"; import { api } from "../utils/api"; import "../styles/globals.css"; import { MenuBar } from "components/MenuBar"; const MyApp: AppType<{ session: Session | null }> = ({ Component, pageProps: { session, ...pageProps }, }) => { return (