first commit

This commit is contained in:
2026-06-14 16:10:40 +03:00
parent 8abc26cf2b
commit e2c51d3b9e
16 changed files with 2272 additions and 26 deletions
+9
View File
@@ -0,0 +1,9 @@
.next
node_modules
.env
.env.*.local
.git
.vscode
docs
README.md
AGENTS.md
+41 -4
View File
@@ -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
View File
@@ -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"]
+15
View File
@@ -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
};
});
+10
View File
@@ -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
View File
@@ -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
}
}
})
+20
View File
@@ -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 }
+9
View File
@@ -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
+6
View File
@@ -0,0 +1,6 @@
import { type ClassValue, clsx } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
+14
View File
@@ -0,0 +1,14 @@
{
"nav": {
"home": "Home",
"about": "About Us",
"contact": "Contact"
},
"hero": {
"title": "Welcome",
"cta": "Learn More"
},
"footer": {
"rights": "All rights reserved."
}
}
+14
View File
@@ -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
View File
@@ -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)
+1981 -17
View File
File diff suppressed because it is too large Load Diff
+11 -1
View File
@@ -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"
}
+61
View File
@@ -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])
}
+20
View File
@@ -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|.*\\..*).*)']
}