├── .dockerignore ├── .env.example ├── .eslintrc.json ├── .gitignore ├── .prettierrc ├── .vscode └── settings.json ├── Dockerfile ├── README.md ├── components.json ├── fly.toml ├── next.config.ts ├── package-lock.json ├── package.json ├── postcss.config.mjs ├── public ├── android-icon-144x144.png ├── android-icon-192x192.png ├── android-icon-36x36.png ├── android-icon-48x48.png ├── android-icon-72x72.png ├── android-icon-96x96.png ├── apple-icon-114x114.png ├── apple-icon-120x120.png ├── apple-icon-144x144.png ├── apple-icon-152x152.png ├── apple-icon-180x180.png ├── apple-icon-57x57.png ├── apple-icon-60x60.png ├── apple-icon-72x72.png ├── apple-icon-76x76.png ├── apple-icon-precomposed.png ├── apple-icon.png ├── browserconfig.xml ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon-96x96.png ├── favicon.ico ├── manifest.json ├── ms-icon-144x144.png ├── ms-icon-150x150.png ├── ms-icon-310x310.png └── ms-icon-70x70.png ├── src ├── app │ ├── (marketing) │ │ ├── actions.ts │ │ ├── page.tsx │ │ └── sitemap.ts │ ├── layout.tsx │ └── not-found.tsx ├── assets │ ├── img │ │ ├── icon-dark.png │ │ ├── icon-light.png │ │ ├── icon.png │ │ ├── logo-dark.png │ │ ├── logo-icon-dark.png │ │ ├── logo-icon-light.png │ │ └── logo-light.png │ └── logos │ │ ├── postmark.png │ │ ├── prisma-dark.png │ │ ├── prisma.png │ │ ├── react.png │ │ ├── remix-dark.png │ │ ├── remix.png │ │ ├── stripe.png │ │ ├── tailwindcss.png │ │ └── typescript.png ├── components │ ├── HomeBreadcrumb.tsx │ ├── brand │ │ ├── IconSvg.tsx │ │ ├── NextJsDark.tsx │ │ └── NextJsLight.tsx │ ├── pages │ │ ├── LandingPage.tsx │ │ ├── Page401.tsx │ │ └── Page404.tsx │ └── ui │ │ ├── accordion.tsx │ │ ├── badges │ │ ├── ColorBadge.tsx │ │ └── SimpleBadge.tsx │ │ ├── banners │ │ ├── ErrorBanner.tsx │ │ ├── InfoBanner.tsx │ │ ├── SuccessBanner.tsx │ │ └── WarningBanner.tsx │ │ ├── breadcrumb.tsx │ │ ├── button.tsx │ │ ├── buttons │ │ ├── ButtonEvent.tsx │ │ ├── ButtonPrimary.tsx │ │ ├── ButtonSecondary.tsx │ │ ├── ButtonTertiary.tsx │ │ └── LinkOrAhref.tsx │ │ ├── card.tsx │ │ ├── carousel.tsx │ │ ├── dropdown-menu.tsx │ │ ├── honeypot │ │ └── HoneypotInput.tsx │ │ ├── icons │ │ ├── CheckIcon.tsx │ │ ├── ChevronDownIcon.tsx │ │ ├── ExternalLinkEmptyIcon.tsx │ │ ├── GitHubIcon.tsx │ │ ├── MegaphoneIcon.tsx │ │ ├── MegaphoneIconFilled.tsx │ │ ├── RightArrowIcon.tsx │ │ ├── SvgOrImg.tsx │ │ ├── TwitterIcon.tsx │ │ ├── XIcon.tsx │ │ └── YouTubeIcon.tsx │ │ ├── input.tsx │ │ ├── input │ │ └── InputSelect.tsx │ │ ├── select.tsx │ │ ├── selectors │ │ ├── DarkModeToggle.tsx │ │ ├── LocaleSelector.tsx │ │ └── ThemeSelector.tsx │ │ ├── sonner.tsx │ │ ├── textarea.tsx │ │ ├── toast.tsx │ │ ├── toaster.tsx │ │ ├── tooltip.tsx │ │ ├── tooltips │ │ └── HintTooltip.tsx │ │ └── use-toast.ts ├── i18n │ ├── i18n-context.tsx │ ├── locales │ │ ├── en │ │ │ └── translations.json │ │ └── es │ │ │ └── translations.json │ ├── server.ts │ └── settings.ts ├── lib │ ├── colors.ts │ ├── session.ts │ ├── theme.ts │ ├── url.ts │ └── utils.ts └── styles │ ├── globals.css │ └── themes.css ├── tailwind.config.ts └── tsconfig.json /.dockerignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | SESSION_SECRET=abc123 -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals", 3 | "rules": { 4 | "no-console": "warn", 5 | "import/first": "off", 6 | "@typescript-eslint/consistent-type-imports": "off", 7 | "prefer-const": "off", 8 | "@typescript-eslint/ban-ts-comment": "off", 9 | "no-var": "off", 10 | "@typescript-eslint/no-unused-vars": "warn" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env 30 | .env.production 31 | .env*.local 32 | 33 | # vercel 34 | .vercel 35 | 36 | # typescript 37 | *.tsbuildinfo 38 | next-env.d.ts 39 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["prettier-plugin-tailwindcss"], 3 | "arrowParens": "always", 4 | "bracketSpacing": true, 5 | "embeddedLanguageFormatting": "auto", 6 | "endOfLine": "lf", 7 | "htmlWhitespaceSensitivity": "css", 8 | "printWidth": 160, 9 | "proseWrap": "preserve", 10 | "quoteProps": "as-needed", 11 | "semi": true, 12 | "singleQuote": false, 13 | "tabWidth": 2, 14 | "trailingComma": "es5", 15 | "useTabs": false 16 | } 17 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "i18n-ally.localesPaths": ["src/i18n/locales"] 3 | } 4 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax = docker/dockerfile:1 2 | 3 | # Adjust NODE_VERSION as desired 4 | ARG NODE_VERSION=20.12.2 5 | FROM node:${NODE_VERSION}-slim as base 6 | 7 | LABEL fly_launch_runtime="Next.js" 8 | 9 | # Next.js app lives here 10 | WORKDIR /app 11 | 12 | # Set production environment 13 | ENV NODE_ENV="production" 14 | 15 | 16 | # Throw-away build stage to reduce size of final image 17 | FROM base as build 18 | 19 | # Install packages needed to build node modules 20 | RUN apt-get update -qq && \ 21 | apt-get install --no-install-recommends -y build-essential node-gyp pkg-config python-is-python3 22 | 23 | # Install node modules 24 | COPY --link package-lock.json package.json ./ 25 | RUN npm ci --include=dev --legacy-peer-deps 26 | 27 | # Copy application code 28 | COPY --link . . 29 | 30 | # Build application 31 | RUN npm run build 32 | 33 | # Remove development dependencies 34 | RUN npm prune --omit=dev --legacy-peer-deps 35 | 36 | 37 | # Final stage for app image 38 | FROM base 39 | 40 | # Copy built application 41 | COPY --from=build /app /app 42 | 43 | # Start the server by default, this can be overwritten at runtime 44 | EXPOSE 3000 45 | CMD [ "npm", "run", "start" ] 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Next.js Gear 2 | 3 | Start from scratch but not from zero: 4 | 5 | - [Next.js](https://nextjs.org/) 6 | - [Tailwind CSS](https://tailwindcss.com/) 7 | - [shadcn/ui](https://ui.shadcn.com/) 8 | - [i18n](https://www.i18next.com/) 9 | 10 | Try [Remix Gear](https://github.com/rockstack-dev/remix-gear) 11 | 12 | ## Demos 13 | 14 | Each demo has it's own branch to keep the main branch clean. 15 | 16 | 1. Form Actions: [@forms](https://nextjs-gear-git-forms-alexandro-pro.vercel.app/forms) 17 | 2. AI Structured Outputs [@ai-structured](https://nextjs-gear-git-ai-structured-alexandro-pro.vercel.app/ai-structured-outputs) 18 | 3. WhatsApp API [@whatsapp-api](https://nextjs-gear-git-whatsapp-api-alexandro-pro.vercel.app/whatsapp-api) 19 | 4. YouTube captions scraper [@youtube-to-data](https://nextjs-gear-git-youtube-to-data-alexandro-pro.vercel.app/youtube-to-data) 20 | 21 | ## Looking for a SaaS starter kit? 22 | 23 | Check out [SaasRock](https://saasrock.com/?ref=nextjs-gear) or [RockStack](https://rockstack.dev/?ref=nextjs-gear)! 24 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "src/styles/globals.css", 9 | "baseColor": "zinc", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /fly.toml: -------------------------------------------------------------------------------- 1 | app = "nextjs-gear" 2 | primary_region = 'iad' 3 | 4 | [build] 5 | 6 | [http_service] 7 | internal_port = 3000 8 | force_https = true 9 | auto_stop_machines = true 10 | auto_start_machines = true 11 | min_machines_running = 0 12 | processes = ['app'] -------------------------------------------------------------------------------- /next.config.ts: -------------------------------------------------------------------------------- 1 | import type { NextConfig } from "next"; 2 | 3 | const nextConfig: NextConfig = { 4 | eslint: { 5 | ignoreDuringBuilds: true, 6 | }, 7 | }; 8 | 9 | export default nextConfig; 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nextjs-gear", 3 | "version": "1.0.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint", 10 | "typecheck": "tsc -b", 11 | "prettier": "prettier --write '**/*.{ts,tsx}'" 12 | }, 13 | "dependencies": { 14 | "@headlessui/react": "^2.1.1", 15 | "@headlessui/tailwindcss": "^0.2.1", 16 | "@radix-ui/react-accordion": "^1.2.0", 17 | "@radix-ui/react-dropdown-menu": "^2.1.1", 18 | "@radix-ui/react-select": "^2.1.1", 19 | "@radix-ui/react-slot": "^1.1.0", 20 | "@radix-ui/react-toast": "^1.2.1", 21 | "@radix-ui/react-tooltip": "^1.1.1", 22 | "accept-language": "^3.0.18", 23 | "class-variance-authority": "^0.7.0", 24 | "clsx": "^2.1.1", 25 | "cookie": "^0.6.0", 26 | "embla-carousel-react": "^8.1.5", 27 | "i18next": "^23.11.5", 28 | "i18next-browser-languagedetector": "^8.0.0", 29 | "i18next-resources-to-backend": "^1.2.1", 30 | "jsonwebtoken": "^9.0.2", 31 | "lucide-react": "^0.395.0", 32 | "marked": "^13.0.1", 33 | "next": "^15.0.0-canary.73", 34 | "next-themes": "^0.3.0", 35 | "react": "19.0.0-rc-512b09b2-20240718", 36 | "react-dom": "19.0.0-rc-512b09b2-20240718", 37 | "react-i18next": "^14.1.2", 38 | "sonner": "^1.5.0", 39 | "tailwind-merge": "^2.3.0", 40 | "tailwindcss-animate": "^1.0.7" 41 | }, 42 | "devDependencies": { 43 | "@types/cookie": "^0.6.0", 44 | "@types/jsonwebtoken": "^9.0.6", 45 | "@types/node": "^20", 46 | "@types/react": "^18", 47 | "@types/react-dom": "^18", 48 | "eslint": "^8", 49 | "eslint-config-next": "14.2.4", 50 | "postcss": "^8", 51 | "prettier": "3.3.2", 52 | "prettier-plugin-tailwindcss": "^0.6.5", 53 | "tailwindcss": "^3.4.1", 54 | "typescript": "^5" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /public/android-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rockstack-dev/nextjs-gear/1b6e9e058ae51ed85d1a6b37d916095d4dad3f6f/public/android-icon-144x144.png -------------------------------------------------------------------------------- /public/android-icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rockstack-dev/nextjs-gear/1b6e9e058ae51ed85d1a6b37d916095d4dad3f6f/public/android-icon-192x192.png -------------------------------------------------------------------------------- /public/android-icon-36x36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rockstack-dev/nextjs-gear/1b6e9e058ae51ed85d1a6b37d916095d4dad3f6f/public/android-icon-36x36.png -------------------------------------------------------------------------------- /public/android-icon-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rockstack-dev/nextjs-gear/1b6e9e058ae51ed85d1a6b37d916095d4dad3f6f/public/android-icon-48x48.png -------------------------------------------------------------------------------- /public/android-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rockstack-dev/nextjs-gear/1b6e9e058ae51ed85d1a6b37d916095d4dad3f6f/public/android-icon-72x72.png -------------------------------------------------------------------------------- /public/android-icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rockstack-dev/nextjs-gear/1b6e9e058ae51ed85d1a6b37d916095d4dad3f6f/public/android-icon-96x96.png -------------------------------------------------------------------------------- /public/apple-icon-114x114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rockstack-dev/nextjs-gear/1b6e9e058ae51ed85d1a6b37d916095d4dad3f6f/public/apple-icon-114x114.png -------------------------------------------------------------------------------- /public/apple-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rockstack-dev/nextjs-gear/1b6e9e058ae51ed85d1a6b37d916095d4dad3f6f/public/apple-icon-120x120.png -------------------------------------------------------------------------------- /public/apple-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rockstack-dev/nextjs-gear/1b6e9e058ae51ed85d1a6b37d916095d4dad3f6f/public/apple-icon-144x144.png -------------------------------------------------------------------------------- /public/apple-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rockstack-dev/nextjs-gear/1b6e9e058ae51ed85d1a6b37d916095d4dad3f6f/public/apple-icon-152x152.png -------------------------------------------------------------------------------- /public/apple-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rockstack-dev/nextjs-gear/1b6e9e058ae51ed85d1a6b37d916095d4dad3f6f/public/apple-icon-180x180.png -------------------------------------------------------------------------------- /public/apple-icon-57x57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rockstack-dev/nextjs-gear/1b6e9e058ae51ed85d1a6b37d916095d4dad3f6f/public/apple-icon-57x57.png -------------------------------------------------------------------------------- /public/apple-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rockstack-dev/nextjs-gear/1b6e9e058ae51ed85d1a6b37d916095d4dad3f6f/public/apple-icon-60x60.png -------------------------------------------------------------------------------- /public/apple-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rockstack-dev/nextjs-gear/1b6e9e058ae51ed85d1a6b37d916095d4dad3f6f/public/apple-icon-72x72.png -------------------------------------------------------------------------------- /public/apple-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rockstack-dev/nextjs-gear/1b6e9e058ae51ed85d1a6b37d916095d4dad3f6f/public/apple-icon-76x76.png -------------------------------------------------------------------------------- /public/apple-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rockstack-dev/nextjs-gear/1b6e9e058ae51ed85d1a6b37d916095d4dad3f6f/public/apple-icon-precomposed.png -------------------------------------------------------------------------------- /public/apple-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rockstack-dev/nextjs-gear/1b6e9e058ae51ed85d1a6b37d916095d4dad3f6f/public/apple-icon.png -------------------------------------------------------------------------------- /public/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | #ffffff -------------------------------------------------------------------------------- /public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rockstack-dev/nextjs-gear/1b6e9e058ae51ed85d1a6b37d916095d4dad3f6f/public/favicon-16x16.png -------------------------------------------------------------------------------- /public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rockstack-dev/nextjs-gear/1b6e9e058ae51ed85d1a6b37d916095d4dad3f6f/public/favicon-32x32.png -------------------------------------------------------------------------------- /public/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rockstack-dev/nextjs-gear/1b6e9e058ae51ed85d1a6b37d916095d4dad3f6f/public/favicon-96x96.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rockstack-dev/nextjs-gear/1b6e9e058ae51ed85d1a6b37d916095d4dad3f6f/public/favicon.ico -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "App", 3 | "icons": [ 4 | { 5 | "src": "\/android-icon-36x36.png", 6 | "sizes": "36x36", 7 | "type": "image\/png", 8 | "density": "0.75" 9 | }, 10 | { 11 | "src": "\/android-icon-48x48.png", 12 | "sizes": "48x48", 13 | "type": "image\/png", 14 | "density": "1.0" 15 | }, 16 | { 17 | "src": "\/android-icon-72x72.png", 18 | "sizes": "72x72", 19 | "type": "image\/png", 20 | "density": "1.5" 21 | }, 22 | { 23 | "src": "\/android-icon-96x96.png", 24 | "sizes": "96x96", 25 | "type": "image\/png", 26 | "density": "2.0" 27 | }, 28 | { 29 | "src": "\/android-icon-144x144.png", 30 | "sizes": "144x144", 31 | "type": "image\/png", 32 | "density": "3.0" 33 | }, 34 | { 35 | "src": "\/android-icon-192x192.png", 36 | "sizes": "192x192", 37 | "type": "image\/png", 38 | "density": "4.0" 39 | } 40 | ] 41 | } -------------------------------------------------------------------------------- /public/ms-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rockstack-dev/nextjs-gear/1b6e9e058ae51ed85d1a6b37d916095d4dad3f6f/public/ms-icon-144x144.png -------------------------------------------------------------------------------- /public/ms-icon-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rockstack-dev/nextjs-gear/1b6e9e058ae51ed85d1a6b37d916095d4dad3f6f/public/ms-icon-150x150.png -------------------------------------------------------------------------------- /public/ms-icon-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rockstack-dev/nextjs-gear/1b6e9e058ae51ed85d1a6b37d916095d4dad3f6f/public/ms-icon-310x310.png -------------------------------------------------------------------------------- /public/ms-icon-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rockstack-dev/nextjs-gear/1b6e9e058ae51ed85d1a6b37d916095d4dad3f6f/public/ms-icon-70x70.png -------------------------------------------------------------------------------- /src/app/(marketing)/actions.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | 3 | import { getUserInfo, setUserSession } from "@/lib/session"; 4 | import { redirect } from "next/navigation"; 5 | 6 | export async function actionToggleScheme(formData: FormData) { 7 | const redirectTo = formData.get("redirectTo") as string; 8 | const userInfo = getUserInfo(); 9 | userInfo.scheme = userInfo.scheme === "light" ? "dark" : "light"; 10 | console.log({ 11 | scheme: userInfo.scheme, 12 | }); 13 | setUserSession(userInfo); 14 | return redirect(redirectTo || "/"); 15 | } 16 | 17 | export async function actionSetTheme(formData: FormData) { 18 | const redirectTo = formData.get("redirectTo") as string; 19 | const userInfo = getUserInfo(); 20 | setUserSession({ 21 | ...userInfo, 22 | theme: formData.get("theme") as string, 23 | }); 24 | return redirect(redirectTo || "/"); 25 | } 26 | 27 | export async function actionLogout(formData: FormData) { 28 | console.log("logout"); 29 | const userInfo = getUserInfo(); 30 | setUserSession({ 31 | ...userInfo, 32 | userId: null, 33 | }); 34 | return redirect("/"); 35 | } 36 | -------------------------------------------------------------------------------- /src/app/(marketing)/page.tsx: -------------------------------------------------------------------------------- 1 | import { getServerTranslations } from "@/i18n/server"; 2 | import { Metadata } from "next/types"; 3 | import LandingPage from "@/components/pages/LandingPage"; 4 | 5 | export async function generateMetadata() { 6 | const { t } = await getServerTranslations(); 7 | const metadata: Metadata = { 8 | title: "Next.js Gear", 9 | }; 10 | return metadata; 11 | } 12 | 13 | export default async function Index() { 14 | return ; 15 | } 16 | -------------------------------------------------------------------------------- /src/app/(marketing)/sitemap.ts: -------------------------------------------------------------------------------- 1 | import { getBaseURL } from "@/lib/url"; 2 | import { MetadataRoute } from "next"; 3 | 4 | export default function sitemap(): MetadataRoute.Sitemap { 5 | const links = [ 6 | { url: getBaseURL(), lastModified: new Date() }, 7 | { url: `${getBaseURL()}/contact`, lastModified: new Date() }, 8 | ]; 9 | return links; 10 | } 11 | -------------------------------------------------------------------------------- /src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import { Inter } from "next/font/google"; 2 | import "@/styles/globals.css"; 3 | import "@/styles/themes.css"; 4 | import { dir } from "i18next"; 5 | import { detectLanguage, getServerTranslations } from "@/i18n/server"; 6 | import { I18nProvider } from "@/i18n/i18n-context"; 7 | import { getUserInfo } from "@/lib/session"; 8 | import clsx from "clsx"; 9 | import { Metadata } from "next"; 10 | import { Toaster as SonnerToaster } from "@/components/ui/sonner"; 11 | 12 | const inter = Inter({ subsets: ["latin"] }); 13 | 14 | export async function generateMetadata(): Promise { 15 | const { t } = await getServerTranslations(); 16 | return { 17 | title: "Next.js Gear", 18 | icons: [ 19 | { url: "/android-icon-192x192.png", sizes: "192x192", type: "image/png" }, 20 | { url: "/favicon-32x32.png", sizes: "32x32", type: "image/png" }, 21 | { url: "/favicon-96x96.png", sizes: "96x96", type: "image/png" }, 22 | { url: "/favicon-16x16.png", sizes: "16x16", type: "image/png" }, 23 | ], 24 | }; 25 | } 26 | 27 | export default async function RootLayout({ 28 | children, 29 | }: Readonly<{ 30 | children: React.ReactNode; 31 | }>) { 32 | const lng = await detectLanguage(); 33 | const userInfo = getUserInfo(); 34 | const scheme = userInfo?.scheme || "light"; 35 | 36 | return ( 37 | 38 | 39 | 40 | {children} 41 | 42 | 43 | 44 | 45 | ); 46 | } 47 | -------------------------------------------------------------------------------- /src/app/not-found.tsx: -------------------------------------------------------------------------------- 1 | import Page404 from "@/components/pages/Page404"; 2 | 3 | export default function NotFound() { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /src/assets/img/icon-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rockstack-dev/nextjs-gear/1b6e9e058ae51ed85d1a6b37d916095d4dad3f6f/src/assets/img/icon-dark.png -------------------------------------------------------------------------------- /src/assets/img/icon-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rockstack-dev/nextjs-gear/1b6e9e058ae51ed85d1a6b37d916095d4dad3f6f/src/assets/img/icon-light.png -------------------------------------------------------------------------------- /src/assets/img/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rockstack-dev/nextjs-gear/1b6e9e058ae51ed85d1a6b37d916095d4dad3f6f/src/assets/img/icon.png -------------------------------------------------------------------------------- /src/assets/img/logo-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rockstack-dev/nextjs-gear/1b6e9e058ae51ed85d1a6b37d916095d4dad3f6f/src/assets/img/logo-dark.png -------------------------------------------------------------------------------- /src/assets/img/logo-icon-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rockstack-dev/nextjs-gear/1b6e9e058ae51ed85d1a6b37d916095d4dad3f6f/src/assets/img/logo-icon-dark.png -------------------------------------------------------------------------------- /src/assets/img/logo-icon-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rockstack-dev/nextjs-gear/1b6e9e058ae51ed85d1a6b37d916095d4dad3f6f/src/assets/img/logo-icon-light.png -------------------------------------------------------------------------------- /src/assets/img/logo-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rockstack-dev/nextjs-gear/1b6e9e058ae51ed85d1a6b37d916095d4dad3f6f/src/assets/img/logo-light.png -------------------------------------------------------------------------------- /src/assets/logos/postmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rockstack-dev/nextjs-gear/1b6e9e058ae51ed85d1a6b37d916095d4dad3f6f/src/assets/logos/postmark.png -------------------------------------------------------------------------------- /src/assets/logos/prisma-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rockstack-dev/nextjs-gear/1b6e9e058ae51ed85d1a6b37d916095d4dad3f6f/src/assets/logos/prisma-dark.png -------------------------------------------------------------------------------- /src/assets/logos/prisma.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rockstack-dev/nextjs-gear/1b6e9e058ae51ed85d1a6b37d916095d4dad3f6f/src/assets/logos/prisma.png -------------------------------------------------------------------------------- /src/assets/logos/react.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rockstack-dev/nextjs-gear/1b6e9e058ae51ed85d1a6b37d916095d4dad3f6f/src/assets/logos/react.png -------------------------------------------------------------------------------- /src/assets/logos/remix-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rockstack-dev/nextjs-gear/1b6e9e058ae51ed85d1a6b37d916095d4dad3f6f/src/assets/logos/remix-dark.png -------------------------------------------------------------------------------- /src/assets/logos/remix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rockstack-dev/nextjs-gear/1b6e9e058ae51ed85d1a6b37d916095d4dad3f6f/src/assets/logos/remix.png -------------------------------------------------------------------------------- /src/assets/logos/stripe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rockstack-dev/nextjs-gear/1b6e9e058ae51ed85d1a6b37d916095d4dad3f6f/src/assets/logos/stripe.png -------------------------------------------------------------------------------- /src/assets/logos/tailwindcss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rockstack-dev/nextjs-gear/1b6e9e058ae51ed85d1a6b37d916095d4dad3f6f/src/assets/logos/tailwindcss.png -------------------------------------------------------------------------------- /src/assets/logos/typescript.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rockstack-dev/nextjs-gear/1b6e9e058ae51ed85d1a6b37d916095d4dad3f6f/src/assets/logos/typescript.png -------------------------------------------------------------------------------- /src/components/HomeBreadcrumb.tsx: -------------------------------------------------------------------------------- 1 | import { Breadcrumb, BreadcrumbList, BreadcrumbItem, BreadcrumbLink, BreadcrumbSeparator, BreadcrumbPage } from "./ui/breadcrumb"; 2 | 3 | interface Props { 4 | title: string; 5 | } 6 | export default function HomeBreadcrumb({ title }: Props) { 7 | return ( 8 | 9 | 10 | 11 | Home 12 | 13 | 14 | 15 | {title} 16 | 17 | 18 | 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /src/components/brand/IconSvg.tsx: -------------------------------------------------------------------------------- 1 | import clsx from "clsx"; 2 | 3 | export default function IconSvg({ className }: { className?: string }) { 4 | return ( 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | ); 58 | return ( 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 116 | 117 | 118 | 119 | 120 | 121 | ); 122 | } 123 | -------------------------------------------------------------------------------- /src/components/brand/NextJsDark.tsx: -------------------------------------------------------------------------------- 1 | export default function NextJsDark({ className }: { className?: string }) { 2 | return ( 3 | 4 | 5 | 6 | 7 | 8 | 9 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /src/components/brand/NextJsLight.tsx: -------------------------------------------------------------------------------- 1 | export default function NextJsLight({ className }: { className?: string }) { 2 | return ( 3 | 4 | 5 | 6 | 7 | 8 | 9 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /src/components/pages/LandingPage.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useTranslation } from "react-i18next"; 4 | import { useToast } from "../ui/use-toast"; 5 | import { Button } from "../ui/button"; 6 | import Link from "next/link"; 7 | import YouTubeIcon from "../ui/icons/YouTubeIcon"; 8 | import GitHubIcon from "../ui/icons/GitHubIcon"; 9 | import DarkModeToggle from "../ui/selectors/DarkModeToggle"; 10 | import LocaleSelector from "../ui/selectors/LocaleSelector"; 11 | import ThemeSelector from "../ui/selectors/ThemeSelector"; 12 | import TwitterIcon from "../ui/icons/TwitterIcon"; 13 | 14 | const GITHUB_URL = "https://github.com/rockstack-dev/nextjs-gear.git"; 15 | const YOUTUBE_URL = "https://www.youtube.com/@rockstack-dev"; 16 | const TWITTER_URL = "https://twitter.com/AlexandroMtzG"; 17 | 18 | export default function LandingPage() { 19 | const { t } = useTranslation(); 20 | const { toast } = useToast(); 21 | return ( 22 |
23 |
24 |
25 |

Next.js Gear 🦾

26 |
27 | 32 | 37 | 42 | 43 | 44 | 45 |
46 |
47 |
48 |
49 | The cleanest way to start a{" "} 50 | 51 | Next.js 52 | {" "} 53 | project with{" "} 54 | 55 | Tailwind CSS 56 | 57 | ,{" "} 58 | 59 | shadcn/ui 60 | 61 | , and{" "} 62 | 63 | i18n 64 | 65 | . 66 |
67 |
68 |
 69 |               
 80 |               git clone {GITHUB_URL}
 81 |             
82 |
83 |
84 | 85 | Remix 86 | 87 |
88 | 89 | Next.js 90 | 91 |
92 | 93 | {t("by")} SaasRock 94 | 95 |
96 |
97 | 98 |
99 |

Demos

100 |
101 | 105 |
Form actions
106 | 107 | 111 |
AI Structured Outputs
112 | 113 | 117 |
WhatsApp API
118 | 119 | 123 |
YouTube to Data
124 | 125 | 129 |
Let me know!
130 | 131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 | ); 144 | } 145 | -------------------------------------------------------------------------------- /src/components/pages/Page401.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useTranslation } from "react-i18next"; 4 | 5 | interface Props { 6 | withFooter?: boolean; 7 | } 8 | export default function Page401({ withFooter = true }: Props) { 9 | const { t } = useTranslation(); 10 | function goBack() { 11 | window.history.back(); 12 | } 13 | return ( 14 | <> 15 |
16 |
17 |
18 |
{/* */}
19 |
20 |
21 |

Unauthorized

22 |

You're not authorized to see this page.

23 |

Contact your admin and verify your permissions.

24 |
25 | 28 |
29 |
30 |
31 |
32 |
33 |
34 | 35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /src/components/pages/Page404.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useTranslation } from "react-i18next"; 4 | 5 | interface Props { 6 | title?: string; 7 | withLogo?: boolean; 8 | withFooter?: boolean; 9 | withGoBack?: boolean; 10 | customBackButton?: React.ReactNode; 11 | logo?: string | undefined; 12 | } 13 | export default function Page404({ title, withLogo = true, withFooter = true, withGoBack = true, customBackButton, logo }: Props) { 14 | const { t } = useTranslation(); 15 | function goBack() { 16 | window.history.back(); 17 | } 18 | return ( 19 | <> 20 |
21 |
22 |
23 | {/* {withLogo &&
{logo ? Logo : }
} */} 24 |
25 |
26 |

404 error

27 |

{title || "Page not found."}

28 |

Sorry, we couldn’t find the page you’re looking for.

29 | {(withGoBack || customBackButton) && ( 30 |
31 | {customBackButton} 32 | {!customBackButton && withGoBack && ( 33 | 36 | )} 37 |
38 | )} 39 |
40 |
41 |
42 |
43 |
44 | {/* {withFooter && } */} 45 | 46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /src/components/ui/accordion.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as AccordionPrimitive from "@radix-ui/react-accordion" 5 | import { ChevronDown } from "lucide-react" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | const Accordion = AccordionPrimitive.Root 10 | 11 | const AccordionItem = React.forwardRef< 12 | React.ElementRef, 13 | React.ComponentPropsWithoutRef 14 | >(({ className, ...props }, ref) => ( 15 | 20 | )) 21 | AccordionItem.displayName = "AccordionItem" 22 | 23 | const AccordionTrigger = React.forwardRef< 24 | React.ElementRef, 25 | React.ComponentPropsWithoutRef 26 | >(({ className, children, ...props }, ref) => ( 27 | 28 | svg]:rotate-180", 32 | className 33 | )} 34 | {...props} 35 | > 36 | {children} 37 | 38 | 39 | 40 | )) 41 | AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName 42 | 43 | const AccordionContent = React.forwardRef< 44 | React.ElementRef, 45 | React.ComponentPropsWithoutRef 46 | >(({ className, children, ...props }, ref) => ( 47 | 52 |
{children}
53 |
54 | )) 55 | 56 | AccordionContent.displayName = AccordionPrimitive.Content.displayName 57 | 58 | export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } 59 | -------------------------------------------------------------------------------- /src/components/ui/badges/ColorBadge.tsx: -------------------------------------------------------------------------------- 1 | import { Colors, getBadgeColor } from "@/lib/colors"; 2 | import clsx from "clsx"; 3 | 4 | interface Props { 5 | color?: Colors | null; 6 | size?: "sm" | "md"; 7 | } 8 | 9 | export default function ColorBadge({ color, size = "md" }: Props) { 10 | return ( 11 | 19 | 20 | 21 | 22 | 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /src/components/ui/badges/SimpleBadge.tsx: -------------------------------------------------------------------------------- 1 | import { Colors, getBadgeColor, getBadgeColorDark } from "@/lib/colors"; 2 | import clsx from "clsx"; 3 | 4 | interface Props { 5 | title?: string; 6 | color: Colors; 7 | className?: string; 8 | children?: React.ReactNode; 9 | underline?: boolean; 10 | darkMode?: boolean; 11 | } 12 | 13 | export default function SimpleBadge({ title, color, className, children, underline, darkMode }: Props) { 14 | return ( 15 |
24 | {title ?? children} 25 |
26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /src/components/ui/banners/ErrorBanner.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from "react"; 2 | import { useTranslation } from "react-i18next"; 3 | import Link from "next/link"; 4 | 5 | interface Props { 6 | title: ReactNode; 7 | text?: ReactNode | string; 8 | children?: ReactNode; 9 | } 10 | 11 | export default function ErrorBanner({ title = "Error", text = "", children }: Props) { 12 | const { t } = useTranslation(); 13 | return ( 14 |
15 |
16 |
17 |
18 | 19 | 24 | 25 |
26 | 27 |
28 |

{title}

29 |
30 |
31 | {text} {children} 32 |
33 |
34 |
35 |
36 |
37 |
38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /src/components/ui/banners/InfoBanner.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from "react"; 2 | import { useTranslation } from "react-i18next"; 3 | import Link from "next/link"; 4 | 5 | interface Props { 6 | title: ReactNode; 7 | text?: ReactNode | string; 8 | children?: ReactNode; 9 | } 10 | 11 | export default function InfoBanner({ title = "Note", text = "", children }: Props) { 12 | const { t } = useTranslation(); 13 | return ( 14 |
15 |
16 |
17 |
18 | 19 | 24 | 25 |
26 | 27 |
28 |

{title}

29 |
30 |
31 | {text} {children} 32 |
33 |
34 |
35 |
36 |
37 |
38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /src/components/ui/banners/SuccessBanner.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from "react"; 2 | import { useTranslation } from "react-i18next"; 3 | import Link from "next/link"; 4 | import CheckIcon from "../icons/CheckIcon"; 5 | 6 | interface Props { 7 | title?: string; 8 | text?: ReactNode | string; 9 | children?: ReactNode; 10 | } 11 | 12 | export default function SuccessBanner({ title, text = "", children }: Props) { 13 | const { t } = useTranslation(); 14 | return ( 15 |
16 |
17 |
18 |
19 | 20 |
21 | 22 |
23 | {title &&

{t(title)}

} 24 |
25 |
26 | {text} {children} 27 |
28 |
29 |
30 |
31 |
32 |
33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /src/components/ui/banners/WarningBanner.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from "react"; 2 | import { useTranslation } from "react-i18next"; 3 | 4 | interface Props { 5 | title: ReactNode; 6 | text?: ReactNode; 7 | children?: ReactNode; 8 | } 9 | 10 | export default function WarningBanner({ title = "Warning", text = "", children }: Props) { 11 | const { t } = useTranslation(); 12 | return ( 13 |
14 |
15 |
16 |
17 | 18 | 23 | 24 |
25 | 26 |
27 |

{title}

28 |
29 |
30 | {text} {children} 31 |
32 |
33 |
34 |
35 |
36 |
37 | ); 38 | } 39 | -------------------------------------------------------------------------------- /src/components/ui/breadcrumb.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { Slot } from "@radix-ui/react-slot" 3 | import { ChevronRight, MoreHorizontal } from "lucide-react" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | const Breadcrumb = React.forwardRef< 8 | HTMLElement, 9 | React.ComponentPropsWithoutRef<"nav"> & { 10 | separator?: React.ReactNode 11 | } 12 | >(({ ...props }, ref) =>