diff --git a/.env.development b/.env.development
new file mode 100644
index 0000000..c8d2505
--- /dev/null
+++ b/.env.development
@@ -0,0 +1,2 @@
+NEXT_PUBLIC_BASE_PATH=
+NEXT_PUBLIC_API_URL=http://localhost:8004/api-ncr
\ No newline at end of file
diff --git a/.env.production b/.env.production
new file mode 100644
index 0000000..398c142
--- /dev/null
+++ b/.env.production
@@ -0,0 +1,2 @@
+NEXT_PUBLIC_BASE_PATH=/temp
+NEXT_PUBLIC_API_URL=https://bysrvapp.mam.gov.tr/api-ncr
\ No newline at end of file
diff --git a/.eslintrc.json b/.eslintrc.json
new file mode 100644
index 0000000..bffb357
--- /dev/null
+++ b/.eslintrc.json
@@ -0,0 +1,3 @@
+{
+ "extends": "next/core-web-vitals"
+}
diff --git a/.gitignore b/.gitignore
index ceaea36..2a9755a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,132 +1,13 @@
-# ---> Node
-# Logs
-logs
-*.log
-npm-debug.log*
-yarn-debug.log*
-yarn-error.log*
-lerna-debug.log*
-.pnpm-debug.log*
-
-# Diagnostic reports (https://nodejs.org/api/report.html)
-report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
-
-# Runtime data
-pids
-*.pid
-*.seed
-*.pid.lock
-
-# Directory for instrumented libs generated by jscoverage/JSCover
-lib-cov
-
-# Coverage directory used by tools like istanbul
-coverage
-*.lcov
-
-# nyc test coverage
-.nyc_output
-
-# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
-.grunt
-
-# Bower dependency directory (https://bower.io/)
-bower_components
-
-# node-waf configuration
-.lock-wscript
-
-# Compiled binary addons (https://nodejs.org/api/addons.html)
-build/Release
-
-# Dependency directories
+### NodeJS
node_modules/
-jspm_packages/
+build/
+.next/
+package-lock.json
-# Snowpack dependency directory (https://snowpack.dev/)
-web_modules/
+# VSCode
+.vscode/
-# TypeScript cache
-*.tsbuildinfo
-
-# Optional npm cache directory
-.npm
-
-# Optional eslint cache
-.eslintcache
-
-# Optional stylelint cache
-.stylelintcache
-
-# Microbundle cache
-.rpt2_cache/
-.rts2_cache_cjs/
-.rts2_cache_es/
-.rts2_cache_umd/
-
-# Optional REPL history
-.node_repl_history
-
-# Output of 'npm pack'
-*.tgz
-
-# Yarn Integrity file
-.yarn-integrity
-
-# dotenv environment variable files
-.env
-.env.development.local
-.env.test.local
-.env.production.local
-.env.local
-
-# parcel-bundler cache (https://parceljs.org/)
-.cache
-.parcel-cache
-
-# Next.js build output
-.next
-out
-
-# Nuxt.js build / generate output
-.nuxt
-dist
-
-# Gatsby files
-.cache/
-# Comment in the public line in if your project uses Gatsby and not Next.js
-# https://nextjs.org/blog/next-9-1#public-directory-support
-# public
-
-# vuepress build output
-.vuepress/dist
-
-# vuepress v2.x temp and cache directory
-.temp
-.cache
-
-# Docusaurus cache and generated files
-.docusaurus
-
-# Serverless directories
-.serverless/
-
-# FuseBox cache
-.fusebox/
-
-# DynamoDB Local files
-.dynamodb/
-
-# TernJS port file
-.tern-port
-
-# Stores VSCode versions used for testing VSCode extensions
-.vscode-test
-
-# yarn v2
-.yarn/cache
-.yarn/unplugged
-.yarn/build-state.yml
-.yarn/install-state.gz
-.pnp.*
+### OS Specific
+.DS_Store
+Thumbs.db
diff --git a/README.md b/README.md
index b4dd661..1fc4c8d 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,69 @@
-# webTemplate
+# NCR Dashboard — Next.js Template
-Nextjs Web Template
\ No newline at end of file
+`webNonConformanceReport` (CRA + react-router-dom v5 + Light Bootstrap
+Dashboard) projesinden, gereksiz bağımlılıklar kaldırılarak türetilmiş güncel
+Next.js (App Router, JavaScript) şablonu.
+
+## Kurulum
+
+```bash
+npm install
+cp .env.example .env.local # NEXT_PUBLIC_API_URL değerini kendi API adresinizle değiştirin
+npm run dev
+```
+
+## Eskisiyle karşılaştırma
+
+| Eski proje | Yeni proje |
+|---|---|
+| Create React App + `react-scripts` | Next.js 15 (App Router) |
+| `react-router-dom` v5 (`Switch`, `useHistory`) | Next.js dosya tabanlı routing (`app/`), `next/navigation` |
+| `ProtectedRoute.js` (route bileşeni) | `components/auth/AuthGuard.jsx` (dashboard route grubunu sarar) |
+| Bootstrap + `react-bootstrap` + `reactstrap` + el yazımı SCSS teması | Tailwind CSS + shadcn/ui birincil (Radix tabanlı, kendi bileşen kodunuz) |
+| Ant Design (`antd`) — sadece Login sayfasında kullanılıyordu | shadcn/ui `Button`/`Input`/`Card` bileşenleri |
+| `@fortawesome/fontawesome-free`, `nc-icon` seti | `lucide-react` (tree-shakeable, tek ikon seti) |
+| `chartist`, `react-chartist` (kullanılmıyordu) | kaldırıldı — ihtiyaç olursa `recharts` eklenebilir |
+| `gulp`, `gulp-append-prepend` (SCSS build script'leri) | kaldırıldı — Tailwind zaten derlemeyi hallediyor |
+| `@tanstack/react-table`, `react-highlight-words`, `react-notification-alert`, `@react-hook/media-query`, `@emotion/*`, `ajv`, `jquery` (optional) | kaldırıldı — hiçbiri `src/` içinde kullanılmıyordu |
+| `localStorage` üzerinden manuel `ncr-user` okuma/yazma her dosyada tekrarlanıyordu | `store/authSlice.js` içinde tek noktadan yönetiliyor (Redux Toolkit korundu) |
+| `REACT_APP_BASE_PATH` her `Route`/`Link`'te elle birleştiriliyordu | `next.config.mjs` → `basePath` (tek noktadan, otomatik) |
+| `REACT_APP_API_URL` + çıplak `fetch` | `NEXT_PUBLIC_API_URL` + `lib/axios.js` (tek `axios` instance, token interceptor) |
+
+## Yapı
+
+```
+src/
+ app/
+ layout.js kök layout, font + Redux Provider
+ login/page.js
+ not-authorized/page.js
+ not-found.js genel 404
+ (dashboard)/
+ layout.js AuthGuard + Sidebar + Navbar + Footer
+ page.js Ana sayfa
+ ncr-list/page.js
+ project/page.js rol korumalı (QUALITY, ADMINISTRATOR)
+ resource/page.js rol korumalı
+ person/page.js rol korumalı
+ components/
+ ui/ shadcn/ui birincil bileşenleri (button, input, card, ...)
+ layout/ Sidebar, Navbar, Footer
+ auth/AuthGuard.jsx oturum + rol kontrolü
+ lib/
+ routes.js sidebar + erişim kontrolü tek kaynağı
+ axios.js API istemcisi
+ utils.js `cn()` class birleştirici
+ store/
+ store.js, authSlice.js Redux Toolkit
+```
+
+## Not
+
+- Kimlik doğrulama halen istemci tarafında `localStorage` (`ncr-user`) üzerinden
+ yürütülüyor — eski projeyle aynı model. Sunucu tarafı (middleware/cookie
+ tabanlı) koruma isterseniz `AuthGuard` yerine `middleware.js` + httpOnly
+ cookie'ye geçmeniz önerilir.
+- `ncr-list`, `project`, `resource`, `person` sayfaları içerik eklemeniz için
+ boş bırakılmış placeholder'lardır.
+- Backend'iniz `POST /auth/login` isteğine `{ token, fullName, role, ... }`
+ benzeri bir JSON dönüyorsa login akışı olduğu gibi çalışır.
diff --git a/jsconfig.json b/jsconfig.json
new file mode 100644
index 0000000..abe04df
--- /dev/null
+++ b/jsconfig.json
@@ -0,0 +1,8 @@
+{
+ "compilerOptions": {
+ "baseUrl": ".",
+ "paths": {
+ "@/*": ["./src/*"]
+ }
+ }
+}
diff --git a/next.config.mjs b/next.config.mjs
new file mode 100644
index 0000000..00cd3be
--- /dev/null
+++ b/next.config.mjs
@@ -0,0 +1,7 @@
+/** @type {import('next').NextConfig} */
+const nextConfig = {
+ basePath: process.env.NEXT_PUBLIC_BASE_PATH || "",
+ reactStrictMode: true,
+};
+
+export default nextConfig;
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..8b5a580
--- /dev/null
+++ b/package.json
@@ -0,0 +1,36 @@
+{
+ "name": "temp",
+ "version": "1.0.0",
+ "private": true,
+ "scripts": {
+ "dev": "next dev",
+ "build": "next build",
+ "start": "next start",
+ "lint": "next lint"
+ },
+ "dependencies": {
+ "@radix-ui/react-avatar": "^1.1.2",
+ "@radix-ui/react-dialog": "^1.1.4",
+ "@radix-ui/react-dropdown-menu": "^2.1.4",
+ "@radix-ui/react-label": "^2.1.1",
+ "@radix-ui/react-separator": "^1.1.1",
+ "@radix-ui/react-slot": "^1.1.1",
+ "@reduxjs/toolkit": "^2.5.0",
+ "axios": "^1.11.0",
+ "class-variance-authority": "^0.7.1",
+ "clsx": "^2.1.1",
+ "lucide-react": "^0.469.0",
+ "next": "^16.3.1",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0",
+ "react-redux": "^9.2.0",
+ "tailwind-merge": "^2.6.0"
+ },
+ "devDependencies": {
+ "autoprefixer": "^10.4.20",
+ "eslint": "^9.17.0",
+ "eslint-config-next": "^15.1.0",
+ "postcss": "^8.4.49",
+ "tailwindcss": "^3.4.17"
+ }
+}
diff --git a/postcss.config.mjs b/postcss.config.mjs
new file mode 100644
index 0000000..2ef30fc
--- /dev/null
+++ b/postcss.config.mjs
@@ -0,0 +1,9 @@
+/** @type {import('postcss-load-config').Config} */
+const config = {
+ plugins: {
+ tailwindcss: {},
+ autoprefixer: {},
+ },
+};
+
+export default config;
diff --git a/src/app/(dashboard)/layout.js b/src/app/(dashboard)/layout.js
new file mode 100644
index 0000000..fb54fac
--- /dev/null
+++ b/src/app/(dashboard)/layout.js
@@ -0,0 +1,34 @@
+"use client";
+
+import AuthGuard from "@/components/auth/AuthGuard";
+import Sidebar from "@/components/layout/Sidebar";
+import Navbar from "@/components/layout/Navbar";
+import Footer from "@/components/layout/Footer";
+import { Sheet, SheetContent } from "@/components/ui/sheet";
+import { useState } from "react";
+
+export default function DashboardLayout({ children }) {
+ const [mobileOpen, setMobileOpen] = useState(false);
+
+ return (
+
+
+
+
+
+
+ setMobileOpen(false)} />
+
+
+
+
+ {children}
+
+
+
+
+
+ );
+}
diff --git a/src/app/(dashboard)/page.js b/src/app/(dashboard)/page.js
new file mode 100644
index 0000000..dc6b970
--- /dev/null
+++ b/src/app/(dashboard)/page.js
@@ -0,0 +1,23 @@
+import { Card, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
+
+export default function MainPage() {
+ return (
+
+
+ Template
+
+
+ Purpose of the application
+
+
+
+
+ Notes
+
+ Free places in the template are the samples.
+
+
+
+
+ );
+}
diff --git a/src/app/(dashboard)/person/page.js b/src/app/(dashboard)/person/page.js
new file mode 100644
index 0000000..58f8b6c
--- /dev/null
+++ b/src/app/(dashboard)/person/page.js
@@ -0,0 +1,10 @@
+import PlaceholderPage from "@/components/layout/PlaceholderPage";
+
+export default function PersonPage() {
+ return (
+
+ );
+}
diff --git a/src/app/globals.css b/src/app/globals.css
new file mode 100644
index 0000000..c3e0576
--- /dev/null
+++ b/src/app/globals.css
@@ -0,0 +1,54 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+@layer base {
+ :root {
+ /* Nötr, soğuk beyaz zemin */
+ --background: 40 14% 97%;
+ --foreground: 222 32% 12%;
+
+ --card: 0 0% 100%;
+ --card-foreground: 222 32% 12%;
+
+ --border: 220 14% 89%;
+ --input: 220 14% 89%;
+ --ring: 38 71% 51%;
+
+ /* Amber/altın vurgu — kalibrasyon etiketi / uygunsuzluk uyarı rengi */
+ --primary: 38 71% 51%;
+ --primary-foreground: 222 32% 12%;
+
+ --secondary: 220 16% 94%;
+ --secondary-foreground: 222 32% 12%;
+
+ --muted: 220 16% 94%;
+ --muted-foreground: 220 9% 46%;
+
+ --accent: 38 71% 96%;
+ --accent-foreground: 38 71% 30%;
+
+ --destructive: 4 62% 47%;
+ --destructive-foreground: 0 0% 100%;
+
+ /* Koyu lacivert sidebar */
+ --sidebar: 222 47% 9%;
+ --sidebar-foreground: 220 20% 88%;
+ --sidebar-muted: 220 14% 60%;
+ --sidebar-active: 38 71% 51%;
+
+ --radius: 0.5rem;
+
+ --font-sans: "Inter", system-ui, -apple-system, sans-serif;
+ --font-mono: "JetBrains Mono", ui-monospace, "SFMono-Regular", monospace;
+ }
+}
+
+@layer base {
+ * {
+ @apply border-border;
+ }
+ body {
+ @apply bg-background text-foreground font-sans antialiased;
+ }
+}
diff --git a/src/app/layout.js b/src/app/layout.js
new file mode 100644
index 0000000..12ce056
--- /dev/null
+++ b/src/app/layout.js
@@ -0,0 +1,30 @@
+import { Inter, JetBrains_Mono } from "next/font/google";
+import { Providers } from "./providers";
+import "./globals.css";
+
+const inter = Inter({
+ subsets: ["latin"],
+ variable: "--font-sans",
+ display: "swap",
+});
+
+const jetbrainsMono = JetBrains_Mono({
+ subsets: ["latin"],
+ variable: "--font-mono",
+ display: "swap",
+});
+
+export const metadata = {
+ title: "Temp Title",
+ description: "Temp Description",
+};
+
+export default function RootLayout({ children }) {
+ return (
+
+
+ {children}
+
+
+ );
+}
diff --git a/src/app/login/page.js b/src/app/login/page.js
new file mode 100644
index 0000000..13028f8
--- /dev/null
+++ b/src/app/login/page.js
@@ -0,0 +1,77 @@
+"use client";
+
+import { useState } from "react";
+import { useRouter } from "next/navigation";
+import { useDispatch } from "react-redux";
+import { setUser } from "@/store/authSlice";
+import api from "@/lib/axios";
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { Label } from "@/components/ui/label";
+import { Card, CardHeader, CardTitle, CardDescription, CardContent } from "@/components/ui/card";
+
+export default function LoginPage() {
+ const [email, setEmail] = useState("");
+ const [password, setPassword] = useState("");
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState("");
+
+ const dispatch = useDispatch();
+ const router = useRouter();
+
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+ setError("");
+ setLoading(true);
+ try {
+ const { data } = await api.post("/auth/login", { email, password });
+ dispatch(setUser(data));
+ router.push("/");
+ } catch (err) {
+ setError(err.response?.data?.message || "Login failed");
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ return (
+
+
+
+ Login Page
+ Login with email & PC login password
+
+
+
+
+
+
+ );
+}
diff --git a/src/app/not-authorized/page.js b/src/app/not-authorized/page.js
new file mode 100644
index 0000000..29a5702
--- /dev/null
+++ b/src/app/not-authorized/page.js
@@ -0,0 +1,18 @@
+"use client";
+
+import { useRouter } from "next/navigation";
+import { Button } from "@/components/ui/button";
+
+export default function NotAuthorizedPage() {
+ const router = useRouter();
+
+ return (
+
+
403 - Yetkiniz Yok
+
Bu sayfayı görüntüleme yetkiniz bulunmuyor.
+
+
+ );
+}
diff --git a/src/app/not-found.js b/src/app/not-found.js
new file mode 100644
index 0000000..3077c02
--- /dev/null
+++ b/src/app/not-found.js
@@ -0,0 +1,20 @@
+"use client";
+
+import { useRouter } from "next/navigation";
+import { Button } from "@/components/ui/button";
+
+export default function NotFound() {
+ const router = useRouter();
+
+ return (
+
+
404 - Page Not Found
+
+ The page you are looking for does not exist or may have been moved..
+
+
+
+ );
+}
diff --git a/src/app/providers.js b/src/app/providers.js
new file mode 100644
index 0000000..0f3b0e7
--- /dev/null
+++ b/src/app/providers.js
@@ -0,0 +1,8 @@
+"use client";
+
+import { Provider } from "react-redux";
+import { store } from "@/store/store";
+
+export function Providers({ children }) {
+ return {children};
+}
diff --git a/src/assets/img/MAM2.png b/src/assets/img/MAM2.png
new file mode 100644
index 0000000..1090f8b
Binary files /dev/null and b/src/assets/img/MAM2.png differ
diff --git a/src/assets/img/sidebar-4.jpg b/src/assets/img/sidebar-4.jpg
new file mode 100644
index 0000000..3085c7b
Binary files /dev/null and b/src/assets/img/sidebar-4.jpg differ
diff --git a/src/components/auth/AuthGuard.jsx b/src/components/auth/AuthGuard.jsx
new file mode 100644
index 0000000..6ce4abc
--- /dev/null
+++ b/src/components/auth/AuthGuard.jsx
@@ -0,0 +1,42 @@
+"use client";
+
+import { useEffect } from "react";
+import { usePathname, useRouter } from "next/navigation";
+import { useDispatch, useSelector } from "react-redux";
+import { hydrateFromStorage } from "@/store/authSlice";
+import { findRouteByPath, canAccessRoute } from "@/lib/routes";
+
+export default function AuthGuard({ children }) {
+ const dispatch = useDispatch();
+ const router = useRouter();
+ const pathname = usePathname();
+ const { user, initialized } = useSelector((state) => state.auth);
+
+ useEffect(() => {
+ dispatch(hydrateFromStorage());
+ }, [dispatch]);
+
+ useEffect(() => {
+ if (!initialized) return;
+
+ if (!user) {
+ router.replace("/login");
+ return;
+ }
+
+ const route = findRouteByPath(pathname);
+ if (route && !canAccessRoute(route, user.role)) {
+ router.replace("/not-authorized");
+ }
+ }, [initialized, user, pathname, router]);
+
+ if (!initialized || !user) {
+ return (
+
+ Yükleniyor...
+
+ );
+ }
+
+ return children;
+}
diff --git a/src/components/layout/Footer.jsx b/src/components/layout/Footer.jsx
new file mode 100644
index 0000000..2e85a8a
--- /dev/null
+++ b/src/components/layout/Footer.jsx
@@ -0,0 +1,7 @@
+export default function Footer() {
+ return (
+
+ );
+}
diff --git a/src/components/layout/Navbar.jsx b/src/components/layout/Navbar.jsx
new file mode 100644
index 0000000..1f73867
--- /dev/null
+++ b/src/components/layout/Navbar.jsx
@@ -0,0 +1,87 @@
+"use client";
+
+import { usePathname, useRouter } from "next/navigation";
+import { useDispatch, useSelector } from "react-redux";
+import { Menu, LogOut, User } from "lucide-react";
+import { logout } from "@/store/authSlice";
+import { dashboardRoutes } from "@/lib/routes";
+import { Button } from "@/components/ui/button";
+import { Avatar, AvatarFallback } from "@/components/ui/avatar";
+import {
+ DropdownMenu,
+ DropdownMenuTrigger,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuLabel,
+ DropdownMenuSeparator,
+} from "@/components/ui/dropdown-menu";
+import { SheetTrigger } from "@/components/ui/sheet";
+
+function getPageTitle(pathname) {
+ const route = dashboardRoutes.find((r) => r.href === pathname);
+ return route?.label || "Main Page";
+}
+
+export default function Navbar() {
+ const pathname = usePathname();
+ const router = useRouter();
+ const dispatch = useDispatch();
+ const user = useSelector((state) => state.auth.user);
+
+ const initials = (user?.fullName || "?")
+ .split(" ")
+ .map((p) => p[0])
+ .slice(0, 2)
+ .join("")
+ .toUpperCase();
+
+ const handleLogout = () => {
+ dispatch(logout());
+ router.push("/login");
+ };
+
+ return (
+
+ );
+}
diff --git a/src/components/layout/PlaceholderPage.jsx b/src/components/layout/PlaceholderPage.jsx
new file mode 100644
index 0000000..2a9d4d8
--- /dev/null
+++ b/src/components/layout/PlaceholderPage.jsx
@@ -0,0 +1,12 @@
+import { Card, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
+
+export default function PlaceholderPage({ title, description }) {
+ return (
+
+
+ {title}
+ {description}
+
+
+ );
+}
diff --git a/src/components/layout/Sidebar.jsx b/src/components/layout/Sidebar.jsx
new file mode 100644
index 0000000..1ec027e
--- /dev/null
+++ b/src/components/layout/Sidebar.jsx
@@ -0,0 +1,77 @@
+"use client";
+
+import Link from "next/link";
+import Image from "next/image";
+import { usePathname } from "next/navigation";
+import { useSelector } from "react-redux";
+import { cn } from "@/lib/utils";
+import { dashboardRoutes, canAccessRoute } from "@/lib/routes";
+
+import backgroundImageSidebar from "@/assets/img/sidebar-4.jpg";
+import logo from "@/assets/img/MAM2.png";
+
+export default function Sidebar({ onNavigate }) {
+ const pathname = usePathname();
+ const user = useSelector((state) => state.auth.user);
+
+ const links = dashboardRoutes.filter(
+ (route) =>
+ route.showInSidebar !== false && canAccessRoute(route, user?.role),
+ );
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ TÜBİTAK MAM
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/ui/avatar.jsx b/src/components/ui/avatar.jsx
new file mode 100644
index 0000000..fede42e
--- /dev/null
+++ b/src/components/ui/avatar.jsx
@@ -0,0 +1,40 @@
+"use client";
+
+import * as React from "react";
+import * as AvatarPrimitive from "@radix-ui/react-avatar";
+import { cn } from "@/lib/utils";
+
+const Avatar = React.forwardRef(({ className, ...props }, ref) => (
+
+));
+Avatar.displayName = AvatarPrimitive.Root.displayName;
+
+const AvatarImage = React.forwardRef(({ className, ...props }, ref) => (
+
+));
+AvatarImage.displayName = AvatarPrimitive.Image.displayName;
+
+const AvatarFallback = React.forwardRef(({ className, ...props }, ref) => (
+
+));
+AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;
+
+export { Avatar, AvatarImage, AvatarFallback };
diff --git a/src/components/ui/button.jsx b/src/components/ui/button.jsx
new file mode 100644
index 0000000..ad6d9b6
--- /dev/null
+++ b/src/components/ui/button.jsx
@@ -0,0 +1,49 @@
+import * as React from "react";
+import { Slot } from "@radix-ui/react-slot";
+import { cva } from "class-variance-authority";
+import { cn } from "@/lib/utils";
+
+const buttonVariants = cva(
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
+ {
+ variants: {
+ variant: {
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
+ destructive:
+ "bg-destructive text-destructive-foreground hover:bg-destructive/90",
+ outline:
+ "border border-input bg-background hover:bg-secondary",
+ secondary:
+ "bg-secondary text-secondary-foreground hover:bg-secondary/80",
+ ghost: "hover:bg-secondary",
+ link: "text-primary underline-offset-4 hover:underline",
+ },
+ size: {
+ default: "h-10 px-4 py-2",
+ sm: "h-9 rounded-md px-3",
+ lg: "h-11 rounded-md px-8",
+ icon: "h-10 w-10",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "default",
+ },
+ }
+);
+
+const Button = React.forwardRef(
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
+ const Comp = asChild ? Slot : "button";
+ return (
+
+ );
+ }
+);
+Button.displayName = "Button";
+
+export { Button, buttonVariants };
diff --git a/src/components/ui/card.jsx b/src/components/ui/card.jsx
new file mode 100644
index 0000000..1e8c7f0
--- /dev/null
+++ b/src/components/ui/card.jsx
@@ -0,0 +1,45 @@
+import * as React from "react";
+import { cn } from "@/lib/utils";
+
+const Card = React.forwardRef(({ className, ...props }, ref) => (
+
+));
+Card.displayName = "Card";
+
+const CardHeader = React.forwardRef(({ className, ...props }, ref) => (
+
+));
+CardHeader.displayName = "CardHeader";
+
+const CardTitle = React.forwardRef(({ className, ...props }, ref) => (
+
+));
+CardTitle.displayName = "CardTitle";
+
+const CardDescription = React.forwardRef(({ className, ...props }, ref) => (
+
+));
+CardDescription.displayName = "CardDescription";
+
+const CardContent = React.forwardRef(({ className, ...props }, ref) => (
+
+));
+CardContent.displayName = "CardContent";
+
+const CardFooter = React.forwardRef(({ className, ...props }, ref) => (
+
+));
+CardFooter.displayName = "CardFooter";
+
+export { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter };
diff --git a/src/components/ui/dropdown-menu.jsx b/src/components/ui/dropdown-menu.jsx
new file mode 100644
index 0000000..ff3d29a
--- /dev/null
+++ b/src/components/ui/dropdown-menu.jsx
@@ -0,0 +1,66 @@
+"use client";
+
+import * as React from "react";
+import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
+import { cn } from "@/lib/utils";
+
+const DropdownMenu = DropdownMenuPrimitive.Root;
+const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
+
+const DropdownMenuContent = React.forwardRef(
+ ({ className, sideOffset = 6, align = "end", ...props }, ref) => (
+
+
+
+ )
+);
+DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
+
+const DropdownMenuItem = React.forwardRef(({ className, ...props }, ref) => (
+
+));
+DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
+
+const DropdownMenuSeparator = React.forwardRef(({ className, ...props }, ref) => (
+
+));
+DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
+
+const DropdownMenuLabel = React.forwardRef(({ className, ...props }, ref) => (
+
+));
+DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
+
+export {
+ DropdownMenu,
+ DropdownMenuTrigger,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuSeparator,
+ DropdownMenuLabel,
+};
diff --git a/src/components/ui/input.jsx b/src/components/ui/input.jsx
new file mode 100644
index 0000000..812518d
--- /dev/null
+++ b/src/components/ui/input.jsx
@@ -0,0 +1,19 @@
+import * as React from "react";
+import { cn } from "@/lib/utils";
+
+const Input = React.forwardRef(({ className, type, ...props }, ref) => {
+ return (
+
+ );
+});
+Input.displayName = "Input";
+
+export { Input };
diff --git a/src/components/ui/label.jsx b/src/components/ui/label.jsx
new file mode 100644
index 0000000..d344771
--- /dev/null
+++ b/src/components/ui/label.jsx
@@ -0,0 +1,19 @@
+"use client";
+
+import * as React from "react";
+import * as LabelPrimitive from "@radix-ui/react-label";
+import { cn } from "@/lib/utils";
+
+const Label = React.forwardRef(({ className, ...props }, ref) => (
+
+));
+Label.displayName = LabelPrimitive.Root.displayName;
+
+export { Label };
diff --git a/src/components/ui/separator.jsx b/src/components/ui/separator.jsx
new file mode 100644
index 0000000..9f4b6c0
--- /dev/null
+++ b/src/components/ui/separator.jsx
@@ -0,0 +1,24 @@
+"use client";
+
+import * as React from "react";
+import * as SeparatorPrimitive from "@radix-ui/react-separator";
+import { cn } from "@/lib/utils";
+
+const Separator = React.forwardRef(
+ ({ className, orientation = "horizontal", decorative = true, ...props }, ref) => (
+
+ )
+);
+Separator.displayName = SeparatorPrimitive.Root.displayName;
+
+export { Separator };
diff --git a/src/components/ui/sheet.jsx b/src/components/ui/sheet.jsx
new file mode 100644
index 0000000..797be82
--- /dev/null
+++ b/src/components/ui/sheet.jsx
@@ -0,0 +1,43 @@
+"use client";
+
+import * as React from "react";
+import * as DialogPrimitive from "@radix-ui/react-dialog";
+import { X } from "lucide-react";
+import { cn } from "@/lib/utils";
+
+const Sheet = DialogPrimitive.Root;
+const SheetTrigger = DialogPrimitive.Trigger;
+const SheetClose = DialogPrimitive.Close;
+
+const SheetOverlay = React.forwardRef(({ className, ...props }, ref) => (
+
+));
+SheetOverlay.displayName = DialogPrimitive.Overlay.displayName;
+
+const SheetContent = React.forwardRef(({ className, children, ...props }, ref) => (
+
+
+
+ {children}
+
+
+ Kapat
+
+
+
+));
+SheetContent.displayName = DialogPrimitive.Content.displayName;
+
+export { Sheet, SheetTrigger, SheetClose, SheetContent };
diff --git a/src/lib/axios.js b/src/lib/axios.js
new file mode 100644
index 0000000..260fc4f
--- /dev/null
+++ b/src/lib/axios.js
@@ -0,0 +1,20 @@
+import axios from "axios";
+
+const api = axios.create({
+ baseURL: process.env.NEXT_PUBLIC_API_URL,
+ headers: { "Content-Type": "application/json" },
+});
+
+// Kayıtlı kullanıcının token'ını (varsa) her isteğe ekler.
+api.interceptors.request.use((config) => {
+ if (typeof window !== "undefined") {
+ const raw = localStorage.getItem("ncr-user");
+ const user = raw ? JSON.parse(raw) : null;
+ if (user?.token) {
+ config.headers.Authorization = `Bearer ${user.token}`;
+ }
+ }
+ return config;
+});
+
+export default api;
diff --git a/src/lib/routes.js b/src/lib/routes.js
new file mode 100644
index 0000000..e749f41
--- /dev/null
+++ b/src/lib/routes.js
@@ -0,0 +1,28 @@
+import { LayoutGrid, ClipboardList, FolderKanban, Wrench, Users } from "lucide-react";
+
+// Sidebar'da görünen ve erişim denetiminde kullanılan tek kaynak.
+// roles: undefined -> herkes erişebilir. Aksi halde listelenen rollere sahip
+// kullanıcılar erişebilir.
+export const dashboardRoutes = [
+ {
+ href: "/",
+ label: "Main Page",
+ icon: LayoutGrid,
+ showInSidebar: false,
+ },
+ {
+ href: "/person",
+ label: "Users",
+ icon: Users,
+ roles: ["ADMINISTRATOR"],
+ },
+];
+
+export function canAccessRoute(route, role) {
+ if (!route.roles) return true;
+ return route.roles.includes(role);
+}
+
+export function findRouteByPath(path) {
+ return dashboardRoutes.find((r) => r.href === path);
+}
diff --git a/src/lib/utils.js b/src/lib/utils.js
new file mode 100644
index 0000000..378ccef
--- /dev/null
+++ b/src/lib/utils.js
@@ -0,0 +1,6 @@
+import { clsx } from "clsx";
+import { twMerge } from "tailwind-merge";
+
+export function cn(...inputs) {
+ return twMerge(clsx(inputs));
+}
diff --git a/src/store/authSlice.js b/src/store/authSlice.js
new file mode 100644
index 0000000..ac6cf44
--- /dev/null
+++ b/src/store/authSlice.js
@@ -0,0 +1,49 @@
+import { createSlice } from "@reduxjs/toolkit";
+
+const STORAGE_KEY = "ncr-user";
+
+function readStoredUser() {
+ if (typeof window === "undefined") return null;
+ try {
+ const raw = localStorage.getItem(STORAGE_KEY);
+ return raw ? JSON.parse(raw) : null;
+ } catch {
+ return null;
+ }
+}
+
+const authSlice = createSlice({
+ name: "auth",
+ initialState: {
+ user: null,
+ // Sunucu tarafında render edilirken localStorage okunamadığı için
+ // gerçek kullanıcı durumu içinde client'ta doldurulur.
+ initialized: false,
+ },
+ reducers: {
+ setUser(state, action) {
+ state.user = action.payload;
+ state.initialized = true;
+ if (typeof window !== "undefined") {
+ if (action.payload) {
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(action.payload));
+ } else {
+ localStorage.removeItem(STORAGE_KEY);
+ }
+ }
+ },
+ hydrateFromStorage(state) {
+ state.user = readStoredUser();
+ state.initialized = true;
+ },
+ logout(state) {
+ state.user = null;
+ if (typeof window !== "undefined") {
+ localStorage.removeItem(STORAGE_KEY);
+ }
+ },
+ },
+});
+
+export const { setUser, hydrateFromStorage, logout } = authSlice.actions;
+export default authSlice.reducer;
diff --git a/src/store/store.js b/src/store/store.js
new file mode 100644
index 0000000..f01020c
--- /dev/null
+++ b/src/store/store.js
@@ -0,0 +1,8 @@
+import { configureStore } from "@reduxjs/toolkit";
+import authReducer from "./authSlice";
+
+export const store = configureStore({
+ reducer: {
+ auth: authReducer,
+ },
+});
diff --git a/tailwind.config.js b/tailwind.config.js
new file mode 100644
index 0000000..d1a1302
--- /dev/null
+++ b/tailwind.config.js
@@ -0,0 +1,59 @@
+/** @type {import('tailwindcss').Config} */
+module.exports = {
+ darkMode: ["class"],
+ content: [
+ "./src/app/**/*.{js,jsx}",
+ "./src/components/**/*.{js,jsx}",
+ ],
+ theme: {
+ extend: {
+ colors: {
+ border: "hsl(var(--border))",
+ input: "hsl(var(--input))",
+ ring: "hsl(var(--ring))",
+ background: "hsl(var(--background))",
+ foreground: "hsl(var(--foreground))",
+ primary: {
+ DEFAULT: "hsl(var(--primary))",
+ foreground: "hsl(var(--primary-foreground))",
+ },
+ secondary: {
+ DEFAULT: "hsl(var(--secondary))",
+ foreground: "hsl(var(--secondary-foreground))",
+ },
+ muted: {
+ DEFAULT: "hsl(var(--muted))",
+ foreground: "hsl(var(--muted-foreground))",
+ },
+ accent: {
+ DEFAULT: "hsl(var(--accent))",
+ foreground: "hsl(var(--accent-foreground))",
+ },
+ destructive: {
+ DEFAULT: "hsl(var(--destructive))",
+ foreground: "hsl(var(--destructive-foreground))",
+ },
+ card: {
+ DEFAULT: "hsl(var(--card))",
+ foreground: "hsl(var(--card-foreground))",
+ },
+ sidebar: {
+ DEFAULT: "hsl(var(--sidebar))",
+ foreground: "hsl(var(--sidebar-foreground))",
+ muted: "hsl(var(--sidebar-muted))",
+ active: "hsl(var(--sidebar-active))",
+ },
+ },
+ borderRadius: {
+ lg: "var(--radius)",
+ md: "calc(var(--radius) - 2px)",
+ sm: "calc(var(--radius) - 4px)",
+ },
+ fontFamily: {
+ sans: ["var(--font-sans)", "system-ui", "sans-serif"],
+ mono: ["var(--font-mono)", "ui-monospace", "monospace"],
+ },
+ },
+ },
+ plugins: [],
+};