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 -->
|
# AGENTS.md
|
||||||
# This is NOT the Next.js you know
|
|
||||||
|
|
||||||
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.
|
## Stack
|
||||||
<!-- END:nextjs-agent-rules -->
|
- 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 = {
|
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"
|
"lint": "eslint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"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": "16.2.9",
|
||||||
|
"next-auth": "^5.0.0-beta.31",
|
||||||
|
"next-intl": "^4.13.0",
|
||||||
"react": "19.2.4",
|
"react": "19.2.4",
|
||||||
"react-dom": "19.2.4"
|
"react-dom": "19.2.4",
|
||||||
|
"tailwind-merge": "^3.6.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/postcss": "^4",
|
"@tailwindcss/postcss": "^4",
|
||||||
@@ -20,6 +29,7 @@
|
|||||||
"@types/react-dom": "^19",
|
"@types/react-dom": "^19",
|
||||||
"eslint": "^9",
|
"eslint": "^9",
|
||||||
"eslint-config-next": "16.2.9",
|
"eslint-config-next": "16.2.9",
|
||||||
|
"prisma": "^7.8.0",
|
||||||
"tailwindcss": "^4",
|
"tailwindcss": "^4",
|
||||||
"typescript": "^5"
|
"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