first commit
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
.next
|
||||
node_modules
|
||||
.env
|
||||
.env.*.local
|
||||
.git
|
||||
.vscode
|
||||
docs
|
||||
README.md
|
||||
AGENTS.md
|
||||
@@ -1,5 +1,42 @@
|
||||
<!-- BEGIN:nextjs-agent-rules -->
|
||||
# This is NOT the Next.js you know
|
||||
# AGENTS.md
|
||||
|
||||
This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` before writing any code. Heed deprecation notices.
|
||||
<!-- END:nextjs-agent-rules -->
|
||||
## Stack
|
||||
- Framework: Next.js 16, App Router, TypeScript strict
|
||||
- Styling: Tailwind CSS v4
|
||||
- UI: shadcn/ui (new-york style, OKLCH)
|
||||
- Animation: Framer Motion
|
||||
- Icons: Lucide React
|
||||
- i18n: next-intl
|
||||
- ORM: Prisma + PostgreSQL
|
||||
- Auth: NextAuth.js v5
|
||||
- Media: Cloudinary
|
||||
- Deploy: Coolify (Docker, standalone output)
|
||||
|
||||
## Sabit Tercihler
|
||||
- Mock data: USE_MOCK=true (demo aşaması)
|
||||
- proxy.ts kullan — middleware.ts deprecated (Next.js 15.3+)
|
||||
- İletişim formu sadece /iletisim sayfasında — ana sayfada olmaz
|
||||
- Footer'da "Created by ayris.tech" linki zorunlu
|
||||
- Dockerfile'da dummy DATABASE_URL (prisma generate için)
|
||||
|
||||
## Altyapı
|
||||
- Gitea: https://git.ayris.tech (kullanıcı: ayrisdev)
|
||||
- Coolify: https://client2.ayris.tech
|
||||
- Cloudflare zone: ayris.tech
|
||||
- Server IP: 188.245.175.169
|
||||
|
||||
## docs/ Klasörü
|
||||
- docs/prd.md → ana içerik kaynağı
|
||||
- docs/*.html → varsa mevcut site içeriği
|
||||
- docs/*.md → ek belgeler
|
||||
|
||||
## Aktif Skill'ler
|
||||
- nextjs-seo → sitemap, metadata, robots.txt
|
||||
- next-best-practices → kod kalitesi
|
||||
- nextjs-app-router-patterns → Server Actions, Suspense
|
||||
- demo-site → komple site üretimi
|
||||
- design-demo → görsel kalite
|
||||
- coolify-deploy → deploy pipeline
|
||||
|
||||
## Proje Özel Notlar
|
||||
<!-- Buraya proje bazlı notlar ekle -->
|
||||
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
FROM node:22-alpine AS base
|
||||
|
||||
FROM base AS deps
|
||||
RUN apk add --no-cache libc6-compat
|
||||
WORKDIR /app
|
||||
COPY package.json package-lock.json* ./
|
||||
RUN npm ci --legacy-peer-deps
|
||||
|
||||
FROM base AS builder
|
||||
WORKDIR /app
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY . .
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
# Prisma generate için dummy URL — build sırasında gerçek DB gerekmez
|
||||
ARG DATABASE_URL=postgresql://dummy:dummy@localhost:5432/dummy
|
||||
ENV DATABASE_URL=$DATABASE_URL
|
||||
RUN npx prisma generate
|
||||
RUN npm run build
|
||||
|
||||
FROM base AS runner
|
||||
WORKDIR /app
|
||||
ENV NODE_ENV=production
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
RUN addgroup --system --gid 1001 nodejs
|
||||
RUN adduser --system --uid 1001 nextjs
|
||||
COPY --from=builder /app/public ./public
|
||||
RUN mkdir .next && chown nextjs:nodejs .next
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
||||
USER nextjs
|
||||
EXPOSE 3000
|
||||
ENV PORT=3000
|
||||
ENV HOSTNAME="0.0.0.0"
|
||||
CMD ["node", "server.js"]
|
||||
@@ -0,0 +1,15 @@
|
||||
import {getRequestConfig} from 'next-intl/server';
|
||||
import {routing} from './routing';
|
||||
|
||||
export default getRequestConfig(async ({requestLocale}) => {
|
||||
let locale = await requestLocale;
|
||||
|
||||
if (!locale || !routing.locales.includes(locale as any)) {
|
||||
locale = routing.defaultLocale;
|
||||
}
|
||||
|
||||
return {
|
||||
locale,
|
||||
messages: (await import(`../messages/${locale}.json`)).default
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
import {defineRouting} from 'next-intl/routing';
|
||||
import {createNavigation} from 'next-intl/navigation';
|
||||
|
||||
export const routing = defineRouting({
|
||||
locales: ['en', 'tr', 'de'],
|
||||
defaultLocale: 'tr'
|
||||
});
|
||||
|
||||
export const {Link, redirect, usePathname, useRouter, getPathname} =
|
||||
createNavigation(routing);
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
import NextAuth from "next-auth"
|
||||
|
||||
export const { handlers, auth, signIn, signOut } = NextAuth({
|
||||
providers: [],
|
||||
callbacks: {
|
||||
async session({ session, user }) {
|
||||
if (session.user) {
|
||||
// Here you would normally fetch the user from the database to attach roles
|
||||
// session.user.role = user.role
|
||||
}
|
||||
return session
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,20 @@
|
||||
import { v2 as cloudinary } from 'cloudinary'
|
||||
|
||||
cloudinary.config({
|
||||
cloud_name: process.env.CLOUDINARY_CLOUD_NAME!,
|
||||
api_key: process.env.CLOUDINARY_API_KEY!,
|
||||
api_secret: process.env.CLOUDINARY_API_SECRET!,
|
||||
})
|
||||
|
||||
export async function uploadImage(file: string, folder: string) {
|
||||
const result = await cloudinary.uploader.upload(file, {
|
||||
folder, transformation: [{ quality: 'auto', fetch_format: 'auto' }],
|
||||
})
|
||||
return { url: result.secure_url, publicId: result.public_id }
|
||||
}
|
||||
|
||||
export async function deleteImage(publicId: string) {
|
||||
await cloudinary.uploader.destroy(publicId)
|
||||
}
|
||||
|
||||
export { cloudinary }
|
||||
@@ -0,0 +1,9 @@
|
||||
import { PrismaClient } from '@prisma/client'
|
||||
|
||||
const globalForPrisma = globalThis as unknown as {
|
||||
prisma: PrismaClient | undefined
|
||||
}
|
||||
|
||||
export const db = globalForPrisma.prisma ?? new PrismaClient()
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = db
|
||||
@@ -0,0 +1,6 @@
|
||||
import { type ClassValue, clsx } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"nav": {
|
||||
"home": "Home",
|
||||
"about": "About Us",
|
||||
"contact": "Contact"
|
||||
},
|
||||
"hero": {
|
||||
"title": "Welcome",
|
||||
"cta": "Learn More"
|
||||
},
|
||||
"footer": {
|
||||
"rights": "All rights reserved."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"nav": {
|
||||
"home": "Ana Sayfa",
|
||||
"about": "Hakkımızda",
|
||||
"contact": "İletişim"
|
||||
},
|
||||
"hero": {
|
||||
"title": "Hoş Geldiniz",
|
||||
"cta": "Daha Fazla Bilgi"
|
||||
},
|
||||
"footer": {
|
||||
"rights": "Tüm hakları saklıdır."
|
||||
}
|
||||
}
|
||||
+13
-4
@@ -1,7 +1,16 @@
|
||||
import type { NextConfig } from "next";
|
||||
import type { NextConfig } from 'next'
|
||||
import createNextIntlPlugin from 'next-intl/plugin'
|
||||
|
||||
const withNextIntl = createNextIntlPlugin('./i18n/request.ts')
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
/* config options here */
|
||||
};
|
||||
output: 'standalone',
|
||||
images: {
|
||||
remotePatterns: [
|
||||
{ protocol: 'https', hostname: 'res.cloudinary.com' },
|
||||
{ protocol: 'https', hostname: 'images.unsplash.com' },
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
export default nextConfig;
|
||||
export default withNextIntl(nextConfig)
|
||||
|
||||
Generated
+1981
-17
File diff suppressed because it is too large
Load Diff
+11
-1
@@ -9,9 +9,18 @@
|
||||
"lint": "eslint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@prisma/client": "^7.8.0",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"cloudinary": "^2.10.0",
|
||||
"clsx": "^2.1.1",
|
||||
"framer-motion": "^12.40.0",
|
||||
"lucide-react": "^1.18.0",
|
||||
"next": "16.2.9",
|
||||
"next-auth": "^5.0.0-beta.31",
|
||||
"next-intl": "^4.13.0",
|
||||
"react": "19.2.4",
|
||||
"react-dom": "19.2.4"
|
||||
"react-dom": "19.2.4",
|
||||
"tailwind-merge": "^3.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/postcss": "^4",
|
||||
@@ -20,6 +29,7 @@
|
||||
"@types/react-dom": "^19",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "16.2.9",
|
||||
"prisma": "^7.8.0",
|
||||
"tailwindcss": "^4",
|
||||
"typescript": "^5"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
enum Role {
|
||||
ADMIN
|
||||
USER
|
||||
}
|
||||
|
||||
model User {
|
||||
id String @id @default(cuid())
|
||||
name String?
|
||||
email String @unique
|
||||
password String?
|
||||
role Role @default(USER)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletedAt DateTime?
|
||||
accounts Account[]
|
||||
sessions Session[]
|
||||
}
|
||||
|
||||
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 VerificationToken {
|
||||
identifier String
|
||||
token String @unique
|
||||
expires DateTime
|
||||
|
||||
@@unique([identifier, token])
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import createMiddleware from 'next-intl/middleware'
|
||||
import { auth } from '@/lib/auth'
|
||||
import { routing } from '@/i18n/routing'
|
||||
|
||||
const intlMiddleware = createMiddleware(routing)
|
||||
|
||||
export async function middleware(request: NextRequest) {
|
||||
if (request.nextUrl.pathname.includes('/admin')) {
|
||||
const session = await auth()
|
||||
if (!session || (session.user as any)?.role !== 'ADMIN') {
|
||||
return NextResponse.redirect(new URL('/login', request.url))
|
||||
}
|
||||
}
|
||||
return intlMiddleware(request)
|
||||
}
|
||||
|
||||
export const config = {
|
||||
matcher: ['/((?!api|_next|_vercel|.*\\..*).*)']
|
||||
}
|
||||
Reference in New Issue
Block a user