Initial commit

This commit is contained in:
Jinho Jang
2026-04-20 22:24:39 +09:00
committed by GitHub
commit c0cb0ff0ed
74 changed files with 8075 additions and 0 deletions
+45
View File
@@ -0,0 +1,45 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# Dependencies
node_modules
.pnp
.pnp.js
# Local env files
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# Testing
coverage
# Turbo
.turbo
# Vercel
.vercel
# Build Outputs
.next/
out/
build
dist
coverage
next-env.d.ts
*.tsbuildinfo
*.db
*.db-*
*.sqlite
*.sqlite3
# Debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Misc
.DS_Store
*.pem
View File
+32
View File
@@ -0,0 +1,32 @@
# logos-turborepo-next-tailwind-i18n-template
A pnpm + Turborepo starter with:
- `apps/web`: Next.js frontend with Tailwind CSS v4 and `next-intl`
- `apps/cms`: standalone Payload CMS app with Admin dashboard
- `packages/ui`: shared React UI primitives
- `packages/config`: shared ESLint / TypeScript / Prettier config
- `packages/types`: shared application types, including Payload-generated types
## Why this shape
- Apps stay as entrypoints only.
- Shared code lives in purpose-specific packages.
- The CMS is isolated from the frontend deployment boundary.
- Payload-generated types can be shared without coupling the frontend to Payload runtime packages.
## Commands
```bash
pnpm install
pnpm dev
pnpm build
pnpm check-types
pnpm generate-types
```
## Local URLs
- Web: `http://localhost:3000`
- CMS: `http://localhost:3001`
- Payload Admin: `http://localhost:3001/admin`
+4
View File
@@ -0,0 +1,4 @@
PAYLOAD_SECRET=change-me
DATABASE_URL=file:./cms.db
NEXT_PUBLIC_SERVER_URL=http://localhost:3001
NEXT_PUBLIC_WEB_URL=http://localhost:3000
+3
View File
@@ -0,0 +1,3 @@
import nextConfig from '../../packages/config/eslint/next.mjs'
export default nextConfig
+14
View File
@@ -0,0 +1,14 @@
import { fileURLToPath } from 'node:url'
import { withPayload } from '@payloadcms/next/withPayload'
const workspaceRoot = fileURLToPath(new URL('../..', import.meta.url))
const nextConfig = {
reactStrictMode: true,
turbopack: {
root: workspaceRoot,
},
}
export default withPayload(nextConfig)
+32
View File
@@ -0,0 +1,32 @@
{
"name": "cms",
"private": true,
"type": "module",
"scripts": {
"dev": "next dev --port 3001",
"build": "next build",
"start": "next start",
"lint": "eslint . --max-warnings 0",
"lint:fix": "eslint . --fix",
"check-types": "next typegen && tsc --noEmit",
"generate-types": "PAYLOAD_CONFIG_PATH=payload.config.ts payload generate:types"
},
"dependencies": {
"@payloadcms/db-sqlite": "3.83.0",
"@payloadcms/next": "3.83.0",
"@payloadcms/richtext-lexical": "3.83.0",
"graphql": "^16.13.2",
"next": "16.2.4",
"payload": "3.83.0",
"react": "^19.2.5",
"react-dom": "^19.2.5",
"sharp": "^0.34.5"
},
"devDependencies": {
"@types/node": "25.6.0",
"@types/react": "^19.2.14",
"@types/react-dom": "19.2.3",
"eslint": "^10.2.1",
"typescript": "^6.0.3"
}
}
+37
View File
@@ -0,0 +1,37 @@
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import { sqliteAdapter } from '@payloadcms/db-sqlite'
import { lexicalEditor } from '@payloadcms/richtext-lexical'
import { buildConfig } from 'payload'
import { Pages } from './src/collections/Pages'
import { Users } from './src/collections/Users'
import { SiteSettings } from './src/globals/SiteSettings'
const dirname = path.dirname(fileURLToPath(import.meta.url))
const serverURL = process.env.NEXT_PUBLIC_SERVER_URL || 'http://localhost:3001'
const frontendURL = process.env.NEXT_PUBLIC_WEB_URL || 'http://localhost:3000'
export default buildConfig({
admin: {
user: Users.slug,
},
collections: [Users, Pages],
cors: [serverURL, frontendURL],
csrf: [serverURL, frontendURL],
db: sqliteAdapter({
client: {
url: process.env.DATABASE_URL || 'file:./cms.db',
},
wal: true,
}),
editor: lexicalEditor(),
globals: [SiteSettings],
secret: process.env.PAYLOAD_SECRET || 'dev-secret',
serverURL,
typescript: {
declare: false,
outputFile: path.resolve(dirname, '../../packages/types/src/payload.ts'),
},
})
+13
View File
@@ -0,0 +1,13 @@
html {
font-family: ui-sans-serif, system-ui, sans-serif;
}
body {
margin: 0;
background: #0b1020;
color: #f3f4f6;
}
a {
color: inherit;
}
+11
View File
@@ -0,0 +1,11 @@
import './globals.css'
import type { ReactNode } from 'react'
export default function FrontendLayout({ children }: { children: ReactNode }) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}
+65
View File
@@ -0,0 +1,65 @@
export default function CmsHomePage() {
return (
<main
style={{
display: 'grid',
gap: '1rem',
maxWidth: '720px',
margin: '0 auto',
minHeight: '100vh',
alignContent: 'center',
padding: '2rem',
}}
>
<span
style={{
width: 'fit-content',
border: '1px solid rgba(255,255,255,0.15)',
borderRadius: '999px',
padding: '0.35rem 0.75rem',
fontSize: '0.875rem',
}}
>
Payload CMS
</span>
<h1 style={{ fontSize: '3rem', lineHeight: 1.05, margin: 0 }}>
Standalone CMS app inside a Turborepo workspace.
</h1>
<p
style={{
color: 'rgba(243,244,246,0.72)',
fontSize: '1.125rem',
margin: 0,
}}
>
Use this app for admin, content APIs, migrations, and generated types.
Keep the public frontend deployed independently in `apps/web`.
</p>
<div style={{ display: 'flex', gap: '0.75rem', flexWrap: 'wrap' }}>
<a
href="/admin"
style={{
background: '#f3f4f6',
color: '#0b1020',
borderRadius: '999px',
padding: '0.75rem 1rem',
textDecoration: 'none',
}}
>
Open Admin
</a>
<a
href="/api/pages"
style={{
border: '1px solid rgba(255,255,255,0.15)',
borderRadius: '999px',
padding: '0.75rem 1rem',
textDecoration: 'none',
}}
>
REST API
</a>
</div>
</main>
)
}
@@ -0,0 +1,14 @@
import { NotFoundPage } from '@payloadcms/next/views'
import config from '@payload-config'
import { importMap } from '../importMap'
export default function NotFound({
params,
searchParams,
}: {
params: Promise<{ segments: string[] }>
searchParams: Promise<{ [key: string]: string | string[] }>
}) {
return NotFoundPage({ config, importMap, params, searchParams })
}
@@ -0,0 +1,22 @@
import { generatePageMetadata, RootPage } from '@payloadcms/next/views'
import config from '@payload-config'
import { importMap } from '../importMap'
export const generateMetadata = async ({
params,
searchParams,
}: {
params: Promise<{ segments: string[] }>
searchParams: Promise<{ [key: string]: string | string[] }>
}) => generatePageMetadata({ config, params, searchParams })
export default function Page({
params,
searchParams,
}: {
params: Promise<{ segments: string[] }>
searchParams: Promise<{ [key: string]: string | string[] }>
}) {
return RootPage({ config, importMap, params, searchParams })
}
@@ -0,0 +1,2 @@
/** @type {import('payload').ImportMap} */
export const importMap = {}
@@ -0,0 +1,17 @@
import {
REST_DELETE,
REST_GET,
REST_OPTIONS,
REST_PATCH,
REST_POST,
REST_PUT,
} from '@payloadcms/next/routes'
import config from '@payload-config'
export const GET = REST_GET(config)
export const POST = REST_POST(config)
export const DELETE = REST_DELETE(config)
export const PATCH = REST_PATCH(config)
export const PUT = REST_PUT(config)
export const OPTIONS = REST_OPTIONS(config)
+37
View File
@@ -0,0 +1,37 @@
import type { ReactNode } from 'react'
import {
RootLayout,
handleServerFunctions,
metadata,
} from '@payloadcms/next/layouts'
import config from '@payload-config'
import { importMap } from './admin/importMap'
export { metadata }
const serverFunction = async (
...args: Parameters<typeof handleServerFunctions>
) => {
'use server'
const [payloadArgs] = args
return handleServerFunctions({
...payloadArgs,
config,
importMap,
})
}
export default function Layout({ children }: { children: ReactNode }) {
return (
<RootLayout
config={config}
importMap={importMap}
serverFunction={serverFunction}
>
{children}
</RootLayout>
)
}
+30
View File
@@ -0,0 +1,30 @@
import type { CollectionConfig } from 'payload'
export const Pages: CollectionConfig = {
slug: 'pages',
admin: {
defaultColumns: ['title', 'slug', 'updatedAt'],
useAsTitle: 'title',
},
fields: [
{
name: 'title',
type: 'text',
required: true,
},
{
name: 'slug',
type: 'text',
index: true,
required: true,
unique: true,
},
{
name: 'content',
type: 'richText',
},
],
versions: {
drafts: true,
},
}
+15
View File
@@ -0,0 +1,15 @@
import type { CollectionConfig } from 'payload'
export const Users: CollectionConfig = {
slug: 'users',
admin: {
useAsTitle: 'email',
},
auth: true,
fields: [
{
name: 'name',
type: 'text',
},
],
}
+16
View File
@@ -0,0 +1,16 @@
import type { GlobalConfig } from 'payload'
export const SiteSettings: GlobalConfig = {
slug: 'siteSettings',
fields: [
{
name: 'siteName',
type: 'text',
required: true,
},
{
name: 'siteDescription',
type: 'textarea',
},
],
}
+25
View File
@@ -0,0 +1,25 @@
{
"extends": "../../packages/config/typescript/next.json",
"compilerOptions": {
"paths": {
"@/*": ["./src/*"],
"@payload-config": ["./payload.config.ts"]
},
"plugins": [
{
"name": "next"
}
],
"strictNullChecks": true
},
"include": [
"next-env.d.ts",
".next/types/**/*.ts",
".next/dev/types/**/*.ts",
"**/*.ts",
"**/*.tsx",
"**/*.mjs",
"**/*.json"
],
"exclude": ["node_modules"]
}
+51
View File
@@ -0,0 +1,51 @@
import '@/css/tailwind.css'
import Header from '@/components/site-headaer'
import Footer from '@/components/site-footer'
import { themeInitScript } from '@/utils/theme'
import { NextIntlClientProvider } from 'next-intl'
import LocaleSwitcher from '@/components/locale/locale-switcher'
export default async function RootLayout({
children,
params,
}: {
children: React.ReactNode
params: Promise<{ locale: string }>
}) {
const { locale } = await params
return (
<NextIntlClientProvider>
<html lang={locale} className={`scroll-smooth`} suppressHydrationWarning>
<head>
<link rel="apple-touch-icon" sizes="76x76" href="/favicon.ico" />
<link rel="icon" href="/favicon.ico" type="image/x-icon" />
<meta name="msapplication-TileColor" content="#000000" />
<meta
name="theme-color"
media="(prefers-color-scheme: light)"
content="#fff"
/>
<meta
name="theme-color"
media="(prefers-color-scheme: dark)"
content="#000"
/>
<script
dangerouslySetInnerHTML={{
__html: themeInitScript,
}}
/>
</head>
<body>
<Header />
<main>{children}</main>
<LocaleSwitcher />
<Footer />
</body>
</html>
</NextIntlClientProvider>
)
}
+7
View File
@@ -0,0 +1,7 @@
export default function NotFound() {
return (
<div className="flex items-center justify-center py-20">
<h1 className="text-4xl font-light text-gray-500">404 | Not Found</h1>
</div>
)
}
+40
View File
@@ -0,0 +1,40 @@
import ThemeToggle from '@/components/theme-toggle'
import { ROUTES } from '@/constants/routes'
import { createDefaultMetadata } from '@/utils/metadata'
import { useTranslations } from 'next-intl'
export async function generateMetadata({
params,
}: {
params: Promise<{ locale: string }>
}) {
const { locale } = await params
const metadata = await createDefaultMetadata({
title: 'Home',
description: 'Home Description',
locale,
path: ROUTES.home,
})
return metadata
}
export default function Page() {
const t = useTranslations('home')
return (
<div className="divide-y divide-gray-200 dark:divide-gray-700">
<div className="space-y-2 pt-6 pb-8 md:space-y-5">
<h1 className="text-3xl leading-9 font-extrabold tracking-tight text-gray-900 sm:text-4xl sm:leading-10 md:text-6xl md:leading-14 dark:text-gray-100">
{t('title')}
</h1>
<br />
<p className="text-lg leading-7 text-gray-500 dark:text-gray-400">
Description
</p>
<br />
<ThemeToggle />
</div>
</div>
)
}
+15
View File
@@ -0,0 +1,15 @@
import { MetadataRoute } from 'next'
import siteConfig from '@/data/siteConfig'
export const dynamic = 'force-static'
export default function robots(): MetadataRoute.Robots {
return {
rules: {
userAgent: '*',
allow: '/',
},
sitemap: `${siteConfig.url}/sitemap.xml`,
host: siteConfig.url,
}
}
+38
View File
@@ -0,0 +1,38 @@
import ThemeToggle from '@/components/theme-toggle'
import { ROUTES } from '@/constants/routes'
import { createDefaultMetadata } from '@/utils/metadata'
export async function generateMetadata({
params,
}: {
params: Promise<{ locale: string }>
}) {
const { locale } = await params
const metadata = await createDefaultMetadata({
title: 'Test',
description: 'Test page description ',
locale,
path: ROUTES.test,
})
return metadata
}
export default async function Page() {
return (
<div className="divide-y divide-gray-200 dark:divide-gray-700">
<div className="space-y-2 pt-6 pb-8 md:space-y-5">
<h1 className="text-3xl leading-9 font-extrabold tracking-tight text-gray-900 sm:text-4xl sm:leading-10 md:text-6xl md:leading-14 dark:text-gray-100">
Test Page
</h1>
<br />
<p className="text-lg leading-7 text-gray-500 dark:text-gray-400">
Description
</p>
<br />
<ThemeToggle />
</div>
</div>
)
}
+48
View File
@@ -0,0 +1,48 @@
import { ImageResponse } from 'next/og'
export async function GET(request: Request) {
const { searchParams } = new URL(request.url)
const title = searchParams.get('title')
const description = searchParams.get('description')
const siteConfig = await import('@/data/siteConfig')
const { name } = siteConfig.default
return new ImageResponse(
<div tw="flex w-full h-full text-black bg-white">
{(title || description) && (
<div tw="flex absolute right-24 bottom-24 flex-row justify-center items-center text-white">
<div tw="text-black flex text-[32px] font-semibold tracking-tight ml-2">
{name}
</div>
</div>
)}
<div tw="flex absolute inset-0 flex-col justify-center items-center p-24 w-full h-full">
{title || description ? (
<div tw="flex flex-col justify-center items-center w-full h-full text-center">
<div tw="tracking-tight flex flex-col justify-center text-black text-balance font-semibold text-[80px]">
{title}
</div>
<div tw="text-[40px] text-gray-600 mt-6 text-balance font-normal">
{description}
</div>
</div>
) : (
<div tw="flex flex-col justify-center items-center w-full h-full text-center">
<div tw="flex flex-row justify-center items-center space-x-4"></div>
<div tw="text-black flex text-[80px] font-semibold tracking-tight">
{name}
</div>
<div tw="flex text-2xl text-gray-600">
<p>Logos Next Tailwind Template</p>
</div>
</div>
)}
</div>
</div>,
{
width: 1200,
height: 628,
}
)
}
+15
View File
@@ -0,0 +1,15 @@
import { MetadataRoute } from 'next'
import siteConfig from '@/data/siteConfig'
export const dynamic = 'force-static'
export default function sitemap(): MetadataRoute.Sitemap {
const siteUrl = siteConfig.url
const routes = [''].map((route) => ({
url: `${siteUrl}/${route}`,
lastModified: new Date().toISOString().split('T')[0],
}))
return [...routes]
}
@@ -0,0 +1,60 @@
'use client'
import { ChangeEvent, ReactNode, useTransition } from 'react'
import { usePathname } from '@/i18n/navigation'
import clsx from 'clsx'
import { Locale } from 'next-intl'
type Props = {
children: ReactNode
defaultValue: string
label: string
}
export default function LocaleSwitcherSelect({
children,
defaultValue,
}: Props) {
const [isPending, startTransition] = useTransition()
const pathname = usePathname()
function onSelectChange(event: ChangeEvent<HTMLSelectElement>) {
const nextLocale = event.target.value as Locale
startTransition(() => {
window.location.pathname = `/${nextLocale}${pathname}`
})
}
return (
<div className="relative inline-block w-24">
<select
className={clsx(
'text-foreground w-full appearance-none border border-black px-3 py-1 text-sm dark:border-white',
'cursor-pointer',
isPending && 'transition-opacity [&:disabled]:opacity-30'
)}
defaultValue={defaultValue}
disabled={isPending}
onChange={onSelectChange}
>
<optgroup label="Language">{children}</optgroup>
</select>
<span className="pointer-events-none absolute top-1/2 right-2 -translate-y-1/2 text-gray-500">
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-4 w-4"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path
fillRule="evenodd"
d="M5.23 7.21a.75.75 0 011.06 0L10 10.44l3.71-3.23a.75.75 0 111.06 1.06l-4 3.5a.75.75 0 01-1.06 0l-4-3.5z"
clipRule="evenodd"
/>
</svg>
</span>
</div>
)
}
@@ -0,0 +1,19 @@
import { routing } from '@/i18n/routing'
import { useLocale, useTranslations } from 'next-intl'
import LocaleSwitcherSelect from './locale-switcher-select'
export default function LocaleSwitcher() {
const t = useTranslations('locale')
const locale = useLocale()
return (
<LocaleSwitcherSelect defaultValue={locale} label={t('language')}>
{routing.locales.map((cur) => (
<option key={cur} value={cur}>
{t(cur, { locale: cur })}
</option>
))}
</LocaleSwitcherSelect>
)
}
+3
View File
@@ -0,0 +1,3 @@
export default function Footer() {
return <footer>Footer</footer>
}
+9
View File
@@ -0,0 +1,9 @@
const Header = () => {
return (
<header>
<span>Header</span>
</header>
)
}
export default Header
+28
View File
@@ -0,0 +1,28 @@
'use client'
import { useEffect, useState } from 'react'
export default function ThemeToggle() {
const [theme, setTheme] = useState<'light' | 'dark'>('light')
useEffect(() => {
const savedTheme = localStorage.getItem('theme') as 'light' | 'dark'
const systemTheme = window.matchMedia('(prefers-color-scheme: dark)')
.matches
? 'dark'
: 'light'
const initialTheme = savedTheme || systemTheme
setTheme(initialTheme)
document.documentElement.setAttribute('data-theme', initialTheme)
}, [])
const toggleTheme = () => {
const newTheme = theme === 'dark' ? 'light' : 'dark'
setTheme(newTheme)
document.documentElement.setAttribute('data-theme', newTheme)
localStorage.setItem('theme', newTheme)
}
return <button onClick={toggleTheme}>Theme Toggle</button>
}
+4
View File
@@ -0,0 +1,4 @@
export const ROUTES = {
home: '/',
test: '/test',
}
+89
View File
@@ -0,0 +1,89 @@
@import 'tailwindcss';
/* Core theme configuration */
@theme {
/* Font families */
--font-sans:
var(--font-space-grotesk), ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji',
'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
/* Colors */
/* Copied from https://tailwindcss.com/docs/theme#default-theme-variable-reference */
--color-primary-50: oklch(0.971 0.014 343.198);
--color-primary-100: oklch(0.948 0.028 342.258);
--color-primary-200: oklch(0.899 0.061 343.231);
--color-primary-300: oklch(0.823 0.12 346.018);
--color-primary-400: oklch(0.718 0.202 349.761);
--color-primary-500: oklch(0.656 0.241 354.308);
--color-primary-600: oklch(0.592 0.249 0.584);
--color-primary-700: oklch(0.525 0.223 3.958);
--color-primary-800: oklch(0.459 0.187 3.815);
--color-primary-900: oklch(0.408 0.153 2.432);
--color-primary-950: oklch(0.284 0.109 3.907);
--color-gray-50: oklch(0.985 0.002 247.839);
--color-gray-100: oklch(0.967 0.003 264.542);
--color-gray-200: oklch(0.928 0.006 264.531);
--color-gray-300: oklch(0.872 0.01 258.338);
--color-gray-400: oklch(0.707 0.022 261.325);
--color-gray-500: oklch(0.551 0.027 264.364);
--color-gray-600: oklch(0.446 0.03 256.802);
--color-gray-700: oklch(0.373 0.034 259.733);
--color-gray-800: oklch(0.278 0.033 256.848);
--color-gray-900: oklch(0.21 0.034 264.665);
--color-gray-950: oklch(0.13 0.028 261.692);
/* Line heights */
--line-height-11: 2.75rem;
--line-height-12: 3rem;
--line-height-13: 3.25rem;
--line-height-14: 3.5rem;
/* Z-index values */
--z-60: 60;
--z-70: 70;
--z-80: 80;
}
/*
The default border color has changed to `currentColor` in Tailwind CSS v4,
so we've added these compatibility styles to make sure everything still
looks the same as it did with Tailwind CSS v3.
If we ever want to remove these styles, we need to add an explicit border
color utility to any element that depends on these defaults.
*/
@layer base {
*,
::after,
::before,
::backdrop,
::file-selector-button {
border-color: var(--color-gray-200, currentColor);
}
a,
button {
outline-color: var(--color-primary-500);
}
a:focus-visible,
button:focus-visible {
outline: 2px solid;
border-radius: var(--radius-sm);
outline-color: var(--color-primary-500);
}
}
html {
scroll-behavior: smooth;
-webkit-font-smoothing: antialiased;
touch-action: manipulation;
text-rendering: optimizelegibility;
}
body {
padding: 20px;
background-color: rgb(var(--lsd-theme-secondary));
}
+19
View File
@@ -0,0 +1,19 @@
type SiteConfig = {
name: string
title: string
description: string
url: string
defaultLocale: string
keywords: string[]
}
const siteConfig: SiteConfig = {
name: 'Logos',
title: 'Logos Next Tailwind Template',
description: 'Template for Next.js, Tailwind CSS, and Acid Info LSD',
url: 'https://logos.co',
keywords: ['Logos', 'Web3'],
defaultLocale: 'en',
}
export default siteConfig
+3
View File
@@ -0,0 +1,3 @@
import nextConfig from '../../packages/config/eslint/next.mjs'
export default nextConfig
+29
View File
@@ -0,0 +1,29 @@
import { useEffect, useState } from 'react'
function useWindowSize() {
const [windowSize, setWindowSize] = useState({
width: 0,
height: 0,
})
useEffect(() => {
function handleResize() {
setWindowSize({
width: window.innerWidth,
height: window.innerHeight,
})
}
window.addEventListener('resize', handleResize)
handleResize()
return () => window.removeEventListener('resize', handleResize)
}, [])
const { width, height } = windowSize
return {
width,
height,
}
}
export default useWindowSize
+7
View File
@@ -0,0 +1,7 @@
import { createNavigation } from 'next-intl/navigation'
import { routing } from './routing'
// Lightweight wrappers around Next.js' navigation
// APIs that consider the routing configuration
export const { Link, redirect, usePathname, useRouter, getPathname } =
createNavigation(routing)
+18
View File
@@ -0,0 +1,18 @@
import { getRequestConfig } from 'next-intl/server'
import { hasLocale } from 'next-intl'
import { routing } from './routing'
// docs: https://next-intl.dev/docs/getting-started/app-router/with-i18n-routing#i18n-request
export default getRequestConfig(async ({ requestLocale }) => {
// Typically corresponds to the `[locale]` segment
const requested = await requestLocale
const locale = hasLocale(routing.locales, requested)
? requested
: routing.defaultLocale
return {
locale,
messages: (await import(`../messages/${locale}.json`)).default,
}
})
+11
View File
@@ -0,0 +1,11 @@
import { defineRouting } from 'next-intl/routing'
export const routing = defineRouting({
// A list of all locales that are supported
locales: ['en', 'fr', 'ko'],
// Used when no locale matches
defaultLocale: 'en',
localePrefix: 'as-needed',
localeDetection: false,
})
+14
View File
@@ -0,0 +1,14 @@
{
"locale": { "language": "Language", "en": "English", "fr": "Français", "ko": "한국어" },
"common": {
"nav": {
"resources": "Resources",
"contributors": "Contributors",
"miilestones": "Milestones"
}
},
"home": {
"title": "main"
},
"footer": {}
}
+14
View File
@@ -0,0 +1,14 @@
{
"locale": { "language": "Langue", "en": "English", "fr": "Français", "ko": "한국어" },
"common": {
"nav": {
"resources": "Ressources",
"contributors": "Contributeurs",
"milestones": "Jalons"
}
},
"home": {
"title": "main"
},
"footer": {}
}
+14
View File
@@ -0,0 +1,14 @@
{
"locale": { "language": "언어", "en": "English", "fr": "Français", "ko": "한국어" },
"common": {
"nav": {
"resources": "리소스",
"contributors": "기여자",
"milestones": "마일스톤"
}
},
"home": {
"title": "메인"
},
"footer": {}
}
+57
View File
@@ -0,0 +1,57 @@
import { fileURLToPath } from 'node:url'
import createNextIntlPlugin from 'next-intl/plugin'
const withNextIntl = createNextIntlPlugin()
const workspaceRoot = fileURLToPath(new URL('../..', import.meta.url))
const securityHeaders = [
{
key: 'Referrer-Policy',
value: 'strict-origin-when-cross-origin',
},
{
key: 'X-Frame-Options',
value: 'DENY',
},
{
key: 'X-Content-Type-Options',
value: 'nosniff',
},
{
key: 'X-DNS-Prefetch-Control',
value: 'on',
},
{
key: 'Strict-Transport-Security',
value: 'max-age=31536000; includeSubDomains',
},
{
key: 'Permissions-Policy',
value: 'camera=(), microphone=(), geolocation=()',
},
]
const nextConfig = {
basePath: process.env.BASE_PATH || undefined,
images: {
unoptimized: Boolean(process.env.UNOPTIMIZED),
},
output: process.env.EXPORT ? 'export' : undefined,
reactStrictMode: true,
transpilePackages: ['@repo/ui'],
trailingSlash: false,
turbopack: {
root: workspaceRoot,
},
async headers() {
return [
{
source: '/(.*)',
headers: securityHeaders,
},
]
},
}
export default withNextIntl(nextConfig)
+31
View File
@@ -0,0 +1,31 @@
{
"name": "web",
"type": "module",
"private": true,
"scripts": {
"dev": "next dev --port 3000",
"build": "next build",
"start": "next start",
"lint": "eslint . --max-warnings 0",
"lint:fix": "eslint . --fix",
"check-types": "next typegen && tsc --noEmit"
},
"dependencies": {
"@repo/ui": "workspace:*",
"clsx": "^2.1.1",
"next": "16.2.4",
"next-intl": "^4.9.1",
"react": "^19.2.5",
"react-dom": "^19.2.5"
},
"devDependencies": {
"@tailwindcss/postcss": "^4.2.2",
"@types/node": "25.6.0",
"@types/react": "^19.2.14",
"@types/react-dom": "19.2.3",
"eslint": "^10.2.1",
"postcss": "^8.5.10",
"tailwindcss": "^4.2.2",
"typescript": "^6.0.3"
}
}
+5
View File
@@ -0,0 +1,5 @@
export default {
plugins: {
'@tailwindcss/postcss': {},
},
}
+13
View File
@@ -0,0 +1,13 @@
import createMiddleware from 'next-intl/middleware'
import { routing } from './i18n/routing'
const intlMiddleware = createMiddleware(routing)
export function proxy(request: import('next/server').NextRequest) {
return intlMiddleware(request)
}
export const config = {
matcher: ['/((?!_next|api|.*\\..*).*)'],
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 B

+3
View File
@@ -0,0 +1,3 @@
<svg width="93" height="126" viewBox="0 0 93 126" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M71.2154 126C66.7864 126 62.9754 124.958 59.7824 122.873C56.5894 120.789 54.1688 117.506 52.5208 113.025C51.4908 110.002 50.7698 106.303 50.3578 101.926C49.9458 97.5484 49.6883 92.9628 49.5853 88.1687C49.4823 83.2705 49.3793 78.5285 49.2763 73.9429C49.2763 72.6923 48.9673 72.067 48.3493 72.067C47.8343 72.067 47.3193 72.4839 46.8043 73.3176C44.8473 76.9653 42.5298 81.134 39.8518 85.8238C37.2768 90.5136 34.7018 95.2035 32.1268 99.8933C29.5517 104.583 27.2342 108.804 25.1742 112.556C23.2172 116.203 21.8267 118.861 21.0027 120.529C19.9727 122.821 18.3247 124.28 16.0587 124.906C13.8957 125.635 11.1662 125.792 7.87017 125.375C4.57415 124.958 2.25664 123.655 0.91764 121.466C-0.524366 119.174 -0.266865 116.777 1.69014 114.275C3.02915 112.608 4.93466 110.107 7.40666 106.772C9.87867 103.333 12.6597 99.4764 15.7497 95.2035C18.8397 90.8263 21.9812 86.3449 25.1742 81.7593C28.4702 77.0695 31.5603 72.5881 34.4443 68.3151C37.3283 63.938 39.7488 60.134 41.7058 56.9032C43.7658 53.5682 45.1048 51.1191 45.7228 49.5558C46.3408 48.0968 47.0103 46.4814 47.7313 44.7097C48.4523 42.938 48.8128 41.1141 48.8128 39.2382C48.8128 32.3598 48.1433 27.201 46.8043 23.7618C45.5683 20.2184 43.8688 17.8734 41.7058 16.727C39.6458 15.4764 37.3798 14.8511 34.9078 14.8511C33.0538 14.8511 31.0453 15.2159 28.8822 15.9454C26.8222 16.6749 25.4832 17.3524 24.8652 17.9777C23.7322 19.1241 22.6507 19.3325 21.6207 18.603C20.5907 17.8734 20.2817 16.6749 20.6937 15.0074C21.8267 11.1514 23.8867 7.71216 26.8737 4.68982C29.8608 1.56327 34.0323 0 39.3883 0C45.0533 0 49.5338 1.61539 52.8298 4.84616C56.1259 8.07692 58.4949 13.2357 59.9369 20.3226C61.3789 27.4094 62.0999 36.7891 62.0999 48.4615C62.0999 62.2184 62.3059 73.2134 62.7179 81.4466C63.1299 89.6799 63.7994 95.8809 64.7264 100.05C65.6534 104.114 66.9409 106.824 68.5889 108.179C70.3399 109.534 72.5029 110.211 75.0779 110.211C77.5499 110.211 79.8674 109.69 82.0305 108.648C84.2965 107.605 86.2535 106.251 87.9015 104.583C88.6225 103.749 89.4465 103.437 90.3735 103.645C91.3005 103.854 92.0215 104.427 92.5365 105.365C93.1545 106.199 93.1545 107.293 92.5365 108.648C90.3735 113.442 87.4895 117.558 83.8845 120.998C80.3824 124.333 76.1594 126 71.2154 126Z" fill="#000"/>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

+28
View File
@@ -0,0 +1,28 @@
{
"extends": "../../packages/config/typescript/next.json",
"compilerOptions": {
"paths": {
"@/*": ["./*"],
"@/components/*": ["./components/*"],
"@/data/*": ["./data/*"],
"@/css/*": ["./css/*"]
},
"plugins": [
{
"name": "next"
}
],
"strictNullChecks": true
},
"include": [
"next-env.d.ts",
".next/types/**/*.ts",
".next/dev/types/**/*.ts",
"**/*.ts",
"**/*.tsx",
"**/*.mjs",
"**/*.json",
"proxy.ts"
],
"exclude": ["node_modules"]
}
+72
View File
@@ -0,0 +1,72 @@
import siteConfig from '@/data/siteConfig'
import { Metadata } from 'next'
type DefaultMetadataProps = {
locale: string
title?: string
description?: string
path?: string
}
const baseUrl = siteConfig.url
export function absoluteUrl(
path: string,
locale: string = siteConfig.defaultLocale
) {
return `${siteConfig.url}${locale === siteConfig.defaultLocale ? '' : `/${locale}`}${path}`
}
export async function createDefaultMetadata({
title = '',
description = '',
locale,
path = '',
}: DefaultMetadataProps): Promise<Metadata> {
const _title = title || siteConfig.title
const _description = description || siteConfig.description
const fullUrl = absoluteUrl(path)
return {
title: _title,
description: _description,
metadataBase: new URL(baseUrl),
alternates: {
canonical: fullUrl,
languages: {
en: fullUrl,
'x-default': fullUrl,
},
},
openGraph: {
title: _title,
description: _description,
url: fullUrl,
type: 'website',
locale,
siteName: siteConfig.name,
images: [
{
url: absoluteUrl('/og'),
width: 1200,
height: 630,
alt: title,
},
],
},
twitter: {
card: 'summary_large_image',
title,
description,
images: [absoluteUrl('/og')],
},
icons: '/favicon.ico',
creator: siteConfig.name,
keywords: siteConfig.keywords,
robots: {
index: process.env.NEXT_PUBLIC_API_MODE === 'production',
follow: true,
},
}
}
+10
View File
@@ -0,0 +1,10 @@
export const themeInitScript = `
(function() {
try {
var theme = localStorage.getItem('theme');
var systemTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
var initialTheme = theme || systemTheme;
document.documentElement.setAttribute('data-theme', initialTheme);
} catch (e) {}
})();
`
+30
View File
@@ -0,0 +1,30 @@
{
"name": "logos-turborepo-next-tailwind-i18n-template",
"private": true,
"scripts": {
"build": "turbo run build",
"dev": "turbo run dev",
"lint": "turbo run lint",
"lint:fix": "turbo run lint:fix",
"format": "prettier --write \"**/*.{js,mjs,ts,tsx,json,md,css}\"",
"check-types": "turbo run check-types",
"generate-types": "turbo run generate-types --filter=cms"
},
"devDependencies": {
"prettier": "^3.8.3",
"turbo": "^2.9.6",
"typescript": "^6.0.3"
},
"pnpm": {
"onlyBuiltDependencies": [
"@parcel/watcher",
"@swc/core",
"esbuild",
"sharp"
]
},
"packageManager": "pnpm@10.9.0",
"engines": {
"node": ">=20.9.0"
}
}
+41
View File
@@ -0,0 +1,41 @@
import js from '@eslint/js'
import prettier from 'eslint-config-prettier'
import prettierPlugin from 'eslint-plugin-prettier'
import globals from 'globals'
import tseslint from 'typescript-eslint'
export default [
{
ignores: ['**/.next/**', '**/dist/**', '**/node_modules/**'],
},
js.configs.recommended,
...tseslint.configs.recommended,
prettier,
{
files: ['**/*.{js,mjs,ts,tsx}'],
plugins: {
prettier: prettierPlugin,
},
languageOptions: {
globals: {
...globals.browser,
...globals.node,
},
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
},
rules: {
'@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
'prettier/prettier': 'warn',
},
},
{
files: ['**/*.js', '**/*.mjs'],
rules: {
'@typescript-eslint/no-require-imports': 'off',
},
},
]
+3
View File
@@ -0,0 +1,3 @@
import baseConfig from './base.mjs'
export default baseConfig
+17
View File
@@ -0,0 +1,17 @@
{
"name": "@repo/config",
"private": true,
"type": "module",
"exports": {
"./eslint/base": "./eslint/base.mjs",
"./eslint/next": "./eslint/next.mjs",
"./prettier": "./prettier/index.mjs"
},
"dependencies": {
"@eslint/js": "^10.0.1",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.5.5",
"globals": "^16.4.0",
"typescript-eslint": "^8.58.2"
}
}
+5
View File
@@ -0,0 +1,5 @@
export default {
semi: false,
singleQuote: true,
trailingComma: 'es5',
}
+19
View File
@@ -0,0 +1,19 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"allowJs": true,
"declaration": false,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"incremental": true,
"isolatedModules": true,
"module": "ESNext",
"moduleResolution": "Bundler",
"noEmit": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"strict": false,
"strictNullChecks": true,
"target": "ES2022"
}
}
+8
View File
@@ -0,0 +1,8 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "./base.json",
"compilerOptions": {
"lib": ["dom", "dom.iterable", "es2022"],
"jsx": "preserve"
}
}
@@ -0,0 +1,8 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "./base.json",
"compilerOptions": {
"jsx": "react-jsx",
"lib": ["dom", "dom.iterable", "es2022"]
}
}
+19
View File
@@ -0,0 +1,19 @@
{
"name": "@repo/types",
"private": true,
"type": "module",
"exports": {
".": "./src/index.ts",
"./payload": "./src/payload.ts"
},
"scripts": {
"lint": "eslint . --config ../../packages/config/eslint/base.mjs --max-warnings 0",
"lint:fix": "eslint . --config ../../packages/config/eslint/base.mjs --fix",
"check-types": "tsc --noEmit"
},
"devDependencies": {
"@types/node": "25.6.0",
"eslint": "^10.2.1",
"typescript": "^6.0.3"
}
}
+1
View File
@@ -0,0 +1 @@
export * from './payload'
+364
View File
@@ -0,0 +1,364 @@
/* tslint:disable */
/* eslint-disable */
/**
* This file was automatically generated by Payload.
* DO NOT MODIFY IT BY HAND. Instead, modify your source Payload config,
* and re-run `payload generate:types` to regenerate this file.
*/
/**
* Supported timezones in IANA format.
*
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "supportedTimezones".
*/
export type SupportedTimezones =
| 'Pacific/Midway'
| 'Pacific/Niue'
| 'Pacific/Honolulu'
| 'Pacific/Rarotonga'
| 'America/Anchorage'
| 'Pacific/Gambier'
| 'America/Los_Angeles'
| 'America/Tijuana'
| 'America/Denver'
| 'America/Phoenix'
| 'America/Chicago'
| 'America/Guatemala'
| 'America/New_York'
| 'America/Bogota'
| 'America/Caracas'
| 'America/Santiago'
| 'America/Buenos_Aires'
| 'America/Sao_Paulo'
| 'Atlantic/South_Georgia'
| 'Atlantic/Azores'
| 'Atlantic/Cape_Verde'
| 'Europe/London'
| 'Europe/Berlin'
| 'Africa/Lagos'
| 'Europe/Athens'
| 'Africa/Cairo'
| 'Europe/Moscow'
| 'Asia/Riyadh'
| 'Asia/Dubai'
| 'Asia/Baku'
| 'Asia/Karachi'
| 'Asia/Tashkent'
| 'Asia/Calcutta'
| 'Asia/Dhaka'
| 'Asia/Almaty'
| 'Asia/Jakarta'
| 'Asia/Bangkok'
| 'Asia/Shanghai'
| 'Asia/Singapore'
| 'Asia/Tokyo'
| 'Asia/Seoul'
| 'Australia/Brisbane'
| 'Australia/Sydney'
| 'Pacific/Guam'
| 'Pacific/Noumea'
| 'Pacific/Auckland'
| 'Pacific/Fiji';
export interface Config {
auth: {
users: UserAuthOperations;
};
blocks: {};
collections: {
users: User;
pages: Page;
'payload-kv': PayloadKv;
'payload-locked-documents': PayloadLockedDocument;
'payload-preferences': PayloadPreference;
'payload-migrations': PayloadMigration;
};
collectionsJoins: {};
collectionsSelect: {
users: UsersSelect<false> | UsersSelect<true>;
pages: PagesSelect<false> | PagesSelect<true>;
'payload-kv': PayloadKvSelect<false> | PayloadKvSelect<true>;
'payload-locked-documents': PayloadLockedDocumentsSelect<false> | PayloadLockedDocumentsSelect<true>;
'payload-preferences': PayloadPreferencesSelect<false> | PayloadPreferencesSelect<true>;
'payload-migrations': PayloadMigrationsSelect<false> | PayloadMigrationsSelect<true>;
};
db: {
defaultIDType: number;
};
fallbackLocale: null;
globals: {
siteSettings: SiteSetting;
};
globalsSelect: {
siteSettings: SiteSettingsSelect<false> | SiteSettingsSelect<true>;
};
locale: null;
widgets: {
collections: CollectionsWidget;
};
user: User;
jobs: {
tasks: unknown;
workflows: unknown;
};
}
export interface UserAuthOperations {
forgotPassword: {
email: string;
password: string;
};
login: {
email: string;
password: string;
};
registerFirstUser: {
email: string;
password: string;
};
unlock: {
email: string;
password: string;
};
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "users".
*/
export interface User {
id: number;
name?: string | null;
updatedAt: string;
createdAt: string;
email: string;
resetPasswordToken?: string | null;
resetPasswordExpiration?: string | null;
salt?: string | null;
hash?: string | null;
loginAttempts?: number | null;
lockUntil?: string | null;
sessions?:
| {
id: string;
createdAt?: string | null;
expiresAt: string;
}[]
| null;
password?: string | null;
collection: 'users';
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "pages".
*/
export interface Page {
id: number;
title: string;
slug: string;
content?: {
root: {
type: string;
children: {
type: any;
version: number;
[k: string]: unknown;
}[];
direction: ('ltr' | 'rtl') | null;
format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | '';
indent: number;
version: number;
};
[k: string]: unknown;
} | null;
updatedAt: string;
createdAt: string;
_status?: ('draft' | 'published') | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-kv".
*/
export interface PayloadKv {
id: number;
key: string;
data:
| {
[k: string]: unknown;
}
| unknown[]
| string
| number
| boolean
| null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-locked-documents".
*/
export interface PayloadLockedDocument {
id: number;
document?:
| ({
relationTo: 'users';
value: number | User;
} | null)
| ({
relationTo: 'pages';
value: number | Page;
} | null);
globalSlug?: string | null;
user: {
relationTo: 'users';
value: number | User;
};
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-preferences".
*/
export interface PayloadPreference {
id: number;
user: {
relationTo: 'users';
value: number | User;
};
key?: string | null;
value?:
| {
[k: string]: unknown;
}
| unknown[]
| string
| number
| boolean
| null;
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-migrations".
*/
export interface PayloadMigration {
id: number;
name?: string | null;
batch?: number | null;
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "users_select".
*/
export interface UsersSelect<T extends boolean = true> {
name?: T;
updatedAt?: T;
createdAt?: T;
email?: T;
resetPasswordToken?: T;
resetPasswordExpiration?: T;
salt?: T;
hash?: T;
loginAttempts?: T;
lockUntil?: T;
sessions?:
| T
| {
id?: T;
createdAt?: T;
expiresAt?: T;
};
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "pages_select".
*/
export interface PagesSelect<T extends boolean = true> {
title?: T;
slug?: T;
content?: T;
updatedAt?: T;
createdAt?: T;
_status?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-kv_select".
*/
export interface PayloadKvSelect<T extends boolean = true> {
key?: T;
data?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-locked-documents_select".
*/
export interface PayloadLockedDocumentsSelect<T extends boolean = true> {
document?: T;
globalSlug?: T;
user?: T;
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-preferences_select".
*/
export interface PayloadPreferencesSelect<T extends boolean = true> {
user?: T;
key?: T;
value?: T;
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-migrations_select".
*/
export interface PayloadMigrationsSelect<T extends boolean = true> {
name?: T;
batch?: T;
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "siteSettings".
*/
export interface SiteSetting {
id: number;
siteName: string;
siteDescription?: string | null;
updatedAt?: string | null;
createdAt?: string | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "siteSettings_select".
*/
export interface SiteSettingsSelect<T extends boolean = true> {
siteName?: T;
siteDescription?: T;
updatedAt?: T;
createdAt?: T;
globalType?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "collections_widget".
*/
export interface CollectionsWidget {
data?: {
[k: string]: unknown;
};
width: 'full';
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "auth".
*/
export interface Auth {
[k: string]: unknown;
}
+5
View File
@@ -0,0 +1,5 @@
{
"extends": "../config/typescript/react-library.json",
"include": ["src"],
"exclude": ["node_modules"]
}
+24
View File
@@ -0,0 +1,24 @@
{
"name": "@repo/ui",
"private": true,
"exports": {
".": "./src/index.ts"
},
"scripts": {
"lint": "eslint . --config ../../packages/config/eslint/base.mjs --max-warnings 0",
"lint:fix": "eslint . --config ../../packages/config/eslint/base.mjs --fix",
"check-types": "tsc --noEmit"
},
"dependencies": {
"clsx": "^2.1.1",
"react": "^19.2.5",
"react-dom": "^19.2.5"
},
"devDependencies": {
"@types/node": "25.6.0",
"@types/react": "^19.2.14",
"@types/react-dom": "19.2.3",
"eslint": "^10.2.1",
"typescript": "^6.0.3"
}
}
+34
View File
@@ -0,0 +1,34 @@
import type { ButtonHTMLAttributes, PropsWithChildren } from 'react'
import { clsx } from 'clsx'
type ButtonProps = PropsWithChildren<
ButtonHTMLAttributes<HTMLButtonElement>
> & {
variant?: 'primary' | 'secondary'
}
export function Button({
children,
className,
type = 'button',
variant = 'primary',
...props
}: ButtonProps) {
return (
<button
className={clsx(
'inline-flex items-center justify-center rounded-full px-5 py-2 text-sm font-medium transition',
variant === 'primary' &&
'bg-black text-white hover:bg-neutral-800 dark:bg-white dark:text-black dark:hover:bg-neutral-200',
variant === 'secondary' &&
'border border-black/10 bg-white text-black hover:border-black/20 hover:bg-neutral-50 dark:border-white/20 dark:bg-transparent dark:text-white dark:hover:border-white/40 dark:hover:bg-white/5',
className
)}
type={type}
{...props}
>
{children}
</button>
)
}
+15
View File
@@ -0,0 +1,15 @@
import type { PropsWithChildren } from 'react'
import { clsx } from 'clsx'
type ContainerProps = PropsWithChildren<{
className?: string
}>
export function Container({ children, className }: ContainerProps) {
return (
<div className={clsx('mx-auto w-full max-w-6xl px-6', className)}>
{children}
</div>
)
}
+2
View File
@@ -0,0 +1,2 @@
export * from './button'
export * from './container'
+9
View File
@@ -0,0 +1,9 @@
{
"extends": "../config/typescript/react-library.json",
"compilerOptions": {
"jsx": "react-jsx",
"strictNullChecks": true
},
"include": ["src"],
"exclude": ["node_modules"]
}
+6231
View File
File diff suppressed because it is too large Load Diff
+3
View File
@@ -0,0 +1,3 @@
packages:
- "apps/*"
- "packages/*"
+1
View File
@@ -0,0 +1 @@
export { default } from './packages/config/prettier/index.mjs'
+28
View File
@@ -0,0 +1,28 @@
{
"$schema": "https://turborepo.dev/schema.json",
"ui": "tui",
"tasks": {
"build": {
"dependsOn": ["^build"],
"inputs": ["$TURBO_DEFAULT$", ".env*"],
"outputs": [".next/**", "!.next/cache/**", "dist/**"]
},
"lint": {
"dependsOn": ["^lint"]
},
"lint:fix": {
"cache": false
},
"check-types": {
"dependsOn": ["^check-types"]
},
"generate-types": {
"cache": false,
"outputs": ["packages/types/src/payload.ts"]
},
"dev": {
"cache": false,
"persistent": true
}
}
}