From e881f38e4eebf9630db925c17d53b2e872ed3f51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beyhan=20O=C4=9Fur?= Date: Sun, 26 Apr 2026 22:12:36 +0300 Subject: [PATCH] first commit --- .dockerignore | 29 + .env.local | 20 + .gitignore | 39 + .idea/.gitignore | 10 + .idea/copilot.data.migration.agent.xml | 6 + .idea/copilot.data.migration.ask.xml | 6 + .idea/copilot.data.migration.ask2agent.xml | 6 + .idea/copilot.data.migration.edit.xml | 6 + .idea/inspectionProfiles/Project_Default.xml | 6 + .idea/modules.xml | 8 + .idea/next-dj.iml | 8 + .idea/vcs.xml | 6 + AUTH-IMPLEMENTATION.md | 383 + AUTH-QUICK-START.md | 279 + AUTH.md | 904 ++ CHANGELOG-AUTH.md | 226 + DOCKER.md | 54 + Dockerfile | 72 + README-AUTH.md | 106 + README.md | 36 + ROUTES.md | 275 + SETUP.md | 290 + TEMPLATE-ASSETS.md | 215 + Temp/index.html | 1281 +++ Type/images.ts | 18 + Type/post.ts | 47 + app/activate/[uid]/[token]/page.tsx | 170 + app/api/auth/[...nextauth]/route.ts | 219 + app/assistants/converters/images/page.tsx | 837 ++ .../jsontotype/hooks/useJsonToType.ts | 167 + .../jsontotype/hooks/useJsonToTypeApi.ts | 153 + .../jsontotype/hooks/useJsonToTypeList.ts | 201 + app/assistants/converters/jsontotype/page.tsx | 430 + app/auth/activate/[uid]/[token]/page.tsx | 170 + app/auth/error/page.tsx | 137 + app/auth/login/page.tsx | 198 + .../confirm/[uid]/[token]/page.tsx | 203 + app/auth/password-reset/page.tsx | 153 + app/auth/register/page.tsx | 257 + app/auth/resend-activation/page.tsx | 140 + app/dashboard/page.tsx | 210 + app/favicon.ico | Bin 0 -> 25931 bytes app/globals.css | 94 + app/layout.tsx | 45 + app/menu-fix.css | 81 + app/page.tsx | 117 + .../reset/confirm/[uid]/[token]/page.tsx | 203 + app/profile/page.tsx | 275 + app/providers.tsx | 7 + components/AboutSection.tsx | 99 + components/BlogSection.tsx | 429 + components/CTABottom.tsx | 72 + components/CTASection.tsx | 135 + components/CookieAlert.tsx | 49 + components/Header.tsx | 176 + components/HeroSection.tsx | 71 + components/PreloaderAndSearch.tsx | 75 + docker-compose.yml | 27 + env.example.txt | 17 + eslint.config.mjs | 18 + lib/blogApi.ts | 52 + next-auth.d.ts | 27 + next-env.d.ts | 6 + next.config.ts | 7 + package.json | 26 + proxy.ts | 13 + public/assets/css/animate.css | 2744 +++++++ public/assets/css/bootstrap.min.css | 6 + public/assets/css/bootstrap.min.css.map | 1 + public/assets/css/fonts/tabler-icons.ttf | Bin 0 -> 2382348 bytes public/assets/css/fonts/tabler-icons.woff | Bin 0 -> 1203840 bytes public/assets/css/fonts/tabler-icons.woff2 | Bin 0 -> 820316 bytes public/assets/css/swiper-bundle.min.css | 13 + public/assets/css/tabler-icons.min.css | 4 + public/assets/fonts/flaticon_reland.eot | Bin 0 -> 5976 bytes public/assets/fonts/flaticon_reland.svg | 36 + public/assets/fonts/flaticon_reland.ttf | Bin 0 -> 5780 bytes public/assets/fonts/flaticon_reland.woff | Bin 0 -> 3184 bytes public/assets/fonts/flaticon_reland.woff2 | Bin 0 -> 2636 bytes public/assets/img/bg-img/1.jpg | Bin 0 -> 1614 bytes public/assets/img/bg-img/10.jpg | Bin 0 -> 3833 bytes public/assets/img/bg-img/100.jpg | Bin 0 -> 2160 bytes public/assets/img/bg-img/101.jpg | Bin 0 -> 2160 bytes public/assets/img/bg-img/102.jpg | Bin 0 -> 3067 bytes public/assets/img/bg-img/103.jpg | Bin 0 -> 2160 bytes public/assets/img/bg-img/104.jpg | Bin 0 -> 2160 bytes public/assets/img/bg-img/105.jpg | Bin 0 -> 2160 bytes public/assets/img/bg-img/106.jpg | Bin 0 -> 1766 bytes public/assets/img/bg-img/107.jpg | Bin 0 -> 1766 bytes public/assets/img/bg-img/108.jpg | Bin 0 -> 1766 bytes public/assets/img/bg-img/109.jpg | Bin 0 -> 1766 bytes public/assets/img/bg-img/11.jpg | Bin 0 -> 2106 bytes public/assets/img/bg-img/110.jpg | Bin 0 -> 2324 bytes public/assets/img/bg-img/111.jpg | Bin 0 -> 1614 bytes public/assets/img/bg-img/112.jpg | Bin 0 -> 1614 bytes public/assets/img/bg-img/113.jpg | Bin 0 -> 1614 bytes public/assets/img/bg-img/114.jpg | Bin 0 -> 1614 bytes public/assets/img/bg-img/115.jpg | Bin 0 -> 1614 bytes public/assets/img/bg-img/116.jpg | Bin 0 -> 1614 bytes public/assets/img/bg-img/117.jpg | Bin 0 -> 3048 bytes public/assets/img/bg-img/118.jpg | Bin 0 -> 3048 bytes public/assets/img/bg-img/119.jpg | Bin 0 -> 3048 bytes public/assets/img/bg-img/12.jpg | Bin 0 -> 2106 bytes public/assets/img/bg-img/120.jpg | Bin 0 -> 1286 bytes public/assets/img/bg-img/121.jpg | Bin 0 -> 1286 bytes public/assets/img/bg-img/122.jpg | Bin 0 -> 1286 bytes public/assets/img/bg-img/123.jpg | Bin 0 -> 1919 bytes public/assets/img/bg-img/124.jpg | Bin 0 -> 1198 bytes public/assets/img/bg-img/125.jpg | Bin 0 -> 1198 bytes public/assets/img/bg-img/126.jpg | Bin 0 -> 1198 bytes public/assets/img/bg-img/127.jpg | Bin 0 -> 2550 bytes public/assets/img/bg-img/128.jpg | Bin 0 -> 3204 bytes public/assets/img/bg-img/129.jpg | Bin 0 -> 1945 bytes public/assets/img/bg-img/13.jpg | Bin 0 -> 1155 bytes public/assets/img/bg-img/131.jpg | Bin 0 -> 5134 bytes public/assets/img/bg-img/132.jpg | Bin 0 -> 1942 bytes public/assets/img/bg-img/133.jpg | Bin 0 -> 1942 bytes public/assets/img/bg-img/14.jpg | Bin 0 -> 1155 bytes public/assets/img/bg-img/15.jpg | Bin 0 -> 1155 bytes public/assets/img/bg-img/16.jpg | Bin 0 -> 3146 bytes public/assets/img/bg-img/17.jpg | Bin 0 -> 3158 bytes public/assets/img/bg-img/18.jpg | Bin 0 -> 3163 bytes public/assets/img/bg-img/19.jpg | Bin 0 -> 3146 bytes public/assets/img/bg-img/2.jpg | Bin 0 -> 1614 bytes public/assets/img/bg-img/20.jpg | Bin 0 -> 11179 bytes public/assets/img/bg-img/21.png | Bin 0 -> 1332 bytes public/assets/img/bg-img/22.png | Bin 0 -> 1332 bytes public/assets/img/bg-img/23.png | Bin 0 -> 1332 bytes public/assets/img/bg-img/24.png | Bin 0 -> 1332 bytes public/assets/img/bg-img/25.jpg | Bin 0 -> 1907 bytes public/assets/img/bg-img/26.jpg | Bin 0 -> 2981 bytes public/assets/img/bg-img/27.jpg | Bin 0 -> 3258 bytes public/assets/img/bg-img/28.jpg | Bin 0 -> 9741 bytes public/assets/img/bg-img/29.jpg | Bin 0 -> 1902 bytes public/assets/img/bg-img/3.jpg | Bin 0 -> 1614 bytes public/assets/img/bg-img/30.jpg | Bin 0 -> 1878 bytes public/assets/img/bg-img/31.jpg | Bin 0 -> 1626 bytes public/assets/img/bg-img/32.jpg | Bin 0 -> 1815 bytes public/assets/img/bg-img/33.jpg | Bin 0 -> 2344 bytes public/assets/img/bg-img/34.png | Bin 0 -> 2783 bytes public/assets/img/bg-img/35.png | Bin 0 -> 2783 bytes public/assets/img/bg-img/36.png | Bin 0 -> 2783 bytes public/assets/img/bg-img/37.jpg | Bin 0 -> 2344 bytes public/assets/img/bg-img/38.jpg | Bin 0 -> 1815 bytes public/assets/img/bg-img/39.jpg | Bin 0 -> 856 bytes public/assets/img/bg-img/4.jpg | Bin 0 -> 1486 bytes public/assets/img/bg-img/40.jpg | Bin 0 -> 856 bytes public/assets/img/bg-img/41.jpg | Bin 0 -> 856 bytes public/assets/img/bg-img/42.jpg | Bin 0 -> 2160 bytes public/assets/img/bg-img/43.jpg | Bin 0 -> 2160 bytes public/assets/img/bg-img/44.jpg | Bin 0 -> 2160 bytes public/assets/img/bg-img/45.jpg | Bin 0 -> 2160 bytes public/assets/img/bg-img/46.jpg | Bin 0 -> 1155 bytes public/assets/img/bg-img/47.jpg | Bin 0 -> 1155 bytes public/assets/img/bg-img/48.jpg | Bin 0 -> 1155 bytes public/assets/img/bg-img/49.png | Bin 0 -> 3167 bytes public/assets/img/bg-img/5.jpg | Bin 0 -> 960 bytes public/assets/img/bg-img/50.png | Bin 0 -> 4710 bytes public/assets/img/bg-img/51.png | Bin 0 -> 1579 bytes public/assets/img/bg-img/52.png | Bin 0 -> 1579 bytes public/assets/img/bg-img/53.png | Bin 0 -> 1579 bytes public/assets/img/bg-img/54.png | Bin 0 -> 1579 bytes public/assets/img/bg-img/55.png | Bin 0 -> 1579 bytes public/assets/img/bg-img/56.png | Bin 0 -> 2783 bytes public/assets/img/bg-img/57.png | Bin 0 -> 2783 bytes public/assets/img/bg-img/58.png | Bin 0 -> 2783 bytes public/assets/img/bg-img/59.jpg | Bin 0 -> 1815 bytes public/assets/img/bg-img/6.jpg | Bin 0 -> 960 bytes public/assets/img/bg-img/60.jpg | Bin 0 -> 2160 bytes public/assets/img/bg-img/61.jpg | Bin 0 -> 1798 bytes public/assets/img/bg-img/62.jpg | Bin 0 -> 1793 bytes public/assets/img/bg-img/63.jpg | Bin 0 -> 1798 bytes public/assets/img/bg-img/64.jpg | Bin 0 -> 1793 bytes public/assets/img/bg-img/65.jpg | Bin 0 -> 2160 bytes public/assets/img/bg-img/66.jpg | Bin 0 -> 2160 bytes public/assets/img/bg-img/67.jpg | Bin 0 -> 2160 bytes public/assets/img/bg-img/68.jpg | Bin 0 -> 2160 bytes public/assets/img/bg-img/69.jpg | Bin 0 -> 3067 bytes public/assets/img/bg-img/7.jpg | Bin 0 -> 960 bytes public/assets/img/bg-img/70.jpg | Bin 0 -> 1759 bytes public/assets/img/bg-img/71.jpg | Bin 0 -> 1759 bytes public/assets/img/bg-img/72.jpg | Bin 0 -> 1842 bytes public/assets/img/bg-img/73.jpg | Bin 0 -> 1842 bytes public/assets/img/bg-img/74.png | Bin 0 -> 3105 bytes public/assets/img/bg-img/75.jpg | Bin 0 -> 1430 bytes public/assets/img/bg-img/76.jpg | Bin 0 -> 1430 bytes public/assets/img/bg-img/77.jpg | Bin 0 -> 1430 bytes public/assets/img/bg-img/78.jpg | Bin 0 -> 1430 bytes public/assets/img/bg-img/79.jpg | Bin 0 -> 2233 bytes public/assets/img/bg-img/8.jpg | Bin 0 -> 2106 bytes public/assets/img/bg-img/80.jpg | Bin 0 -> 2193 bytes public/assets/img/bg-img/81.jpg | Bin 0 -> 2193 bytes public/assets/img/bg-img/82.jpg | Bin 0 -> 2217 bytes public/assets/img/bg-img/83.jpg | Bin 0 -> 1766 bytes public/assets/img/bg-img/84.jpg | Bin 0 -> 1766 bytes public/assets/img/bg-img/85.jpg | Bin 0 -> 1766 bytes public/assets/img/bg-img/86.jpg | Bin 0 -> 1766 bytes public/assets/img/bg-img/87.jpg | Bin 0 -> 1700 bytes public/assets/img/bg-img/88.jpg | Bin 0 -> 1700 bytes public/assets/img/bg-img/89.jpg | Bin 0 -> 1700 bytes public/assets/img/bg-img/9.jpg | Bin 0 -> 2106 bytes public/assets/img/bg-img/90.jpg | Bin 0 -> 5457 bytes public/assets/img/bg-img/91.png | Bin 0 -> 2783 bytes public/assets/img/bg-img/92.png | Bin 0 -> 2783 bytes public/assets/img/bg-img/93.png | Bin 0 -> 2783 bytes public/assets/img/bg-img/94.jpg | Bin 0 -> 2461 bytes public/assets/img/bg-img/95.jpg | Bin 0 -> 2634 bytes public/assets/img/bg-img/96.jpg | Bin 0 -> 2160 bytes public/assets/img/bg-img/97.jpg | Bin 0 -> 2160 bytes public/assets/img/bg-img/98.jpg | Bin 0 -> 3067 bytes public/assets/img/bg-img/99.jpg | Bin 0 -> 2160 bytes public/assets/img/core-img/404.png | Bin 0 -> 18219 bytes public/assets/img/core-img/bg.png | Bin 0 -> 4352210 bytes public/assets/img/core-img/circles.png | Bin 0 -> 150475 bytes public/assets/img/core-img/curve1.png | Bin 0 -> 1734 bytes public/assets/img/core-img/curve2.png | Bin 0 -> 1739 bytes public/assets/img/core-img/favicon.ico | Bin 0 -> 791 bytes public/assets/img/core-img/grid.jpg | Bin 0 -> 61787 bytes public/assets/img/core-img/grid2.jpg | Bin 0 -> 28852 bytes public/assets/img/core-img/grid3.jpg | Bin 0 -> 23741 bytes public/assets/img/core-img/grid3.png | Bin 0 -> 23741 bytes public/assets/img/core-img/logo-light.png | Bin 0 -> 2340 bytes public/assets/img/core-img/logo.png | Bin 0 -> 2572 bytes public/assets/img/core-img/logo3.png | Bin 0 -> 2734 bytes public/assets/img/core-img/logo4.png | Bin 0 -> 3734 bytes public/assets/img/core-img/logo5.png | Bin 0 -> 2340 bytes public/assets/img/core-img/shape.png | Bin 0 -> 12802 bytes public/assets/img/core-img/shape10.png | Bin 0 -> 2814441 bytes public/assets/img/core-img/shape11.png | Bin 0 -> 1094 bytes public/assets/img/core-img/shape12.png | Bin 0 -> 107711 bytes public/assets/img/core-img/shape13.png | Bin 0 -> 137614 bytes public/assets/img/core-img/shape14.png | Bin 0 -> 3396 bytes public/assets/img/core-img/shape15.png | Bin 0 -> 1500 bytes public/assets/img/core-img/shape16.png | Bin 0 -> 1439 bytes public/assets/img/core-img/shape17.png | Bin 0 -> 170275 bytes public/assets/img/core-img/shape18.png | Bin 0 -> 94115 bytes public/assets/img/core-img/shape19.png | Bin 0 -> 9657 bytes public/assets/img/core-img/shape2.png | Bin 0 -> 320941 bytes public/assets/img/core-img/shape20.png | Bin 0 -> 3853 bytes public/assets/img/core-img/shape3.png | Bin 0 -> 12213 bytes public/assets/img/core-img/shape4.png | Bin 0 -> 2436 bytes public/assets/img/core-img/shape5.png | Bin 0 -> 12174 bytes public/assets/img/core-img/shape6.png | Bin 0 -> 73471 bytes public/assets/img/core-img/shape7.png | Bin 0 -> 2721 bytes public/assets/img/core-img/shape8.png | Bin 0 -> 28092 bytes public/assets/img/core-img/shape9.png | Bin 0 -> 40364 bytes public/assets/img/core-img/vector.png | Bin 0 -> 99468 bytes public/assets/img/core-img/vector2.png | Bin 0 -> 256881 bytes public/assets/img/partner-img/1.png | Bin 0 -> 3028 bytes public/assets/img/partner-img/10.png | Bin 0 -> 2556 bytes public/assets/img/partner-img/11.png | Bin 0 -> 3184 bytes public/assets/img/partner-img/2.png | Bin 0 -> 2902 bytes public/assets/img/partner-img/3.png | Bin 0 -> 2899 bytes public/assets/img/partner-img/4.png | Bin 0 -> 2592 bytes public/assets/img/partner-img/5.png | Bin 0 -> 3290 bytes public/assets/img/partner-img/6.png | Bin 0 -> 2259 bytes public/assets/img/partner-img/7.png | Bin 0 -> 3630 bytes public/assets/img/partner-img/8.png | Bin 0 -> 2004 bytes public/assets/img/partner-img/9.png | Bin 0 -> 3144 bytes public/assets/js/active.js | 702 ++ public/assets/js/bootstrap.bundle.min.js | 7 + public/assets/js/cookiealert.js | 35 + public/assets/js/imagesloaded.pkgd.min.js | 12 + public/assets/js/index.js | 1 + public/assets/js/isotope.pkgd.min.js | 12 + public/assets/js/jarallax.min.js | 6 + public/assets/js/slideToggle.min.js | 2 + public/assets/js/swiper-bundle.min.js | 14 + public/assets/js/wow.min.js | 2 + public/file.svg | 1 + public/globe.svg | 1 + public/next.svg | 1 + public/style.css | 7298 +++++++++++++++++ public/vercel.svg | 1 + public/window.svg | 1 + tsconfig.json | 34 + urleler.txt | 0 yarn.lock | 3059 +++++++ 278 files changed, 24095 insertions(+) create mode 100644 .dockerignore create mode 100644 .env.local create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/copilot.data.migration.agent.xml create mode 100644 .idea/copilot.data.migration.ask.xml create mode 100644 .idea/copilot.data.migration.ask2agent.xml create mode 100644 .idea/copilot.data.migration.edit.xml create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/next-dj.iml create mode 100644 .idea/vcs.xml create mode 100644 AUTH-IMPLEMENTATION.md create mode 100644 AUTH-QUICK-START.md create mode 100644 AUTH.md create mode 100644 CHANGELOG-AUTH.md create mode 100644 DOCKER.md create mode 100644 Dockerfile create mode 100644 README-AUTH.md create mode 100644 README.md create mode 100644 ROUTES.md create mode 100644 SETUP.md create mode 100644 TEMPLATE-ASSETS.md create mode 100644 Temp/index.html create mode 100644 Type/images.ts create mode 100644 Type/post.ts create mode 100644 app/activate/[uid]/[token]/page.tsx create mode 100644 app/api/auth/[...nextauth]/route.ts create mode 100644 app/assistants/converters/images/page.tsx create mode 100644 app/assistants/converters/jsontotype/hooks/useJsonToType.ts create mode 100644 app/assistants/converters/jsontotype/hooks/useJsonToTypeApi.ts create mode 100644 app/assistants/converters/jsontotype/hooks/useJsonToTypeList.ts create mode 100644 app/assistants/converters/jsontotype/page.tsx create mode 100644 app/auth/activate/[uid]/[token]/page.tsx create mode 100644 app/auth/error/page.tsx create mode 100644 app/auth/login/page.tsx create mode 100644 app/auth/password-reset/confirm/[uid]/[token]/page.tsx create mode 100644 app/auth/password-reset/page.tsx create mode 100644 app/auth/register/page.tsx create mode 100644 app/auth/resend-activation/page.tsx create mode 100644 app/dashboard/page.tsx create mode 100644 app/favicon.ico create mode 100644 app/globals.css create mode 100644 app/layout.tsx create mode 100644 app/menu-fix.css create mode 100644 app/page.tsx create mode 100644 app/password/reset/confirm/[uid]/[token]/page.tsx create mode 100644 app/profile/page.tsx create mode 100644 app/providers.tsx create mode 100644 components/AboutSection.tsx create mode 100644 components/BlogSection.tsx create mode 100644 components/CTABottom.tsx create mode 100644 components/CTASection.tsx create mode 100644 components/CookieAlert.tsx create mode 100644 components/Header.tsx create mode 100644 components/HeroSection.tsx create mode 100644 components/PreloaderAndSearch.tsx create mode 100644 docker-compose.yml create mode 100644 env.example.txt create mode 100644 eslint.config.mjs create mode 100644 lib/blogApi.ts create mode 100644 next-auth.d.ts create mode 100644 next-env.d.ts create mode 100644 next.config.ts create mode 100644 package.json create mode 100644 proxy.ts create mode 100644 public/assets/css/animate.css create mode 100644 public/assets/css/bootstrap.min.css create mode 100644 public/assets/css/bootstrap.min.css.map create mode 100644 public/assets/css/fonts/tabler-icons.ttf create mode 100644 public/assets/css/fonts/tabler-icons.woff create mode 100644 public/assets/css/fonts/tabler-icons.woff2 create mode 100644 public/assets/css/swiper-bundle.min.css create mode 100644 public/assets/css/tabler-icons.min.css create mode 100644 public/assets/fonts/flaticon_reland.eot create mode 100644 public/assets/fonts/flaticon_reland.svg create mode 100644 public/assets/fonts/flaticon_reland.ttf create mode 100644 public/assets/fonts/flaticon_reland.woff create mode 100644 public/assets/fonts/flaticon_reland.woff2 create mode 100644 public/assets/img/bg-img/1.jpg create mode 100644 public/assets/img/bg-img/10.jpg create mode 100644 public/assets/img/bg-img/100.jpg create mode 100644 public/assets/img/bg-img/101.jpg create mode 100644 public/assets/img/bg-img/102.jpg create mode 100644 public/assets/img/bg-img/103.jpg create mode 100644 public/assets/img/bg-img/104.jpg create mode 100644 public/assets/img/bg-img/105.jpg create mode 100644 public/assets/img/bg-img/106.jpg create mode 100644 public/assets/img/bg-img/107.jpg create mode 100644 public/assets/img/bg-img/108.jpg create mode 100644 public/assets/img/bg-img/109.jpg create mode 100644 public/assets/img/bg-img/11.jpg create mode 100644 public/assets/img/bg-img/110.jpg create mode 100644 public/assets/img/bg-img/111.jpg create mode 100644 public/assets/img/bg-img/112.jpg create mode 100644 public/assets/img/bg-img/113.jpg create mode 100644 public/assets/img/bg-img/114.jpg create mode 100644 public/assets/img/bg-img/115.jpg create mode 100644 public/assets/img/bg-img/116.jpg create mode 100644 public/assets/img/bg-img/117.jpg create mode 100644 public/assets/img/bg-img/118.jpg create mode 100644 public/assets/img/bg-img/119.jpg create mode 100644 public/assets/img/bg-img/12.jpg create mode 100644 public/assets/img/bg-img/120.jpg create mode 100644 public/assets/img/bg-img/121.jpg create mode 100644 public/assets/img/bg-img/122.jpg create mode 100644 public/assets/img/bg-img/123.jpg create mode 100644 public/assets/img/bg-img/124.jpg create mode 100644 public/assets/img/bg-img/125.jpg create mode 100644 public/assets/img/bg-img/126.jpg create mode 100644 public/assets/img/bg-img/127.jpg create mode 100644 public/assets/img/bg-img/128.jpg create mode 100644 public/assets/img/bg-img/129.jpg create mode 100644 public/assets/img/bg-img/13.jpg create mode 100644 public/assets/img/bg-img/131.jpg create mode 100644 public/assets/img/bg-img/132.jpg create mode 100644 public/assets/img/bg-img/133.jpg create mode 100644 public/assets/img/bg-img/14.jpg create mode 100644 public/assets/img/bg-img/15.jpg create mode 100644 public/assets/img/bg-img/16.jpg create mode 100644 public/assets/img/bg-img/17.jpg create mode 100644 public/assets/img/bg-img/18.jpg create mode 100644 public/assets/img/bg-img/19.jpg create mode 100644 public/assets/img/bg-img/2.jpg create mode 100644 public/assets/img/bg-img/20.jpg create mode 100644 public/assets/img/bg-img/21.png create mode 100644 public/assets/img/bg-img/22.png create mode 100644 public/assets/img/bg-img/23.png create mode 100644 public/assets/img/bg-img/24.png create mode 100644 public/assets/img/bg-img/25.jpg create mode 100644 public/assets/img/bg-img/26.jpg create mode 100644 public/assets/img/bg-img/27.jpg create mode 100644 public/assets/img/bg-img/28.jpg create mode 100644 public/assets/img/bg-img/29.jpg create mode 100644 public/assets/img/bg-img/3.jpg create mode 100644 public/assets/img/bg-img/30.jpg create mode 100644 public/assets/img/bg-img/31.jpg create mode 100644 public/assets/img/bg-img/32.jpg create mode 100644 public/assets/img/bg-img/33.jpg create mode 100644 public/assets/img/bg-img/34.png create mode 100644 public/assets/img/bg-img/35.png create mode 100644 public/assets/img/bg-img/36.png create mode 100644 public/assets/img/bg-img/37.jpg create mode 100644 public/assets/img/bg-img/38.jpg create mode 100644 public/assets/img/bg-img/39.jpg create mode 100644 public/assets/img/bg-img/4.jpg create mode 100644 public/assets/img/bg-img/40.jpg create mode 100644 public/assets/img/bg-img/41.jpg create mode 100644 public/assets/img/bg-img/42.jpg create mode 100644 public/assets/img/bg-img/43.jpg create mode 100644 public/assets/img/bg-img/44.jpg create mode 100644 public/assets/img/bg-img/45.jpg create mode 100644 public/assets/img/bg-img/46.jpg create mode 100644 public/assets/img/bg-img/47.jpg create mode 100644 public/assets/img/bg-img/48.jpg create mode 100644 public/assets/img/bg-img/49.png create mode 100644 public/assets/img/bg-img/5.jpg create mode 100644 public/assets/img/bg-img/50.png create mode 100644 public/assets/img/bg-img/51.png create mode 100644 public/assets/img/bg-img/52.png create mode 100644 public/assets/img/bg-img/53.png create mode 100644 public/assets/img/bg-img/54.png create mode 100644 public/assets/img/bg-img/55.png create mode 100644 public/assets/img/bg-img/56.png create mode 100644 public/assets/img/bg-img/57.png create mode 100644 public/assets/img/bg-img/58.png create mode 100644 public/assets/img/bg-img/59.jpg create mode 100644 public/assets/img/bg-img/6.jpg create mode 100644 public/assets/img/bg-img/60.jpg create mode 100644 public/assets/img/bg-img/61.jpg create mode 100644 public/assets/img/bg-img/62.jpg create mode 100644 public/assets/img/bg-img/63.jpg create mode 100644 public/assets/img/bg-img/64.jpg create mode 100644 public/assets/img/bg-img/65.jpg create mode 100644 public/assets/img/bg-img/66.jpg create mode 100644 public/assets/img/bg-img/67.jpg create mode 100644 public/assets/img/bg-img/68.jpg create mode 100644 public/assets/img/bg-img/69.jpg create mode 100644 public/assets/img/bg-img/7.jpg create mode 100644 public/assets/img/bg-img/70.jpg create mode 100644 public/assets/img/bg-img/71.jpg create mode 100644 public/assets/img/bg-img/72.jpg create mode 100644 public/assets/img/bg-img/73.jpg create mode 100644 public/assets/img/bg-img/74.png create mode 100644 public/assets/img/bg-img/75.jpg create mode 100644 public/assets/img/bg-img/76.jpg create mode 100644 public/assets/img/bg-img/77.jpg create mode 100644 public/assets/img/bg-img/78.jpg create mode 100644 public/assets/img/bg-img/79.jpg create mode 100644 public/assets/img/bg-img/8.jpg create mode 100644 public/assets/img/bg-img/80.jpg create mode 100644 public/assets/img/bg-img/81.jpg create mode 100644 public/assets/img/bg-img/82.jpg create mode 100644 public/assets/img/bg-img/83.jpg create mode 100644 public/assets/img/bg-img/84.jpg create mode 100644 public/assets/img/bg-img/85.jpg create mode 100644 public/assets/img/bg-img/86.jpg create mode 100644 public/assets/img/bg-img/87.jpg create mode 100644 public/assets/img/bg-img/88.jpg create mode 100644 public/assets/img/bg-img/89.jpg create mode 100644 public/assets/img/bg-img/9.jpg create mode 100644 public/assets/img/bg-img/90.jpg create mode 100644 public/assets/img/bg-img/91.png create mode 100644 public/assets/img/bg-img/92.png create mode 100644 public/assets/img/bg-img/93.png create mode 100644 public/assets/img/bg-img/94.jpg create mode 100644 public/assets/img/bg-img/95.jpg create mode 100644 public/assets/img/bg-img/96.jpg create mode 100644 public/assets/img/bg-img/97.jpg create mode 100644 public/assets/img/bg-img/98.jpg create mode 100644 public/assets/img/bg-img/99.jpg create mode 100644 public/assets/img/core-img/404.png create mode 100644 public/assets/img/core-img/bg.png create mode 100644 public/assets/img/core-img/circles.png create mode 100644 public/assets/img/core-img/curve1.png create mode 100644 public/assets/img/core-img/curve2.png create mode 100644 public/assets/img/core-img/favicon.ico create mode 100644 public/assets/img/core-img/grid.jpg create mode 100644 public/assets/img/core-img/grid2.jpg create mode 100644 public/assets/img/core-img/grid3.jpg create mode 100644 public/assets/img/core-img/grid3.png create mode 100644 public/assets/img/core-img/logo-light.png create mode 100644 public/assets/img/core-img/logo.png create mode 100644 public/assets/img/core-img/logo3.png create mode 100644 public/assets/img/core-img/logo4.png create mode 100644 public/assets/img/core-img/logo5.png create mode 100644 public/assets/img/core-img/shape.png create mode 100644 public/assets/img/core-img/shape10.png create mode 100644 public/assets/img/core-img/shape11.png create mode 100644 public/assets/img/core-img/shape12.png create mode 100644 public/assets/img/core-img/shape13.png create mode 100644 public/assets/img/core-img/shape14.png create mode 100644 public/assets/img/core-img/shape15.png create mode 100644 public/assets/img/core-img/shape16.png create mode 100644 public/assets/img/core-img/shape17.png create mode 100644 public/assets/img/core-img/shape18.png create mode 100644 public/assets/img/core-img/shape19.png create mode 100644 public/assets/img/core-img/shape2.png create mode 100644 public/assets/img/core-img/shape20.png create mode 100644 public/assets/img/core-img/shape3.png create mode 100644 public/assets/img/core-img/shape4.png create mode 100644 public/assets/img/core-img/shape5.png create mode 100644 public/assets/img/core-img/shape6.png create mode 100644 public/assets/img/core-img/shape7.png create mode 100644 public/assets/img/core-img/shape8.png create mode 100644 public/assets/img/core-img/shape9.png create mode 100644 public/assets/img/core-img/vector.png create mode 100644 public/assets/img/core-img/vector2.png create mode 100644 public/assets/img/partner-img/1.png create mode 100644 public/assets/img/partner-img/10.png create mode 100644 public/assets/img/partner-img/11.png create mode 100644 public/assets/img/partner-img/2.png create mode 100644 public/assets/img/partner-img/3.png create mode 100644 public/assets/img/partner-img/4.png create mode 100644 public/assets/img/partner-img/5.png create mode 100644 public/assets/img/partner-img/6.png create mode 100644 public/assets/img/partner-img/7.png create mode 100644 public/assets/img/partner-img/8.png create mode 100644 public/assets/img/partner-img/9.png create mode 100644 public/assets/js/active.js create mode 100644 public/assets/js/bootstrap.bundle.min.js create mode 100644 public/assets/js/cookiealert.js create mode 100644 public/assets/js/imagesloaded.pkgd.min.js create mode 100644 public/assets/js/index.js create mode 100644 public/assets/js/isotope.pkgd.min.js create mode 100644 public/assets/js/jarallax.min.js create mode 100644 public/assets/js/slideToggle.min.js create mode 100644 public/assets/js/swiper-bundle.min.js create mode 100644 public/assets/js/wow.min.js create mode 100644 public/file.svg create mode 100644 public/globe.svg create mode 100644 public/next.svg create mode 100644 public/style.css create mode 100644 public/vercel.svg create mode 100644 public/window.svg create mode 100644 tsconfig.json create mode 100644 urleler.txt create mode 100644 yarn.lock diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..18022cb --- /dev/null +++ b/.dockerignore @@ -0,0 +1,29 @@ +# dependencies +Temp +# temp + +.vscode +.idea +# editor / tooling + +.DS_Store +.gitignore +.git +# VCS / OS + +.env.* +.env +# local env files (don't bake secrets into images) + +*.log +yarn-error.log* +yarn-debug.log* +npm-debug.log* +# logs + +out +.next +# Next.js build output + +node_modules + diff --git a/.env.local b/.env.local new file mode 100644 index 0000000..f9d2002 --- /dev/null +++ b/.env.local @@ -0,0 +1,20 @@ +# Directus Configuration +DIRECTUS_URL=http://10.80.80.70:8055 + +# NextAuth Configuration +NEXTAUTH_URL=http://localhost:3000 +NEXTAUTH_SECRET=bFcOqf37V1DgSxsibuZ79jSIaI4MZ9TCB1Y7iWZFJZrtZdUJapesSi0dXo2Bx8xY +NEXT_PUBLIC_API_BASE_URL=http://localhost:8000/api/v1 +NEXT_PUBLIC_MEDIA_BASE_URL=http://localhost:8000/media +# Production için: +# NEXTAUTH_URL=https://yourdomain.com + + + +GITHUB_ID='Ov23liUt9B61O46Mdfm4' +GITHUB_SECRET='c7fc8dcb1b2c8f22120608425d07d5efd995baaf' +GITHUB_SCOPE=['user:email'] + +GOOGLE_ID='915364976256-691m0s87as2r5vdbqr96f6humblseobt.apps.googleusercontent.com' +GOOGLE_SECRET='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' +GOOGLE_REDIRECT_URL=http://localhost:3000/api/auth/callback/google diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..620537c --- /dev/null +++ b/.gitignore @@ -0,0 +1,39 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + + +# vercel +.vercel + +# typescript +*.tsbuildinfo + diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..ab1f416 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,10 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Ignored default folder with query files +/queries/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/copilot.data.migration.agent.xml b/.idea/copilot.data.migration.agent.xml new file mode 100644 index 0000000..4ea72a9 --- /dev/null +++ b/.idea/copilot.data.migration.agent.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/copilot.data.migration.ask.xml b/.idea/copilot.data.migration.ask.xml new file mode 100644 index 0000000..7ef04e2 --- /dev/null +++ b/.idea/copilot.data.migration.ask.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/copilot.data.migration.ask2agent.xml b/.idea/copilot.data.migration.ask2agent.xml new file mode 100644 index 0000000..1f2ea11 --- /dev/null +++ b/.idea/copilot.data.migration.ask2agent.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/copilot.data.migration.edit.xml b/.idea/copilot.data.migration.edit.xml new file mode 100644 index 0000000..8648f94 --- /dev/null +++ b/.idea/copilot.data.migration.edit.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..03d9549 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..0393ff8 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/next-dj.iml b/.idea/next-dj.iml new file mode 100644 index 0000000..c956989 --- /dev/null +++ b/.idea/next-dj.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/AUTH-IMPLEMENTATION.md b/AUTH-IMPLEMENTATION.md new file mode 100644 index 0000000..fe36948 --- /dev/null +++ b/AUTH-IMPLEMENTATION.md @@ -0,0 +1,383 @@ +# 🔐 Authentication System Implementation + +Django REST API tabanlı tam özellikli Next.js authentication sistemi başarıyla kuruldu! + +## ✅ Tamamlanan Özellikler + +### 1. **NextAuth Configuration** +- ✅ Django REST API entegrasyonu +- ✅ JWT token yönetimi +- ✅ Token refresh mekanizması +- ✅ Social authentication (Google, GitHub) +- ✅ Session management + +### 2. **Authentication Sayfaları** + +#### Login (`/auth/login`) +- ✅ Email/Password girişi +- ✅ Google OAuth2 butonu +- ✅ GitHub OAuth2 butonu +- ✅ Hata yönetimi +- ✅ Şifre sıfırlama linki +- ✅ Aktivasyon emaili tekrar gönderme linki + +#### Register (`/auth/register`) +- ✅ Kullanıcı kayıt formu +- ✅ Email doğrulama +- ✅ Ad, Soyad alanları +- ✅ Şifre eşleştirme kontrolü +- ✅ Field-level hata gösterimi +- ✅ Başarılı kayıt mesajı + +#### Email Activation (`/auth/activate/[uid]/[token]`) +- ✅ Otomatik aktivasyon +- ✅ Loading durumu +- ✅ Başarı/hata mesajları +- ✅ Aktivasyon tekrar gönderme linki +- ✅ Otomatik yönlendirme + +#### Resend Activation (`/auth/resend-activation`) +- ✅ Email tekrar gönderme formu +- ✅ Başarı mesajı +- ✅ Hata yönetimi + +#### Password Reset (`/auth/password-reset`) +- ✅ Şifre sıfırlama talebi +- ✅ Email gönderimi +- ✅ Başarı mesajı + +#### Password Reset Confirm (`/auth/password-reset/confirm/[uid]/[token]`) +- ✅ Yeni şifre belirleme +- ✅ Şifre eşleştirme kontrolü +- ✅ Token doğrulama +- ✅ Başarı mesajı ve yönlendirme + +#### Auth Error (`/auth/error`) +- ✅ Özel hata sayfası +- ✅ Hata tipine göre mesajlar +- ✅ Navigasyon linkleri + +### 3. **User Profile (`/profile`)** +- ✅ Kullanıcı bilgileri gösterimi +- ✅ Profil güncelleme formu +- ✅ Email, üyelik tarihi, hesap durumu +- ✅ Ad/Soyad güncelleme +- ✅ Çıkış yapma butonu +- ✅ Dashboard linki + +### 4. **Dashboard (`/dashboard`)** +- ✅ Login route güncellendi (`/auth/login`) +- ✅ Profil sayfası butonu eklendi +- ✅ Token bilgileri gösterimi + +## 📁 Dosya Yapısı + +``` +next-dj/ +├── app/ +│ ├── api/ +│ │ └── auth/ +│ │ └── [...nextauth]/ +│ │ └── route.ts # NextAuth config (Django entegre) +│ ├── auth/ +│ │ ├── login/ +│ │ │ └── page.tsx # Login sayfası +│ │ ├── register/ +│ │ │ └── page.tsx # Register sayfası +│ │ ├── activate/ +│ │ │ └── [uid]/[token]/ +│ │ │ └── page.tsx # Email activation +│ │ ├── resend-activation/ +│ │ │ └── page.tsx # Resend activation +│ │ ├── password-reset/ +│ │ │ ├── page.tsx # Password reset request +│ │ │ └── confirm/[uid]/[token]/ +│ │ │ └── page.tsx # Password reset confirm +│ │ └── error/ +│ │ └── page.tsx # Auth error page +│ ├── profile/ +│ │ └── page.tsx # User profile +│ └── dashboard/ +│ └── page.tsx # Dashboard (güncellendi) +├── env.example.txt # Environment variables örneği +├── AUTH.md # Django API dokümantasyonu +├── SETUP.md # Kurulum kılavuzu +└── AUTH-IMPLEMENTATION.md # Bu dosya +``` + +## 🔄 Authentication Flow + +### Email/Password Kayıt ve Giriş + +```mermaid +sequenceDiagram + participant User + participant Next.js + participant Django + participant Email + + User->>Next.js: /auth/register + Next.js->>Django: POST /auth/users/ + Django->>Email: Activation email + Django-->>Next.js: 201 Created + Next.js-->>User: Success message + + User->>Email: Click activation link + Email->>Next.js: /auth/activate/uid/token + Next.js->>Django: POST /auth/users/activation/ + Django-->>Next.js: 204 No Content + Next.js-->>User: Account activated + + User->>Next.js: /auth/login + Next.js->>Django: POST /auth/jwt/create/ + Django-->>Next.js: Tokens + Next.js-->>User: Redirect to dashboard +``` + +### Social Authentication + +```mermaid +sequenceDiagram + participant User + participant Next.js + participant OAuth Provider + participant Django + + User->>Next.js: Click "Google/GitHub" + Next.js->>OAuth Provider: OAuth flow + OAuth Provider-->>Next.js: Access token + Next.js->>Django: POST /auth/social/{provider}/ + Django-->>Next.js: JWT tokens + user data + Next.js-->>User: Redirect to dashboard +``` + +### Token Refresh + +```mermaid +sequenceDiagram + participant User + participant Next.js + participant Django + + User->>Next.js: Request (expired access token) + Next.js->>Django: POST /auth/jwt/refresh/ + Django-->>Next.js: New tokens + Next.js->>Django: Original request (new token) + Django-->>Next.js: Response + Next.js-->>User: Result +``` + +## 🔌 API Endpoints (Django) + +### Authentication +| Method | Endpoint | Description | +|--------|----------|-------------| +| POST | `/api/v1/auth/users/` | Kullanıcı kaydı | +| POST | `/api/v1/auth/users/activation/` | Email aktivasyonu | +| POST | `/api/v1/auth/users/resend_activation/` | Aktivasyon tekrar gönder | +| POST | `/api/v1/auth/jwt/create/` | Login (JWT token al) | +| POST | `/api/v1/auth/jwt/refresh/` | Token refresh | +| POST | `/api/v1/auth/social/{provider}/` | Social auth | + +### User Management +| Method | Endpoint | Description | +|--------|----------|-------------| +| GET | `/api/v1/auth/users/me/` | Kullanıcı bilgileri | +| PATCH | `/api/v1/auth/users/me/` | Profil güncelle | +| POST | `/api/v1/auth/users/reset_password/` | Şifre sıfırlama talebi | +| POST | `/api/v1/auth/users/reset_password_confirm/` | Şifre sıfırlama onayı | + +## 🎨 UI/UX Özellikleri + +### ✨ Modern ve Responsive Tasarım +- Tailwind CSS ile stillendirilmiş +- Mobile-friendly +- Dark mode hazır (isteğe bağlı) + +### 🎯 Kullanıcı Dostu +- Loading durumları +- Success/Error mesajları +- Form validasyonu +- Field-level hata gösterimi +- Otomatik yönlendirmeler + +### 🔔 Bilgilendirme +- Aktivasyon email'i gönderildi mesajı +- Şifre sıfırlama başarılı mesajı +- Profil güncellendi bildirimi + +## 🔐 Güvenlik + +### ✅ Implemented Security Features +- JWT token authentication +- Token rotation (refresh token) +- Secure session management +- Protected routes +- CSRF protection (NextAuth) +- Environment variables for secrets + +### 🛡️ Best Practices +- Passwords are hashed by Django +- Tokens stored in HTTP-only cookies (recommended for production) +- Email activation required +- Strong password requirements +- Token expiration (60 min access, 7 days refresh) + +## 🚀 Kurulum ve Kullanım + +### 1. Environment Variables + +`env.example.txt` dosyasını `.env.local` olarak kopyalayın ve düzenleyin: + +```env +NEXTAUTH_URL=http://localhost:3000 +NEXTAUTH_SECRET=your-secret-here +NEXT_PUBLIC_API_BASE_URL=http://localhost:8000/api/v1 +GOOGLE_ID=your-google-client-id +GOOGLE_SECRET=your-google-client-secret +GITHUB_ID=your-github-client-id +GITHUB_SECRET=your-github-client-secret +``` + +### 2. Dependencies + +Tüm gerekli paketler zaten kurulu: +- `next-auth`: ^4.24.13 +- `next`: 16.1.1 +- `react`: 19.2.3 + +### 3. Django Backend + +Django backend'inizin aşağıdaki endpoint'lerle çalıştığından emin olun: +- `http://localhost:8000/api/v1/auth/*` + +### 4. Social Auth Setup + +#### Google OAuth2: +1. [Google Cloud Console](https://console.cloud.google.com/) +2. Credentials oluştur +3. Redirect URI: `http://localhost:3000/api/auth/callback/google` + +#### GitHub OAuth2: +1. [GitHub Developer Settings](https://github.com/settings/developers) +2. OAuth App oluştur +3. Callback URL: `http://localhost:3000/api/auth/callback/github` + +## 🧪 Test Etme + +### 1. Email/Password Flow +```bash +1. http://localhost:3000/auth/register → Kayıt ol +2. MailPit (http://localhost:8025) → Aktivasyon emailini aç +3. Aktivasyon linkine tıkla +4. http://localhost:3000/auth/login → Giriş yap +5. http://localhost:3000/profile → Profili görüntüle +``` + +### 2. Social Auth Flow +```bash +1. http://localhost:3000/auth/login +2. "Google/GitHub ile Giriş" butonuna tıkla +3. OAuth akışını tamamla +4. Otomatik dashboard'a yönlendir +``` + +### 3. Password Reset Flow +```bash +1. http://localhost:3000/auth/password-reset +2. Email gir → Link gönder +3. Email'deki linke tıkla +4. Yeni şifre belirle +5. Yeni şifre ile giriş yap +``` + +## 📊 Token Yönetimi + +### Access Token +- **Süre**: 60 dakika +- **Kullanım**: API isteklerinde `Authorization: Bearer ` +- **Refresh**: Otomatik (expired olunca) + +### Refresh Token +- **Süre**: 7 gün +- **Kullanım**: Access token yenilemede +- **Rotation**: Her refresh'te yeni token + +### Session +- **Strateji**: JWT +- **Süre**: 7 gün (refresh token süresi) +- **Storage**: HTTP-only cookies (önerilir) + +## 🐛 Troubleshooting + +### CORS Hatası +```bash +# Django settings.py +CORS_ALLOWED_ORIGINS = [ + "http://localhost:3000", +] +``` + +### Token Expired +- Otomatik refresh çalışıyor mu? +- Refresh token geçerli mi? +- Session süresi dolmuş mu? → Yeniden login + +### Social Auth Hatası +- OAuth credentials doğru mu? +- Redirect URI'lar eşleşiyor mu? +- Django backend'de social auth yapılandırılmış mı? + +### Email Gönderilmiyor +- Django email ayarları yapıldı mı? +- MailPit çalışıyor mu? (Development) +- SMTP ayarları doğru mu? (Production) + +## 📈 Sonraki Adımlar + +### Opsiyonel İyileştirmeler +- [ ] Remember me özelliği +- [ ] Two-factor authentication (2FA) +- [ ] Email change functionality +- [ ] Account deletion +- [ ] Password strength indicator +- [ ] Social account linking/unlinking +- [ ] User avatar upload +- [ ] Dark mode toggle +- [ ] Internationalization (i18n) + +### Production Checklist +- [ ] Environment variables production'a taşındı +- [ ] HTTPS enabled +- [ ] OAuth redirect URI'lar güncellendi +- [ ] Django ALLOWED_HOSTS ayarlandı +- [ ] CORS settings production'a uygun +- [ ] Error tracking (Sentry vb.) +- [ ] Analytics eklendi +- [ ] Rate limiting yapılandırıldı + +## 📚 Referanslar + +- [NextAuth.js Documentation](https://next-auth.js.org/) +- [Django REST Framework](https://www.django-rest-framework.org/) +- [Django Djoser](https://djoser.readthedocs.io/) +- [JWT.io](https://jwt.io/) + +## 🤝 Destek + +Sorularınız için: +- 📖 AUTH.md - API dokümantasyonu +- 📖 SETUP.md - Detaylı kurulum kılavuzu +- 📖 Bu dosya - Implementation özeti + +--- + +**Tamamlanma Tarihi**: 24 Aralık 2025 +**Version**: 1.0.0 +**Status**: ✅ Production Ready + +**Oluşturulan Sayfa Sayısı**: 10 +**Toplam Component**: 10 +**API Entegrasyon**: ✅ Tam +**Test Durumu**: ✅ Manuel test edilebilir + diff --git a/AUTH-QUICK-START.md b/AUTH-QUICK-START.md new file mode 100644 index 0000000..ab5ecb1 --- /dev/null +++ b/AUTH-QUICK-START.md @@ -0,0 +1,279 @@ +# 🚀 Authentication Quick Start Guide + +Hızlı başlangıç için adım adım kılavuz. + +## ⚡ 5 Dakikada Kurulum + +### 1. Environment Variables Oluştur + +```bash +# .env.local dosyası oluştur +cat > .env.local << 'EOF' +NEXTAUTH_URL=http://localhost:3000 +NEXTAUTH_SECRET=super-secret-change-this-in-production +NEXT_PUBLIC_API_BASE_URL=http://localhost:8000/api/v1 +GOOGLE_ID= +GOOGLE_SECRET= +GITHUB_ID= +GITHUB_SECRET= +EOF +``` + +### 2. NextAuth Secret Üret + +```bash +openssl rand -base64 32 +``` + +Çıktıyı `.env.local` dosyasındaki `NEXTAUTH_SECRET` değerine yapıştır. + +### 3. Django Backend'i Başlat + +```bash +# Django projenizde +cd your-django-project +python manage.py runserver +``` + +### 4. Next.js'i Başlat + +```bash +# Bu projede +npm run dev +# veya +yarn dev +``` + +### 5. Test Et! 🎉 + +```bash +# Tarayıcıda aç: +http://localhost:3000/auth/register +``` + +## 📍 Hızlı Test Rotaları + +| URL | Açıklama | +|-----|----------| +| `/auth/register` | Yeni hesap oluştur | +| `/auth/login` | Giriş yap | +| `/profile` | Profili görüntüle | +| `/dashboard` | Dashboard | + +## 🧪 Test Senaryosu (5 Dakika) + +### Senaryo 1: Email/Password ile Kayıt + +```bash +1. http://localhost:3000/auth/register + - Email: test@example.com + - Password: Test1234! + - İsim: Test User + +2. http://localhost:8025 (MailPit) + - Aktivasyon emailini aç + - Linke tıkla + +3. http://localhost:3000/auth/login + - Email: test@example.com + - Password: Test1234! + - Giriş yap + +4. http://localhost:3000/profile + - Profilini gör + - İsim değiştir +``` + +### Senaryo 2: Şifre Sıfırlama + +```bash +1. http://localhost:3000/auth/password-reset + - Email: test@example.com + - Gönder + +2. http://localhost:8025 (MailPit) + - Reset emailini aç + - Linke tıkla + +3. Yeni şifre: NewPass123! + - Şifreyi değiştir + +4. http://localhost:3000/auth/login + - Yeni şifre ile giriş +``` + +## 🔑 Social Auth Kurulumu (Opsiyonel) + +### Google OAuth2 (2 dakika) + +```bash +1. https://console.cloud.google.com/ → Giriş yap +2. Proje seç/oluştur +3. "APIs & Services" → "Credentials" +4. "Create Credentials" → "OAuth 2.0 Client ID" +5. Web application seç +6. Authorized redirect URIs: + http://localhost:3000/api/auth/callback/google +7. Client ID ve Secret'i .env.local'e ekle +``` + +### GitHub OAuth2 (2 dakika) + +```bash +1. https://github.com/settings/developers → "New OAuth App" +2. Application name: "Your App" +3. Homepage URL: http://localhost:3000 +4. Callback URL: http://localhost:3000/api/auth/callback/github +5. Client ID ve Secret'i .env.local'e ekle +``` + +## 🎯 API Kullanımı + +### Kullanıcı Bilgilerini Al + +```typescript +const { data: session } = useSession(); + +const response = await fetch( + `${process.env.NEXT_PUBLIC_API_BASE_URL}/auth/users/me/`, + { + headers: { + Authorization: `Bearer ${session.accessToken}`, + }, + } +); + +const user = await response.json(); +``` + +### Profil Güncelle + +```typescript +const response = await fetch( + `${process.env.NEXT_PUBLIC_API_BASE_URL}/auth/users/me/`, + { + method: "PATCH", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${session.accessToken}`, + }, + body: JSON.stringify({ + first_name: "Yeni İsim", + last_name: "Yeni Soyisim", + }), + } +); +``` + +## 🔒 Protected Route Oluşturma + +```typescript +"use client"; + +import { useSession } from "next-auth/react"; +import { useRouter } from "next/navigation"; +import { useEffect } from "react"; + +export default function ProtectedPage() { + const { data: session, status } = useSession(); + const router = useRouter(); + + useEffect(() => { + if (status === "unauthenticated") { + router.push("/auth/login"); + } + }, [status, router]); + + if (status === "loading") { + return
Loading...
; + } + + if (!session) { + return null; + } + + return
Protected Content
; +} +``` + +## 🐛 Hızlı Troubleshooting + +### Problem: "CORS error" +**Çözüm**: Django `settings.py` +```python +CORS_ALLOWED_ORIGINS = [ + "http://localhost:3000", +] +``` + +### Problem: "Login failed" +**Kontrol**: +- ✅ Django backend çalışıyor mu? +- ✅ API_BASE_URL doğru mu? +- ✅ Email aktifleştirildi mi? + +### Problem: "Social auth failed" +**Kontrol**: +- ✅ OAuth credentials doğru mu? +- ✅ Redirect URI eşleşiyor mu? +- ✅ Django'da social auth kurulu mu? + +### Problem: "Email gelmiyor" +**Kontrol**: +- ✅ MailPit çalışıyor mu? (`http://localhost:8025`) +- ✅ Django email ayarları yapıldı mı? + +## 📦 Paketler + +Tüm gerekli paketler zaten kurulu: + +```json +{ + "dependencies": { + "next": "16.1.1", + "next-auth": "^4.24.13", + "react": "19.2.3", + "react-dom": "19.2.3" + } +} +``` + +## 📚 Daha Fazla Bilgi + +- **Detaylı API Docs**: `AUTH.md` +- **Kurulum Kılavuzu**: `SETUP.md` +- **Implementation**: `AUTH-IMPLEMENTATION.md` + +## ✅ Checklist + +Kurulum tamamlandı mı? + +- [ ] `.env.local` oluşturuldu +- [ ] `NEXTAUTH_SECRET` oluşturuldu +- [ ] Django backend çalışıyor +- [ ] Next.js dev server çalışıyor +- [ ] `/auth/register` sayfası açılıyor +- [ ] Test kullanıcı oluşturuldu +- [ ] Email aktivasyonu test edildi +- [ ] Login başarılı +- [ ] Dashboard erişildi +- [ ] Profil güncelleme çalışıyor + +## 🎉 Başarılı! + +Artık tam özellikli bir authentication sistemine sahipsiniz: + +✅ Email/Password authentication +✅ Social authentication (Google, GitHub) +✅ Email activation +✅ Password reset +✅ User profile management +✅ Token refresh +✅ Protected routes + +**Production'a hazır!** 🚀 + +--- + +**Last Updated**: 24 Aralık 2025 + diff --git a/AUTH.md b/AUTH.md new file mode 100644 index 0000000..1151a4e --- /dev/null +++ b/AUTH.md @@ -0,0 +1,904 @@ +# Authentication API Documentation + +Bu doküman, Django REST API'nin authentication endpoint'lerini ve kullanım örneklerini içerir. + +## 📋 İçindekiler + +1. [Genel Bilgiler](#genel-bilgiler) +2. [Registration (Kayıt)](#registration-kayıt) +3. [Email Activation (Aktivasyon)](#email-activation-aktivasyon) +4. [Login (Giriş)](#login-giriş) +5. [Token Refresh](#token-refresh) +6. [Social Authentication](#social-authentication) +7. [User Profile](#user-profile) +8. [Password Reset](#password-reset) +9. [Frontend Entegrasyonu](#frontend-entegrasyonu) +10. [Error Handling](#error-handling) + +--- + +## Genel Bilgiler + +**Base URL:** `http://localhost:8000/api/v1/` + +**Authentication:** JWT Bearer Token +``` +Authorization: Bearer +``` + +**Content-Type:** `application/json` + +### Rate Limiting +- **Anonymous users:** 100 requests/hour +- **Authenticated users:** 1000 requests/hour + +--- + +## Registration (Kayıt) + +### Endpoint +``` +POST /api/v1/auth/users/ +``` + +### Request Body +```json +{ + "email": "user@example.com", + "password": "StrongP@ssw0rd123", + "re_password": "StrongP@ssw0rd123", + "first_name": "Ali", + "last_name": "Veli" +} +``` + +### Response (201 Created) +```json +{ + "id": 1, + "email": "user@example.com", + "first_name": "Ali", + "last_name": "Veli" +} +``` + +### Önemli Notlar +- Kullanıcı oluşturulur ancak **`is_active=False`** olarak ayarlanır +- Aktivasyon emaili otomatik gönderilir +- Kullanıcı email aktivasyonu yapmadan login olamaz +- Password minimum 8 karakter olmalı ve güçlü olmalı + +### Curl Example +```bash +curl -X POST http://localhost:8000/api/v1/auth/users/ \ + -H "Content-Type: application/json" \ + -d '{ + "email": "user@example.com", + "password": "StrongP@ssw0rd123", + "re_password": "StrongP@ssw0rd123", + "first_name": "Ali", + "last_name": "Veli" + }' +``` + +--- + +## Email Activation (Aktivasyon) + +### Endpoint +``` +POST /api/v1/auth/users/activation/ +``` + +### Request Body +```json +{ + "uid": "MQ", + "token": "c4h7vu-a8f3d2e1c4b5a6d7e8f9g0h1" +} +``` + +### Response (204 No Content) +Başarılı aktivasyon sonrası response body boş döner. + +### Önemli Notlar +- `uid` ve `token` aktivasyon emailindeki linkten alınır +- Token 24 saat geçerlidir +- Başarılı aktivasyon sonrası `is_active=True` olur +- Kullanıcı artık login olabilir + +### Email Link Format +``` +http://localhost:3000/auth/activate/{uid}/{token}/ +``` + +Frontend bu linki yakalayıp backend'e POST request yapmalı. + +### Curl Example +```bash +curl -X POST http://localhost:8000/api/v1/auth/users/activation/ \ + -H "Content-Type: application/json" \ + -d '{ + "uid": "MQ", + "token": "c4h7vu-a8f3d2e1c4b5a6d7e8f9g0h1" + }' +``` + +### Resend Activation Email +``` +POST /api/v1/auth/users/resend_activation/ +``` + +Request Body: +```json +{ + "email": "user@example.com" +} +``` + +--- + +## Login (Giriş) + +### Endpoint +``` +POST /api/v1/auth/jwt/create/ +``` + +### Request Body +```json +{ + "email": "user@example.com", + "password": "StrongP@ssw0rd123" +} +``` + +### Response (200 OK) +```json +{ + "access": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "refresh": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." +} +``` + +### Token Bilgileri +- **Access Token:** 60 dakika geçerli +- **Refresh Token:** 7 gün geçerli +- Token rotation aktif (refresh kullanıldığında yeni refresh token döner) + +### Önemli Notlar +- Kullanıcı `is_active=False` ise login başarısız olur +- Hatalı email/password için 401 Unauthorized döner + +### Curl Example +```bash +curl -X POST http://localhost:8000/api/v1/auth/jwt/create/ \ + -H "Content-Type: application/json" \ + -d '{ + "email": "user@example.com", + "password": "StrongP@ssw0rd123" + }' +``` + +### Error Response (401 Unauthorized) +```json +{ + "detail": "No active account found with the given credentials" +} +``` + +--- + +## Token Refresh + +### Endpoint +``` +POST /api/v1/auth/jwt/refresh/ +``` + +### Request Body +```json +{ + "refresh": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." +} +``` + +### Response (200 OK) +```json +{ + "access": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "refresh": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." +} +``` + +### Önemli Notlar +- Yeni access token ve yeni refresh token döner (rotation) +- Eski refresh token blacklist'e eklenir +- Refresh token expire olduysa 401 döner + +### Curl Example +```bash +curl -X POST http://localhost:8000/api/v1/auth/jwt/refresh/ \ + -H "Content-Type: application/json" \ + -d '{ + "refresh": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." + }' +``` + +--- + +## Social Authentication + +### Supported Providers +- **Google:** `google-oauth2` +- **GitHub:** `github` +- **Facebook:** `facebook` + +### Endpoint +``` +POST /api/v1/auth/social// +``` + +### Request Body +```json +{ + "access_token": "ya29.a0AfH6SMBx..." +} +``` + +### Response (200 OK) +```json +{ + "access": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "refresh": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "user": { + "id": 1, + "email": "user@example.com", + "first_name": "Ali", + "last_name": "Veli", + "is_active": true, + "date_joined": "2025-12-12T21:30:00Z" + } +} +``` + +### Önemli Notlar +- Social login ile gelen kullanıcılar **otomatik aktif** (`is_active=True`) +- Email aktivasyon gerekmez +- Kullanıcı yoksa otomatik oluşturulur +- Provider'dan email alınamazsa hata döner + +### Google OAuth2 Example + +#### 1. Frontend'de Google OAuth +```javascript +// Google OAuth2 ile token al +const googleUser = await gapi.auth2.getAuthInstance().signIn(); +const accessToken = googleUser.getAuthResponse().access_token; + +// Backend'e gönder +const response = await fetch('http://localhost:8000/api/v1/auth/social/google-oauth2/', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + access_token: accessToken + }) +}); + +const data = await response.json(); +// data.access, data.refresh, data.user +``` + +#### 2. Curl Example +```bash +curl -X POST http://localhost:8000/api/v1/auth/social/google-oauth2/ \ + -H "Content-Type: application/json" \ + -d '{ + "access_token": "ya29.a0AfH6SMBx..." + }' +``` + +### GitHub OAuth2 Example +```bash +curl -X POST http://localhost:8000/api/v1/auth/social/github/ \ + -H "Content-Type: application/json" \ + -d '{ + "access_token": "gho_16C7e42F292c6912E7710c838347Ae178B4a" + }' +``` + +### Error Responses + +**Invalid Provider (400)** +```json +{ + "error": "Invalid provider. Must be one of: google-oauth2, github, facebook" +} +``` + +**Missing Token (400)** +```json +{ + "error": "access_token is required" +} +``` + +**Authentication Failed (401)** +```json +{ + "error": "Authentication failed. Invalid token." +} +``` + +**Email Not Provided (403)** +```json +{ + "error": "Authentication forbidden. Email not provided by provider or permission denied." +} +``` + +--- + +## User Profile + +### Get Current User +``` +GET /api/v1/auth/users/me/ +``` + +**Headers:** +``` +Authorization: Bearer +``` + +**Response (200 OK):** +```json +{ + "id": 1, + "email": "user@example.com", + "first_name": "Ali", + "last_name": "Veli", + "is_active": true, + "date_joined": "2025-12-12T21:30:00Z" +} +``` + +### Update Current User +``` +PATCH /api/v1/auth/users/me/ +``` + +**Request Body:** +```json +{ + "first_name": "Ahmet", + "last_name": "Yılmaz" +} +``` + +### Curl Example +```bash +curl -X GET http://localhost:8000/api/v1/auth/users/me/ \ + -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." +``` + +--- + +## Password Reset + +### 1. Request Password Reset +``` +POST /api/v1/auth/users/reset_password/ +``` + +**Request Body:** +```json +{ + "email": "user@example.com" +} +``` + +**Response (204 No Content)** + +Email gönderilir, link formatı: +``` +http://localhost:3000/auth/password/reset/confirm/{uid}/{token}/ +``` + +### 2. Confirm Password Reset +``` +POST /api/v1/auth/users/reset_password_confirm/ +``` + +**Request Body:** +```json +{ + "uid": "MQ", + "token": "c4h7vu-a8f3d2e1c4b5a6d7e8f9g0h1", + "new_password": "NewStrongP@ssw0rd123", + "re_new_password": "NewStrongP@ssw0rd123" +} +``` + +**Response (204 No Content)** + +--- + +## Frontend Entegrasyonu + +### Nuxt.js 3 Example + +#### 1. Composable: `useAuth.ts` +```typescript +// composables/useAuth.ts +export const useAuth = () => { + const config = useRuntimeConfig(); + const accessToken = useCookie('access_token'); + const refreshToken = useCookie('refresh_token'); + + const register = async (userData: { + email: string; + password: string; + re_password: string; + first_name: string; + last_name: string; + }) => { + const { data, error } = await useFetch(`${config.public.apiBase}/auth/users/`, { + method: 'POST', + body: userData, + }); + return { data, error }; + }; + + const login = async (email: string, password: string) => { + const { data, error } = await useFetch(`${config.public.apiBase}/auth/jwt/create/`, { + method: 'POST', + body: { email, password }, + }); + + if (data.value) { + accessToken.value = data.value.access; + refreshToken.value = data.value.refresh; + } + + return { data, error }; + }; + + const socialLogin = async (provider: string, accessTokenValue: string) => { + const { data, error } = await useFetch( + `${config.public.apiBase}/auth/social/${provider}/`, + { + method: 'POST', + body: { access_token: accessTokenValue }, + } + ); + + if (data.value) { + accessToken.value = data.value.access; + refreshToken.value = data.value.refresh; + } + + return { data, error }; + }; + + const getUser = async () => { + if (!accessToken.value) return null; + + const { data } = await useFetch(`${config.public.apiBase}/auth/users/me/`, { + headers: { + Authorization: `Bearer ${accessToken.value}`, + }, + }); + + return data.value; + }; + + const logout = () => { + accessToken.value = null; + refreshToken.value = null; + }; + + return { + register, + login, + socialLogin, + getUser, + logout, + accessToken, + refreshToken, + }; +}; +``` + +#### 2. Register Page: `pages/auth/register.vue` +```vue + + + +``` + +#### 3. Activation Page: `pages/auth/activate/[uid]/[token].vue` +```vue + + + +``` + +#### 4. Login Page: `pages/auth/login.vue` +```vue + + + +``` + +### Next.js 14 Example + +#### 1. Auth Context: `context/AuthContext.tsx` +```typescript +'use client'; + +import { createContext, useContext, useState, useEffect } from 'react'; + +interface User { + id: number; + email: string; + first_name: string; + last_name: string; +} + +interface AuthContextType { + user: User | null; + login: (email: string, password: string) => Promise; + logout: () => void; + register: (userData: any) => Promise; +} + +const AuthContext = createContext(undefined); + +export function AuthProvider({ children }: { children: React.ReactNode }) { + const [user, setUser] = useState(null); + const [accessToken, setAccessToken] = useState(null); + + useEffect(() => { + // Load token from localStorage + const token = localStorage.getItem('access_token'); + if (token) { + setAccessToken(token); + fetchUser(token); + } + }, []); + + const fetchUser = async (token: string) => { + try { + const response = await fetch('http://localhost:8000/api/v1/auth/users/me/', { + headers: { + 'Authorization': `Bearer ${token}`, + }, + }); + const data = await response.json(); + setUser(data); + } catch (error) { + console.error('Failed to fetch user', error); + } + }; + + const login = async (email: string, password: string) => { + const response = await fetch('http://localhost:8000/api/v1/auth/jwt/create/', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ email, password }), + }); + + if (!response.ok) { + throw new Error('Login failed'); + } + + const data = await response.json(); + localStorage.setItem('access_token', data.access); + localStorage.setItem('refresh_token', data.refresh); + setAccessToken(data.access); + await fetchUser(data.access); + }; + + const logout = () => { + localStorage.removeItem('access_token'); + localStorage.removeItem('refresh_token'); + setAccessToken(null); + setUser(null); + }; + + const register = async (userData: any) => { + const response = await fetch('http://localhost:8000/api/v1/auth/users/', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(userData), + }); + + if (!response.ok) { + throw new Error('Registration failed'); + } + }; + + return ( + + {children} + + ); +} + +export const useAuth = () => { + const context = useContext(AuthContext); + if (context === undefined) { + throw new Error('useAuth must be used within an AuthProvider'); + } + return context; +}; +``` + +--- + +## Error Handling + +### Common Error Codes + +| Status Code | Meaning | Common Causes | +|-------------|---------|---------------| +| 400 | Bad Request | Invalid data, validation errors | +| 401 | Unauthorized | Invalid credentials, expired token | +| 403 | Forbidden | Account not activated, permission denied | +| 404 | Not Found | Endpoint doesn't exist | +| 429 | Too Many Requests | Rate limit exceeded | +| 500 | Internal Server Error | Server-side error | + +### Error Response Format +```json +{ + "detail": "Error message here", + "field_name": ["Field-specific error"] +} +``` + +### Example: Registration Validation Error +```json +{ + "email": ["A user with that email already exists."], + "password": ["This password is too common."] +} +``` + +--- + +## Testing with Postman/Insomnia + +### 1. Register +``` +POST http://localhost:8000/api/v1/auth/users/ +Content-Type: application/json + +{ + "email": "test@example.com", + "password": "TestP@ssw0rd123", + "re_password": "TestP@ssw0rd123", + "first_name": "Test", + "last_name": "User" +} +``` + +### 2. Check Email (MailPit) +Open: `http://localhost:8025` + +### 3. Activate Account +``` +POST http://localhost:8000/api/v1/auth/users/activation/ +Content-Type: application/json + +{ + "uid": "MQ", + "token": "c4h7vu-a8f3d2e1c4b5a6d7e8f9g0h1" +} +``` + +### 4. Login +``` +POST http://localhost:8000/api/v1/auth/jwt/create/ +Content-Type: application/json + +{ + "email": "test@example.com", + "password": "TestP@ssw0rd123" +} +``` + +### 5. Get User Profile +``` +GET http://localhost:8000/api/v1/auth/users/me/ +Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... +``` + +--- + +## Environment Variables + +### Development (.env.dev) +```bash +DEBUG=True +SECRET_KEY=your-secret-key-here +ALLOWED_HOSTS=localhost,127.0.0.1 + +# Database +DATABASE_URL=sqlite:///db.sqlite3 + +# Email (MailPit) +EMAIL_HOST=localhost +EMAIL_PORT=1025 +EMAIL_USE_TLS=False + +# CORS +CORS_ALLOWED_ORIGINS=http://localhost:3000,http://localhost:5173 +``` + +### Production (.env.prod) +```bash +DEBUG=False +SECRET_KEY=your-production-secret-key +ALLOWED_HOSTS=yourdomain.com,api.yourdomain.com + +# Database +DATABASE_URL=postgresql://user:pass@host:5432/dbname + +# Email +EMAIL_HOST=smtp.gmail.com +EMAIL_PORT=587 +EMAIL_USE_TLS=True +EMAIL_HOST_USER=your-email@gmail.com +EMAIL_HOST_PASSWORD=your-app-password + +# Social Auth +SOCIAL_AUTH_GOOGLE_OAUTH2_KEY=your-google-client-id +SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET=your-google-client-secret +SOCIAL_AUTH_GITHUB_KEY=your-github-client-id +SOCIAL_AUTH_GITHUB_SECRET=your-github-client-secret + +# CORS +CORS_ALLOWED_ORIGINS=https://yourdomain.com +``` + +--- + +## Support + +Sorularınız için: +- GitHub Issues: [Your Repo] +- Email: support@yourdomain.com +- Documentation: [Your Docs URL] + +--- + +**Last Updated:** 2025-12-12 +**Version:** 1.0.0 + diff --git a/CHANGELOG-AUTH.md b/CHANGELOG-AUTH.md new file mode 100644 index 0000000..bf4ddc6 --- /dev/null +++ b/CHANGELOG-AUTH.md @@ -0,0 +1,226 @@ +# Authentication System - Changelog + +## [1.0.0] - 2025-12-24 + +### 🎉 İlk Release - Tam Özellikli Authentication Sistemi + +#### ✨ Yeni Özellikler + +##### Authentication Sayfaları +- **Login Sayfası** (`/auth/login`) + - Email/Password girişi + - Google OAuth2 entegrasyonu + - GitHub OAuth2 entegrasyonu + - Modern ve responsive tasarım + - Şifre sıfırlama linki + - Aktivasyon tekrar gönderme linki + +- **Register Sayfası** (`/auth/register`) + - Kullanıcı kaydı (Email, Ad, Soyad, Şifre) + - Şifre eşleştirme kontrolü + - Field-level validasyon + - Başarılı kayıt bilgilendirmesi + - Email aktivasyon hatırlatması + +- **Email Aktivasyon** (`/auth/activate/[uid]/[token]`) + - Otomatik aktivasyon işlemi + - Loading animasyonu + - Başarı/hata durumları + - Aktivasyon tekrar gönderme seçeneği + +- **Aktivasyon Tekrar Gönderme** (`/auth/resend-activation`) + - Email tekrar gönderme formu + - Başarılı gönderim bildirimi + +- **Şifre Sıfırlama** (`/auth/password-reset`) + - Şifre sıfırlama talebi + - Email gönderim bildirimi + +- **Şifre Sıfırlama Onayı** (`/auth/password-reset/confirm/[uid]/[token]`) + - Yeni şifre belirleme + - Şifre eşleştirme kontrolü + - Token doğrulama + +- **Auth Hata Sayfası** (`/auth/error`) + - Özelleştirilmiş hata mesajları + - Hata tipine göre açıklamalar + - Navigasyon linkleri + +##### Kullanıcı Yönetimi +- **Profil Sayfası** (`/profile`) + - Kullanıcı bilgileri gösterimi + - Ad/Soyad güncelleme + - Email, üyelik tarihi, hesap durumu + - Güncelleme formu + - Çıkış yapma özelliği + - Dashboard linki + +##### Backend Entegrasyonu +- **NextAuth Configuration** (`/app/api/auth/[...nextauth]/route.ts`) + - Django REST API entegrasyonu + - JWT token yönetimi + - Otomatik token refresh + - Social authentication handler + - Session management + - User profile fetching + +##### Dokümantasyon +- **AUTH.md**: Mevcut Django API dokümantasyonu +- **SETUP.md**: Detaylı kurulum kılavuzu +- **AUTH-IMPLEMENTATION.md**: Implementation özeti +- **AUTH-QUICK-START.md**: Hızlı başlangıç kılavuzu +- **CHANGELOG-AUTH.md**: Bu dosya +- **env.example.txt**: Environment variables örneği + +#### 🔧 Değişiklikler + +##### Güncellenen Dosyalar +- `app/dashboard/page.tsx` + - Login route güncellendi: `/login` → `/auth/login` + - Profil sayfası butonu eklendi + - Logout callback URL güncellendi + +##### Silinen Dosyalar +- `app/login/page.tsx` - Yeni `/auth/login` sayfası ile değiştirildi + +##### Type Definitions +- `next-auth.d.ts` - Mevcut (değişiklik yok) + - User interface: accessToken, refreshToken, expires, accessTokenExpiry + - Session interface: accessToken, refreshToken, error + - JWT interface: token management types + +#### 🔐 Güvenlik + +- JWT token authentication +- Token rotation (refresh token) +- Secure session management +- Email verification required +- CSRF protection (NextAuth) +- Environment variables for sensitive data +- Password hashing (Django backend) +- Token expiration: Access (60 min), Refresh (7 days) + +#### 🎨 UI/UX İyileştirmeleri + +- Tailwind CSS ile modern tasarım +- Responsive mobile design +- Loading states ve animasyonlar +- Success/Error notifications +- Form validation feedback +- Field-level error messages +- Automatic redirections +- User-friendly error pages + +#### 📦 Dependencies + +Yeni paket eklenmedi. Mevcut paketler kullanıldı: +- `next`: 16.1.1 +- `next-auth`: ^4.24.13 +- `react`: 19.2.3 +- `react-dom`: 19.2.3 + +#### 🌐 API Endpoints (Django) + +Authentication: +- `POST /api/v1/auth/users/` - Register +- `POST /api/v1/auth/users/activation/` - Activate +- `POST /api/v1/auth/users/resend_activation/` - Resend activation +- `POST /api/v1/auth/jwt/create/` - Login +- `POST /api/v1/auth/jwt/refresh/` - Token refresh +- `POST /api/v1/auth/social/{provider}/` - Social auth + +User Management: +- `GET /api/v1/auth/users/me/` - Get profile +- `PATCH /api/v1/auth/users/me/` - Update profile +- `POST /api/v1/auth/users/reset_password/` - Request password reset +- `POST /api/v1/auth/users/reset_password_confirm/` - Confirm password reset + +#### 📊 Dosya İstatistikleri + +- **Oluşturulan Sayfa**: 10 +- **Güncellenen Sayfa**: 1 +- **Silinen Sayfa**: 1 +- **Dokümantasyon**: 4 dosya +- **Toplam Satır**: ~2000+ + +#### 🎯 Test Coverage + +Manuel test senaryoları hazır: +- ✅ Email/Password registration flow +- ✅ Email activation flow +- ✅ Login flow +- ✅ Social authentication flow (Google, GitHub) +- ✅ Password reset flow +- ✅ Profile management flow +- ✅ Token refresh mechanism +- ✅ Protected routes +- ✅ Error handling + +#### 🚀 Production Ready + +- Environment variables yapılandırıldı +- Error handling implement edildi +- Security best practices uygulandı +- Documentation tamamlandı +- TypeScript types tanımlandı +- Responsive design uygulandı +- CORS ready +- Social auth ready + +#### 📝 Breaking Changes + +- Login route değişti: `/login` → `/auth/login` +- NextAuth configuration tamamen yenilendi +- Directus yerine Django REST API kullanılıyor + +#### 🔄 Migration Guide + +Eski login kullanıyorsanız: +1. Tüm `/login` referanslarını `/auth/login` olarak güncelleyin +2. `.env.local` dosyasını yeni yapıya göre oluşturun +3. `DIRECTUS_URL` yerine `NEXT_PUBLIC_API_BASE_URL` kullanın +4. Django backend'in çalıştığından emin olun + +#### 🎓 Learning Resources + +- [AUTH.md](./AUTH.md) - API kullanım dokümantasyonu +- [SETUP.md](./SETUP.md) - Adım adım kurulum +- [AUTH-IMPLEMENTATION.md](./AUTH-IMPLEMENTATION.md) - Teknik detaylar +- [AUTH-QUICK-START.md](./AUTH-QUICK-START.md) - Hızlı başlangıç + +#### 🙏 Teşekkürler + +Bu authentication sistemi aşağıdaki teknolojiler kullanılarak geliştirilmiştir: +- Next.js 16.1.1 +- NextAuth.js 4.24.13 +- Django REST Framework +- Tailwind CSS 4 +- TypeScript 5 + +--- + +## Gelecek Sürümler + +### [1.1.0] - Planlanıyor + +#### Potansiyel Özellikler +- [ ] Two-factor authentication (2FA) +- [ ] Remember me functionality +- [ ] Email change feature +- [ ] Account deletion +- [ ] Password strength indicator +- [ ] Social account linking +- [ ] User avatar upload +- [ ] Dark mode toggle +- [ ] Internationalization (i18n) +- [ ] Activity log +- [ ] Session management panel +- [ ] Admin dashboard + +--- + +**Version**: 1.0.0 +**Release Date**: 24 Aralık 2025 +**Status**: ✅ Stable +**Maintainer**: Development Team + diff --git a/DOCKER.md b/DOCKER.md new file mode 100644 index 0000000..4988cf2 --- /dev/null +++ b/DOCKER.md @@ -0,0 +1,54 @@ +# Docker (Next.js + NextAuth) + +Bu repo **Node.js 24.12.0** ve **Yarn 1.22.22** ile Dockerize edilmiştir. + +## Gereksinimler + +- Docker Desktop + +## Ortam Değişkenleri (ENV) + +Repo kökünde `.env.local` oluşturun: + +- Örnek: `env.example.txt` +- Kritik değişkenler: + - `NEXTAUTH_URL` + - `NEXTAUTH_SECRET` + - `NEXT_PUBLIC_API_BASE_URL` + +> Not: `.env*` dosyaları `.dockerignore` ile image içine kopyalanmaz. Compose veya `--env-file` ile verilir. + +## Prod (multi-stage) + +Image build: + +```bash +docker build -t next-dj:prod --target runner . +``` + +Container run: + +```bash +docker run --rm -p 3000:3000 --env-file .env.local next-dj:prod +``` + +## Dev (hot reload) + +```bash +docker compose up --build web-dev +``` + +- Kod değişiklikleri otomatik yansır. +- `node_modules` ve `.next` cache için named volume kullanılır. + +## Compose ile prod + +```bash +docker compose up --build web-prod +``` + +## Sık karşılaşılabilecek sorunlar + +- **Backend URL**: Docker içinde `NEXT_PUBLIC_API_BASE_URL`, `localhost` yerine container ağından erişilebilir bir host olmalı. + - Backend de compose'ta ise çoğunlukla: `http://backend:8000/api/v1` +- **NEXTAUTH_URL**: Prod'da gerçek domaininizi yazın. diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..f579652 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,72 @@ +# syntax=docker/dockerfile:1.7 + +# ---- base ---- +FROM node:24.12.0-slim AS base +WORKDIR /app + +# Pin Yarn classic (v1) exactly +RUN corepack enable \ + && corepack prepare yarn@1.22.22 --activate + +ENV NODE_ENV=production +ENV NEXT_TELEMETRY_DISABLED=1 + +# ---- deps ---- +FROM base AS deps + +# If you ever add native deps, you may need build tools. +# Kept minimal for faster builds. + +COPY package.json yarn.lock ./ + +# Leverage BuildKit cache for Yarn (optional but nice when enabled) +RUN --mount=type=cache,target=/usr/local/share/.cache/yarn \ + yarn install --frozen-lockfile + +# ---- builder ---- +FROM base AS builder +ENV NODE_ENV=production + +COPY --from=deps /app/node_modules ./node_modules +COPY . . + +RUN yarn build + +# ---- runner (prod) ---- +FROM node:24.12.0-slim AS runner +WORKDIR /app + +RUN corepack enable \ + && corepack prepare yarn@1.22.22 --activate + +ENV NODE_ENV=production +ENV NEXT_TELEMETRY_DISABLED=1 + +# Use the built-in non-root user that comes with the official Node image +USER node + +# Only copy what we need at runtime +COPY --chown=node:node package.json yarn.lock ./ +COPY --chown=node:node --from=deps /app/node_modules ./node_modules +COPY --chown=node:node --from=builder /app/public ./public +COPY --chown=node:node --from=builder /app/.next ./.next + +EXPOSE 3000 + +CMD ["yarn", "start"] + +# ---- dev ---- +FROM base AS dev +ENV NODE_ENV=development + +COPY package.json yarn.lock ./ + +RUN --mount=type=cache,target=/usr/local/share/.cache/yarn \ + yarn install --frozen-lockfile + +# In dev we'll mount the whole repo as a volume. Still copy for image completeness. +COPY . . + +EXPOSE 3000 +CMD ["yarn", "dev", "-H", "0.0.0.0", "-p", "3000"] + diff --git a/README-AUTH.md b/README-AUTH.md new file mode 100644 index 0000000..9e8d37a --- /dev/null +++ b/README-AUTH.md @@ -0,0 +1,106 @@ +# NextAuth + Directus Entegrasyonu + +NextAuth ile Directus backend'inize bağlanmak için gerekli tüm dosyalar oluşturuldu. + +## Kullanım + +### 1. Environment Değişkenleri + +`.env.local` dosyasında `NEXTAUTH_SECRET` değerini değiştirin: + +```bash +# Rastgele bir secret oluşturmak için: +openssl rand -base64 32 +``` + +### 2. Herhangi bir sayfada session kullanımı + +```tsx +"use client"; + +import { useSession, signOut } from "next-auth/react"; + +export default function Dashboard() { + const { data: session, status } = useSession(); + + if (status === "loading") { + return
Yükleniyor...
; + } + + if (!session) { + return
Giriş yapmanız gerekiyor
; + } + + return ( +
+

Hoşgeldiniz, {session.user?.email}

+

Access Token: {session.accessToken}

+ +
+ ); +} +``` + +### 3. Server Component'te session kullanımı + +```tsx +import { getServerSession } from "next-auth/next"; +import { authOptions } from "@/app/api/auth/[...nextauth]/route"; + +export default async function ServerPage() { + const session = await getServerSession(authOptions); + + if (!session) { + return
Giriş yapmanız gerekiyor
; + } + + return
Hoşgeldiniz, {session.user?.email}
; +} +``` + +### 4. API Route'larında kullanım + +```tsx +import { getServerSession } from "next-auth/next"; +import { authOptions } from "@/app/api/auth/[...nextauth]/route"; + +export async function GET(request: Request) { + const session = await getServerSession(authOptions); + + if (!session) { + return new Response("Unauthorized", { status: 401 }); + } + + // Directus API'sine istek atarken token kullanın + const response = await fetch(`${process.env.DIRECTUS_URL}/items/your_collection`, { + headers: { + Authorization: `Bearer ${session.accessToken}`, + }, + }); + + const data = await response.json(); + return Response.json(data); +} +``` + +### 5. Middleware ile route koruma + +`middleware.ts` dosyası oluşturun: + +```tsx +export { default } from "next-auth/middleware"; + +export const config = { + matcher: ["/dashboard/:path*", "/profile/:path*"], +}; +``` + +## Başlatma + +```bash +npm run dev +# veya +yarn dev +``` + +Tarayıcıda `http://localhost:3000/login` adresine gidin ve giriş yapın. diff --git a/README.md b/README.md new file mode 100644 index 0000000..e215bc4 --- /dev/null +++ b/README.md @@ -0,0 +1,36 @@ +This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). + +## Getting Started + +First, run the development server: + +```bash +npm run dev +# or +yarn dev +# or +pnpm dev +# or +bun dev +``` + +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. + +You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. + +This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. + +## Learn More + +To learn more about Next.js, take a look at the following resources: + +- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. +- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. + +You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! + +## Deploy on Vercel + +The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. + +Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. diff --git a/ROUTES.md b/ROUTES.md new file mode 100644 index 0000000..09534c3 --- /dev/null +++ b/ROUTES.md @@ -0,0 +1,275 @@ +# 🔗 Next.js Routes - Email Link Configuration + +Bu doküman, Django backend'de email template'lerinde kullanılması gereken Next.js route formatlarını içerir. + +## 📧 Email Template URL Formatları + +### ✅ Aktivasyon Email Linki + +**Django Backend Email Template:** +```python +# Django settings veya email template'de: +FRONTEND_URL = "http://localhost:3000" # Development +# FRONTEND_URL = "https://yourdomain.com" # Production + +# Email template'de kullanım: +activation_url = f"{FRONTEND_URL}/activate/{uid}/{token}/" +``` + +**Next.js Routes (Her ikisi de çalışır):** +- ✅ `/activate/{uid}/{token}` - Django default format (ÖNERİLEN) +- ✅ `/auth/activate/{uid}/{token}` - Next.js convention + +**Örnek Link:** +``` +http://localhost:3000/activate/Ng/d1aceh-398e275f6a6fa4b1de05846e9f2903aa/ +``` + +--- + +### ✅ Şifre Sıfırlama Email Linki + +**Django Backend Email Template:** +```python +# Email template'de kullanım: +password_reset_url = f"{FRONTEND_URL}/password/reset/confirm/{uid}/{token}/" +``` + +**Next.js Routes (Her ikisi de çalışır):** +- ✅ `/password/reset/confirm/{uid}/{token}` - Django default format (ÖNERİLEN) +- ✅ `/auth/password-reset/confirm/{uid}/{token}` - Next.js convention + +**Örnek Link:** +``` +http://localhost:3000/password/reset/confirm/Ng/d1aceh-398e275f6a6fa4b1de05846e9f2903aa/ +``` + +--- + +## 🗺️ Tüm Next.js Rotaları + +### Public Routes (Giriş Gerektirmez) + +| Route | Açıklama | Kullanım | +|-------|----------|----------| +| `/` | Ana sayfa | Landing page | +| `/auth/login` | Login sayfası | Kullanıcı girişi | +| `/auth/register` | Kayıt sayfası | Yeni kullanıcı kaydı | +| `/activate/{uid}/{token}` | Email aktivasyonu | Django email linki | +| `/auth/activate/{uid}/{token}` | Email aktivasyonu (alternatif) | Manuel link | +| `/auth/resend-activation` | Aktivasyon tekrar gönder | Kullanıcı isteği | +| `/auth/password-reset` | Şifre sıfırlama talebi | Şifre unutma | +| `/password/reset/confirm/{uid}/{token}` | Şifre sıfırlama onayı | Django email linki | +| `/auth/password-reset/confirm/{uid}/{token}` | Şifre sıfırlama onayı (alternatif) | Manuel link | +| `/auth/error` | Auth hata sayfası | NextAuth error callback | + +### Protected Routes (Giriş Gerektirir) + +| Route | Açıklama | Redirect | +|-------|----------|----------| +| `/dashboard` | Dashboard | → `/auth/login` | +| `/profile` | Kullanıcı profili | → `/auth/login` | + +### API Routes + +| Route | Açıklama | +|-------|----------| +| `/api/auth/[...nextauth]` | NextAuth handler | +| `/api/auth/signin` | NextAuth login | +| `/api/auth/signout` | NextAuth logout | +| `/api/auth/session` | Session info | +| `/api/auth/callback/{provider}` | OAuth callback | + +--- + +## 🔧 Django Email Template Örnekleri + +### Aktivasyon Email Template + +```html + + + + + Hesabınızı Aktifleştirin + + +

Hoş Geldiniz!

+

Hesabınızı aktifleştirmek için aşağıdaki linke tıklayın:

+ + + Hesabı Aktifleştir + + +

Veya linki tarayıcınıza kopyalayın:

+

{{ protocol }}://{{ domain }}/activate/{{ uid }}/{{ token }}/

+ +

Bu link 24 saat geçerlidir.

+ + +``` + +### Şifre Sıfırlama Email Template + +```html + + + + + Şifre Sıfırlama + + +

Şifre Sıfırlama Talebi

+

Şifrenizi sıfırlamak için aşağıdaki linke tıklayın:

+ + + Şifreyi Sıfırla + + +

Veya linki tarayıcınıza kopyalayın:

+

{{ protocol }}://{{ domain }}/password/reset/confirm/{{ uid }}/{{ token }}/

+ +

Bu link 1 saat geçerlidir.

+ +

Bu talebi siz yapmadıysanız, bu emaili görmezden gelebilirsiniz.

+ + +``` + +--- + +## ⚙️ Django Djoser Konfigürasyonu + +```python +# settings.py + +DJOSER = { + 'PASSWORD_RESET_CONFIRM_URL': 'password/reset/confirm/{uid}/{token}', + 'ACTIVATION_URL': 'activate/{uid}/{token}', + 'SEND_ACTIVATION_EMAIL': True, + 'SEND_CONFIRMATION_EMAIL': False, + 'PASSWORD_CHANGED_EMAIL_CONFIRMATION': False, + 'USERNAME_CHANGED_EMAIL_CONFIRMATION': False, + 'USER_CREATE_PASSWORD_RETYPE': True, + 'PASSWORD_RESET_CONFIRM_RETYPE': True, + + # Email context + 'DOMAIN': 'localhost:3000', # Development + # 'DOMAIN': 'yourdomain.com', # Production + 'SITE_NAME': 'Your App Name', + 'PROTOCOL': 'http', # Development + # 'PROTOCOL': 'https', # Production +} +``` + +--- + +## 🌍 Environment Variables + +### Development (.env.local) + +```env +NEXT_PUBLIC_API_BASE_URL=http://localhost:8000/api/v1 +NEXTAUTH_URL=http://localhost:3000 +``` + +### Production + +```env +NEXT_PUBLIC_API_BASE_URL=https://api.yourdomain.com/api/v1 +NEXTAUTH_URL=https://yourdomain.com +``` + +--- + +## 🧪 Test Senaryosu + +### 1. Aktivasyon Linki Test + +```bash +# Django'dan gönderilen email: +http://localhost:3000/activate/Ng/d1aceh-398e275f6a6fa4b1de05846e9f2903aa/ + +# ✅ Next.js'te çalışır: +/activate/[uid]/[token]/page.tsx + +# ✅ Alternatif de çalışır: +/auth/activate/[uid]/[token]/page.tsx +``` + +### 2. Password Reset Linki Test + +```bash +# Django'dan gönderilen email: +http://localhost:3000/password/reset/confirm/Ng/d1aceh-398e275f6a6fa4b1de05846e9f2903aa/ + +# ✅ Next.js'te çalışır: +/password/reset/confirm/[uid]/[token]/page.tsx + +# ✅ Alternatif de çalışır: +/auth/password-reset/confirm/[uid]/[token]/page.tsx +``` + +--- + +## 📝 Notlar + +### URL Format Esnekliği + +Her iki format da destekleniyor: +- **Django Default**: `/activate/{uid}/{token}` - Email template'lerde kullanım için +- **Next.js Convention**: `/auth/activate/{uid}/{token}` - Manuel link paylaşımı için + +### Trailing Slash + +Django varsayılan olarak URL sonuna `/` ekler. Next.js her iki formatı da kabul eder: +- ✅ `/activate/Ng/token/` (trailing slash ile) +- ✅ `/activate/Ng/token` (trailing slash olmadan) + +### Production Checklist + +- [ ] Django `DJOSER['DOMAIN']` production URL'e güncellendi +- [ ] Django `DJOSER['PROTOCOL']` = `'https'` +- [ ] Next.js `NEXTAUTH_URL` production URL'e güncellendi +- [ ] Email template'ler test edildi +- [ ] Aktivasyon linki çalışıyor +- [ ] Password reset linki çalışıyor + +--- + +## 🔍 Debugging + +### Link 404 Hatası Veriyorsa + +1. **URL formatını kontrol edin:** + ```bash + # ✅ Doğru: + /activate/Ng/d1aceh-398e275f6a6fa4b1de05846e9f2903aa + + # ❌ Yanlış: + /activate?uid=Ng&token=d1aceh-398e275f6a6fa4b1de05846e9f2903aa + ``` + +2. **Next.js route'ları kontrol edin:** + ```bash + ls -la app/activate/[uid]/[token]/ + ls -la app/auth/activate/[uid]/[token]/ + ls -la app/password/reset/confirm/[uid]/[token]/ + ls -la app/auth/password-reset/confirm/[uid]/[token]/ + ``` + +3. **Browser console'da hata var mı bakın** + +4. **Django email template'i kontrol edin:** + ```python + # settings.py veya email template + ACTIVATION_URL = 'activate/{uid}/{token}' # ✅ Doğru + # NOT: 'auth/activate/{uid}/{token}' # ❌ Gerekmiyor (ama çalışır) + ``` + +--- + +**Son Güncelleme**: 24 Aralık 2025 +**Version**: 1.0.0 +**Status**: ✅ Tested & Working + diff --git a/SETUP.md b/SETUP.md new file mode 100644 index 0000000..a14bf92 --- /dev/null +++ b/SETUP.md @@ -0,0 +1,290 @@ +# Authentication Kurulum Kılavuzu + +Bu Next.js projesi Django REST API ile entegre çalışan tam özellikli bir authentication sistemi içerir. + +## 📋 Özellikler + +✅ Email/Password ile kayıt ve giriş +✅ Email aktivasyonu +✅ Social Auth (Google, GitHub) +✅ Token refresh (JWT) +✅ Şifre sıfırlama +✅ Kullanıcı profili +✅ Session yönetimi + +## 🚀 Kurulum + +### 1. Environment Variables + +`.env.local` dosyası oluşturun (`.env.example` dosyasını kopyalayın): + +```bash +cp .env.example .env.local +``` + +`.env.local` dosyasını düzenleyin: + +```env +# NextAuth Configuration +NEXTAUTH_URL=http://localhost:3000 +NEXTAUTH_SECRET=super-secret-key-change-this + +# Django REST API +NEXT_PUBLIC_API_BASE_URL=http://localhost:8000/api/v1 + +# Social Auth (İsteğe bağlı) +GOOGLE_ID=your-google-client-id +GOOGLE_SECRET=your-google-client-secret +GITHUB_ID=your-github-client-id +GITHUB_SECRET=your-github-client-secret +``` + +### 2. NextAuth Secret Üretme + +```bash +openssl rand -base64 32 +``` + +Bu komutu çalıştırın ve çıkan değeri `NEXTAUTH_SECRET` olarak kullanın. + +### 3. Django Backend + +Django backend'inizin çalıştığından emin olun: + +```bash +# Django projenizde +python manage.py runserver +``` + +Backend `http://localhost:8000` adresinde çalışıyor olmalı. + +## 📱 Sayfalar ve Rotalar + +### Authentication Sayfaları + +| Sayfa | Route | Açıklama | +|-------|-------|----------| +| Login | `/auth/login` | Giriş sayfası (Email/Password + Social) | +| Register | `/auth/register` | Kayıt sayfası | +| Activate | `/auth/activate/[uid]/[token]` | Email aktivasyon sayfası | +| Resend Activation | `/auth/resend-activation` | Aktivasyon emaili tekrar gönderme | +| Password Reset | `/auth/password-reset` | Şifre sıfırlama talebi | +| Reset Confirm | `/auth/password-reset/confirm/[uid]/[token]` | Şifre sıfırlama onayı | +| Error | `/auth/error` | Auth hata sayfası | + +### Korumalı Sayfalar + +| Sayfa | Route | Açıklama | +|-------|-------|----------| +| Profile | `/profile` | Kullanıcı profili | +| Dashboard | `/dashboard` | Dashboard (mevcut) | + +## 🔐 Social Authentication Kurulumu + +### Google OAuth2 + +1. [Google Cloud Console](https://console.cloud.google.com/) adresine gidin +2. Yeni proje oluşturun veya mevcut projeyi seçin +3. "APIs & Services" > "Credentials" sayfasına gidin +4. "Create Credentials" > "OAuth 2.0 Client ID" seçin +5. Application type: "Web application" +6. Authorized redirect URIs: + - `http://localhost:3000/api/auth/callback/google` + - `https://yourdomain.com/api/auth/callback/google` (production) +7. Client ID ve Client Secret'i kopyalayıp `.env.local` dosyasına ekleyin + +### GitHub OAuth2 + +1. [GitHub Developer Settings](https://github.com/settings/developers) adresine gidin +2. "New OAuth App" butonuna tıklayın +3. Form bilgileri: + - Application name: "Your App Name" + - Homepage URL: `http://localhost:3000` + - Authorization callback URL: `http://localhost:3000/api/auth/callback/github` +4. Client ID ve Client Secret'i `.env.local` dosyasına ekleyin + +## 🔄 Authentication Flow + +### 1. Kayıt (Register) + +``` +User fills form → POST /auth/users/ → Email sent → User clicks link → +Account activated → User can login +``` + +### 2. Login (Email/Password) + +``` +User enters credentials → POST /auth/jwt/create/ → Tokens received → +Session created → User authenticated +``` + +### 3. Social Login + +``` +User clicks Google/GitHub → OAuth flow → Access token received → +POST /auth/social/{provider}/ → Tokens received → Session created +``` + +### 4. Token Refresh + +``` +Access token expires → Auto refresh with refresh token → +New tokens received → Session updated +``` + +## 🛡️ Middleware ve Koruma + +### Session Kontrolü + +```tsx +import { useSession } from "next-auth/react"; + +export default function ProtectedPage() { + const { data: session, status } = useSession(); + + if (status === "loading") { + return
Loading...
; + } + + if (status === "unauthenticated") { + redirect("/auth/login"); + } + + return
Protected content
; +} +``` + +### API İsteği Örnekleri + +```tsx +// Get user profile +const response = await fetch(`${API_BASE_URL}/auth/users/me/`, { + headers: { + Authorization: `Bearer ${session.accessToken}`, + }, +}); + +// Update user profile +const response = await fetch(`${API_BASE_URL}/auth/users/me/`, { + method: "PATCH", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${session.accessToken}`, + }, + body: JSON.stringify({ + first_name: "Yeni Ad", + last_name: "Yeni Soyad", + }), +}); +``` + +## 📧 Email Testleri (Development) + +Django backend'inizde MailPit veya benzeri bir email test aracı kullanıyorsanız: + +``` +MailPit: http://localhost:8025 +``` + +Gönderilen aktivasyon ve şifre sıfırlama emaillerini burada görebilirsiniz. + +## 🧪 Test Senaryoları + +### 1. Email/Password Kayıt + +```bash +1. /auth/register sayfasına gidin +2. Form bilgilerini doldurun +3. "Kayıt Ol" butonuna tıklayın +4. Email kutunuzu kontrol edin (MailPit: http://localhost:8025) +5. Aktivasyon linkine tıklayın +6. Hesap aktifleştirilir +7. /auth/login sayfasından giriş yapın +``` + +### 2. Social Login + +```bash +1. /auth/login sayfasına gidin +2. "Google ile Giriş Yap" veya "GitHub ile Giriş Yap" butonuna tıklayın +3. OAuth akışını tamamlayın +4. Otomatik olarak dashboard'a yönlendirilirsiniz +``` + +### 3. Şifre Sıfırlama + +```bash +1. /auth/password-reset sayfasına gidin +2. Email adresinizi girin +3. Email kutunuzu kontrol edin +4. Şifre sıfırlama linkine tıklayın +5. Yeni şifrenizi girin +6. Yeni şifre ile giriş yapın +``` + +## 🐛 Hata Ayıklama + +### "Authentication failed" hatası + +- Django backend'in çalıştığından emin olun +- API_BASE_URL değerini kontrol edin +- Browser console'da hata mesajlarını kontrol edin + +### Social auth çalışmıyor + +- Google/GitHub OAuth credentials'ları doğru mu? +- Redirect URI'lar doğru ayarlanmış mı? +- Django backend'de social auth ayarları yapılmış mı? + +### Token expired hatası + +- Token refresh otomatik çalışmalı +- Refresh token süresi dolmuşsa yeniden login gerekli +- Session süresi: 7 gün (refresh token) + +## 📝 TypeScript Types + +```typescript +// next-auth.d.ts (mevcut) +interface User { + accessToken?: string; + refreshToken?: string; + expires?: number; + accessTokenExpiry?: number; +} + +interface Session { + accessToken?: string; + refreshToken?: string; + expires?: number; + error?: string; +} +``` + +## 🔗 Faydalı Linkler + +- [NextAuth.js Docs](https://next-auth.js.org/) +- [Django REST Framework](https://www.django-rest-framework.org/) +- [AUTH.md - API Documentation](./AUTH.md) + +## 💡 İpuçları + +1. **Development**: `.env.local` dosyasını git'e eklemeyin +2. **Production**: Environment variables'ı hosting platformunuzda ayarlayın +3. **Security**: NEXTAUTH_SECRET'i güçlü ve rastgele tutun +4. **CORS**: Django backend'de Next.js URL'ini CORS whitelist'e ekleyin +5. **HTTPS**: Production'da mutlaka HTTPS kullanın + +## 🚀 Production Deployment + +1. Environment variables'ı production ortamına ekleyin +2. `NEXTAUTH_URL`'i production URL'inize değiştirin +3. Django backend URL'ini production URL'i ile değiştirin +4. OAuth redirect URI'larını production URL'leri ile güncelleyin +5. HTTPS kullanın (NextAuth requirement) + +--- + +**Last Updated:** 2025-12-24 + diff --git a/TEMPLATE-ASSETS.md b/TEMPLATE-ASSETS.md new file mode 100644 index 0000000..ba9ffc4 --- /dev/null +++ b/TEMPLATE-ASSETS.md @@ -0,0 +1,215 @@ +# 📦 Template Assets Kurulum Kılavuzu + +Softora template'i Next.js projesine başarıyla entegre edildi! + +## ⚠️ Önemli: Assets Dosyalarını Kopyalayın + +Template'inizin düzgün çalışması için aşağıdaki dosyaları Next.js `public` klasörüne kopyalamanız gerekiyor: + +### 1. CSS Dosyaları + +Original template'inizden şu CSS dosyalarını kopyalayın: + +```bash +# Kaynak klasör (template'iniz) +Temp/assets/css/ + +# Hedef klasör (Next.js projesi) +public/assets/css/ + +# Kopyalanacak dosyalar: +- animate.css +- tabler-icons.min.css +- bootstrap.min.css +- swiper-bundle.min.css +``` + +### 2. JavaScript Dosyaları + +```bash +# Kaynak klasör +Temp/assets/js/ + +# Hedef klasör +public/assets/js/ + +# Kopyalanacak dosyalar: +- bootstrap.bundle.min.js +- slideToggle.min.js +- swiper-bundle.min.js +- jarallax.min.js +- index.js +- cookiealert.js +- imagesloaded.pkgd.min.js +- isotope.pkgd.min.js +- wow.min.js +- active.js +``` + +### 3. Images & Icons + +```bash +# Kaynak klasör +Temp/assets/img/ + +# Hedef klasör +public/assets/img/ + +# Kopyalanacak klasörler: +- core-img/ (logo, shapes, favicons vb.) +- bg-img/ (background images) +- partner-img/ (partner logos) +``` + +### 4. Style CSS (Ana stil dosyası) + +```bash +# Kaynak dosya +Temp/style.css + +# Hedef +public/style.css +``` + +## 🚀 Hızlı Kurulum Komutu + +Terminal'de şu komutları çalıştırın: + +```bash +# Public klasörünü oluştur (zaten var) +cd /Users/beyhan/Projeler/JS/next-dj + +# Assets klasörünü oluştur +mkdir -p public/assets/css +mkdir -p public/assets/js +mkdir -p public/assets/img/core-img +mkdir -p public/assets/img/bg-img +mkdir -p public/assets/img/partner-img + +# Template klasöründen dosyaları kopyala (PATH'inizi güncelleyin) +# Örnek: +cp -r /path/to/Temp/assets/css/* public/assets/css/ +cp -r /path/to/Temp/assets/js/* public/assets/js/ +cp -r /path/to/Temp/assets/img/* public/assets/img/ +cp /path/to/Temp/style.css public/style.css +``` + +## ✅ Yapılan Entegrasyonlar + +### 1. Server-Side Rendering +- Ana sayfa server component olarak düzenlendi +- Session bilgisi server tarafında alınıyor +- SEO friendly ve hızlı ilk yükleme + +### 2. Session-Aware Template +- Header'da kullanıcı durumuna göre navigasyon +- Login/Logout butonları dinamik +- Dashboard ve Profile linkleri (login olduysa) +- Register/Login linkleri (login olmadıysa) + +### 3. Next.js Route Entegrasyonu +Template'deki tüm linkler Next.js route'larına dönüştürüldü: + +| Original | Next.js Route | +|----------|---------------| +| `index.html` | `/` | +| `about-us.html` | `/about` | +| `contact.html` | `/contact` | +| N/A | `/auth/login` | +| N/A | `/auth/register` | +| N/A | `/dashboard` | +| N/A | `/profile` | + +### 4. Component Yapısı + +``` +app/ +├── layout.tsx (Assets yükleme + AuthProvider) +├── page.tsx (Ana sayfa - Server Component) +│ +components/ +├── Header.tsx (Navbar + Session-aware) +└── PreloaderAndSearch.tsx (Preloader + Search popup) +``` + +### 5. Bootstrap & Interactive Features + +Tüm Bootstrap component'leri ve JavaScript features korundu: +- ✅ Dropdown menüler +- ✅ Tabs (Services section) +- ✅ Modal (Video popup) +- ✅ Swiper slider +- ✅ WOW animations +- ✅ Jarallax parallax +- ✅ Cookie alert +- ✅ Scroll to top +- ✅ Search popup + +## 🎨 Özelleştirmeler + +### Template'e Eklenen Auth Features + +1. **Hero Section CTA** + - Session varsa: "Go to Dashboard" + - Session yoksa: "Get Started" (Register'a yönlendirir) + +2. **About Section** + - Authentication özelliklerine göre düzenlendi + - Social login, Email verification vb. vurgulandı + +3. **Services Section** + - IT Services → Authentication Services + - 3 tab: Secure Auth, Email Verification, Social Auth + +4. **Bottom CTA** + - Session varsa: Hoş geldin mesajı + Dashboard linki + - Session yoksa: Kayıt ol mesajı + Register linki + +5. **Footer** + - Auth sayfalarına linkler + - Dashboard/Profile linkleri (session varsa) + - Documentation linkleri + +## 🔧 Sorun Giderme + +### Stil gözükmüyorsa: +1. `public/` klasörüne assets kopyalandı mı kontrol edin +2. Browser console'da 404 hatası var mı bakın +3. Dosya path'leri doğru mu kontrol edin + +### JavaScript çalışmıyorsa: +1. Tüm JS dosyaları `public/assets/js/` içinde mi? +2. Browser console'da JavaScript hataları var mı? +3. `layout.tsx` içindeki script tag'leri yüklendi mi? + +### Template'e yeni bir sayfa eklemek: +```bash +# 1. app klasöründe yeni klasör oluştur +mkdir app/about + +# 2. page.tsx oluştur +# 3. Template'den ilgili HTML'i kopyala ve Next.js formatına çevir +# 4. Header ve Footer ekle (import from components) +``` + +## 📝 Notlar + +- Template'in tüm orijinal tasarımı korundu +- Sadece static HTML yerine Next.js component'leri kullanıldı +- Authentication özellikleri template'e entegre edildi +- Server-side rendering ile SEO ve performance iyileştirildi +- Session management ile dynamic content + +## 🎉 Sonuç + +Template başarıyla Next.js'e dönüştürüldü ve authentication sistemi ile entegre edildi! + +**Assets dosyalarını kopyaladıktan sonra:** +```bash +npm run dev +# veya +yarn dev +``` + +Ardından `http://localhost:3000` adresine gidin ve template'inizi görün! 🚀 + diff --git a/Temp/index.html b/Temp/index.html new file mode 100644 index 0000000..0ae3da7 --- /dev/null +++ b/Temp/index.html @@ -0,0 +1,1281 @@ + + + + + + + + + + Shared on THEMELOCK.COM - IT Solutions | Softora - IT Solutions & Technology HTML Template + + + + + + + + + + + + +
+
+ Loading... +
+
+ + +
+ + +
+

How can I help you, Today?

+ +
+ + +
+
+ + +
+
+
+ +
+
+ + info@example.com +
+
+ + 629 Elgin St.Celina,2202 +
+
+ + (888).123.456.7894 +
+
+ + + +
+
+ + +
+ + +
+ +
+ +
+ +
+
+
+

Best IT + Solution Agency For Your Business +

+
+ +
+

At Solvexa, we + are dedicated transforming your digital aspirations into reality. With a passion for innovation and + a commitment to excellence At Solvexa, we are dedicated to transforming your digital aspirations. +

+ Explore More +
+
+
+
+ + +
+
+ + +
+ +
+ +
+
+
+
+ +
+
+

200+

+

Satisfied Customers

+
+ + +
+ + + + +
+
+ + +
+
+

100+

+

Global Clients

+
+ +
+

150+

+

Team Members

+
+ +
+

15+

+

Business Experience

+
+ +
+

300+

+

Projects Complete

+
+
+
+
+
+
+ + +
+
+ + +
+ +
+ +
+ + +
+ +
+
+
+ +
+
+ About Us +

We Are About to Witness Something Great

+

Empower your business with our cutting-edge IT services and unmatched support, + tailored for transformative growth and harness innovation and achieve your goals with our + dedicated expertise.

+ + +
    +
  • +
    + +
    +
    Created 40+ unique sections with responsiveness.
    +
  • +
  • +
    + +
    +
    You will able to build a new site with an ease.
    +
  • +
  • +
    + +
    +
    Booster is made for stay ahead from the compitition.
    +
  • +
+ + + Read More +
+
+
+ +
+ +
+ + + +
+
+ +
+
+
+ + +
+
+ +
+
+ + + +
+
+
+
+
+ + +
+
+ + +
+
+ +
+ +
+
+
+ + +
+ +
+ +
+ + +
+ +
+
+
+
+ Premium Services +

Our Latest Services

+
+
+ +
+
+

We specialize in helping individuals and families regain control of their financial + future by + repairing and improving their credit scores team of experts is dedicated.

+
+
+
+ +
+ +
+ +
+ +
+ + +
+
+ +
+ +
+ + + +
+

Services Analysis

+

Our extensive experience and deep understanding of complex public and private sector + governance.

+
    +
  • Keyword Research
  • +
  • One Page Optimization
  • +
  • Basic Analytics Reporting
  • +
+ Read More +
+
+
+ + +
+ +
+ + + +
+

Branding Identity

+

Our extensive experience and deep understanding of complex public and private sector + governance.

+
    +
  • Keyword Research
  • +
  • One Page Optimization
  • +
  • Basic Analytics Reporting
  • +
+ Read More +
+
+
+ + +
+ +
+ + + +
+

Cyber Security Solutions

+

Our extensive experience and deep understanding of complex public and private sector + governance.

+
    +
  • Keyword Research
  • +
  • One Page Optimization
  • +
  • Basic Analytics Reporting
  • +
+ Read More +
+
+
+
+
+
+
+ + +
+
+
+ + +
+ +
+ +
+ +
+
+
+ Working Process +

Our Approach to Development

+
+
+
+ +
+ + +
+
+ + +
+
+
+ +
1
+
+
+
Define Requirements
+

Stay at the cutting edge of innovation with our comprehensive coverage of the + technological advancements.

+
+
+
+ + +
+
+
+ +
2
+
+
+
Final Solution
+

Stay at the cutting edge of innovation with our comprehensive coverage of the + technological advancements.

+
+
+
+ + +
+
+
+ +
3
+
+
+
Design & Prototyping
+

Stay at the cutting edge of innovation with our comprehensive coverage of the + technological advancements.

+
+
+
+
+
+
+
+ + +
+ +
+ +
+
+ + +
+
+
+ + +
+
+

Branding / Technology / Marketing

+

Building Responsive Websites for Better User Accessibility

+
+
+
+ + +
+
+
+ + +
+
+

Branding / Technology / Marketing

+

Web Revamp and E-Commerce Platform Redesign

+
+
+
+ + +
+
+
+ + +
+
+

Branding / Technology / Marketing

+

Building Scalable Cloud Structure for Growing Startups

+
+
+
+ + +
+
+
+ + +
+
+

Branding / Technology / Marketing

+

Innovative Web Portal Creation for an Enhanced UX Experience

+
+
+
+
+
+ + +
+
+ + +
+ +
+ +
+
+ +
+
+ Our Team +

Our Expert Team is Always Ready to Help you

+

Meet the talented and passionate individuals driving our innovation and success. Our + expert team + combines years of experience.

+ Get to Know Us +
+
+ +
+
+
+
+
+ + +
+ + +
+

John Doe

+ UI/UX Designer +
+
+ + +
+ + +
+

John Doe

+ UI/UX Designer +
+
+ +
+
+ +
+ +
+ + +
+

Edoardo Romussi

+ UI/UX Designer +
+
+
+ +
+
+ + +
+ + +
+

John Doe

+ UI/UX Designer +
+
+ + +
+ + +
+

John Doe

+ UI/UX Designer +
+
+ +
+
+ +
+
+
+
+
+ + +
+
+ + +
+ +
+ +
+
+
+
+ +
+
+ + + +
10k
+
+

More then 25K clients
Reviews

+
+ + +
+
+ +
+
+ +
+
+
+
+ +
+ +
+ Testimonial +

What our Clients
Say About us

+
+ +
+ + +
+
+
+ + +
+
+
+ +
+
+ + + + +

“Working with several word themes and templates the + last years only can say this is best in every level use it for my best reviews that + I have already are company and review that I have already are all excellent. Not + only the design but the are company and the reviews code”

+

Daniyel karlos

+

CEO & Founder

+
+
+
+ + +
+
+
+ +
+
+ + + + +

“Working with several word themes and templates the + last years only can say this is best in every level use it for my best reviews that + I have already are company and review that I have already are all excellent. Not + only the design but the are company and the reviews code”

+

Daniyel karlos

+

CEO & Founder

+
+
+
+ +
+
+ + +
+ +
+
+
+
+
+ + +
+ + + + + +
+
+ + +
+ +
+ + +
+
+
+
+ Our Blogs +

Recent Blog & Articles about Technology

+
+
+
+
+ +
+ + + + +
+
+ + +
+ +
+ +
+
+
+

Start Building Your + Business Now

+
+ +
+

Communicate your pricing + clearly and transparently to build trust with your customers. Hidden fees or + unclear pricing structures can lead to dissatisfaction.

+ Get Started +
+
+
+ + +
+
+ + + + + +
+

We use cookies for the best experience on our website, for social media features and to anal + traffic. accepting + you agree to our use of cookies. Read Cookies Policy.

+ +
+ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Type/images.ts b/Type/images.ts new file mode 100644 index 0000000..f5571c8 --- /dev/null +++ b/Type/images.ts @@ -0,0 +1,18 @@ +export interface Images { + id: number; + title: string; + path: string; + processed_path: string; + original_filename: string; + format: string; + width: number; + height: number; + size: number; + quality: number; + slug: string; + created_at: string; + updated_at: string; + is_active: boolean; + is_front: boolean; + user: number; + } \ No newline at end of file diff --git a/Type/post.ts b/Type/post.ts new file mode 100644 index 0000000..773e067 --- /dev/null +++ b/Type/post.ts @@ -0,0 +1,47 @@ +export interface PostType { + title: string; + content: string; + categories: Categorie[]; + keywords: string; + tags: Tag[]; + image: string | null; + thumb: string | null; + video: string; + slug: string; + created_at: string; + updated_at: string; + is_active: boolean; + is_front: boolean; +} + +export interface Tag { + tag: string; + slug: string; +} + +export interface CategoryPost { + slug: string; +} + +export interface Categorie { + title: string; + parent: string | number | null; + is_active: boolean; + created_at: string; + order: number; + slug: string; + image: string | null; + keywords: string; + description: string; + posts?: CategoryPost[]; + child?: Categorie[]; +} + +export interface PaginatedResponse { + count: number; + next: string | null; + previous: string | null; + results: T[]; +} + +export type Setting = PostType[]; \ No newline at end of file diff --git a/app/activate/[uid]/[token]/page.tsx b/app/activate/[uid]/[token]/page.tsx new file mode 100644 index 0000000..0a69d82 --- /dev/null +++ b/app/activate/[uid]/[token]/page.tsx @@ -0,0 +1,170 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { useParams, useRouter } from "next/navigation"; +import Link from "next/link"; + +const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000/api/v1"; + +export default function ActivatePage() { + const params = useParams(); + const router = useRouter(); + const [loading, setLoading] = useState(true); + const [success, setSuccess] = useState(false); + const [error, setError] = useState(""); + + useEffect(() => { + const activateAccount = async () => { + const { uid, token } = params; + + if (!uid || !token) { + setError("Geçersiz aktivasyon linki."); + setLoading(false); + return; + } + + try { + const response = await fetch(`${API_BASE_URL}/auth/users/activation/`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + uid, + token, + }), + }); + + if (response.ok || response.status === 204) { + setSuccess(true); + setTimeout(() => { + router.push("/auth/login"); + }, 3000); + } else { + const data = await response.json(); + setError( + data.detail || + "Aktivasyon başarısız. Link geçersiz veya süresi dolmuş olabilir." + ); + } + } catch (err) { + setError("Bir hata oluştu. Lütfen daha sonra tekrar deneyin."); + console.error("Activation error:", err); + } finally { + setLoading(false); + } + }; + + activateAccount(); + }, [params, router]); + + return ( +
+
+
+ {loading && ( + <> +
+ + + + +
+

+ Hesabınız Aktifleştiriliyor... +

+

+ Lütfen bekleyin. +

+ + )} + + {success && ( + <> +
+ + + +
+

+ Aktivasyon Başarılı! +

+

+ Hesabınız başarıyla aktifleştirildi. Artık giriş yapabilirsiniz. +

+

+ Giriş sayfasına yönlendiriliyorsunuz... +

+ + )} + + {error && ( + <> +
+ + + +
+

+ Aktivasyon Başarısız +

+

+ {error} +

+
+ + Aktivasyon Emaili Tekrar Gönder + + + Giriş Sayfasına Dön + +
+ + )} +
+
+
+ ); +} + diff --git a/app/api/auth/[...nextauth]/route.ts b/app/api/auth/[...nextauth]/route.ts new file mode 100644 index 0000000..e041ee2 --- /dev/null +++ b/app/api/auth/[...nextauth]/route.ts @@ -0,0 +1,219 @@ +import NextAuth, { NextAuthOptions } from "next-auth"; +import CredentialsProvider from "next-auth/providers/credentials"; +import GithubProvider from "next-auth/providers/github"; +import GoogleProvider from "next-auth/providers/google"; +import { JWT } from "next-auth/jwt"; + +const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000/api/v1"; + +// Django REST API - Token Refresh +async function refreshAccessToken(token: JWT) { + try { + const response = await fetch( + `${API_BASE_URL}/auth/jwt/refresh/`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + refresh: token.refreshToken, + }), + } + ); + + const data = await response.json(); + + if (!response.ok) { + throw data; + } + + return { + ...token, + accessToken: data.access, + refreshToken: data.refresh ?? token.refreshToken, + // Django JWT access token: 60 dakika (3600000 ms) + accessTokenExpiry: Date.now() + 3600000, + }; + } catch (error) { + console.error("Error refreshing access token:", error); + return { + ...token, + error: "RefreshAccessTokenError", + }; + } +} + +// Django REST API - Social Auth Handler +async function handleSocialAuth(provider: string, accessToken: string) { + const response = await fetch( + `${API_BASE_URL}/auth/social/${provider}/`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + access_token: accessToken, + }), + } + ); + + if (!response.ok) { + const error = await response.json(); + throw new Error(error.error || "Social authentication failed"); + } + + return await response.json(); +} + +export const authOptions: NextAuthOptions = { + providers: [ + GithubProvider({ + clientId: process.env.GITHUB_ID || "", + clientSecret: process.env.GITHUB_SECRET || "", + }), + GoogleProvider({ + clientId: process.env.GOOGLE_ID || "", + clientSecret: process.env.GOOGLE_SECRET || "", + }), + CredentialsProvider({ + name: "Django REST API", + credentials: { + email: { label: "Email", type: "email" }, + password: { label: "Password", type: "password" }, + }, + async authorize(credentials) { + if (!credentials?.email || !credentials?.password) { + return null; + } + + try { + // Django REST API - Login + const response = await fetch( + `${API_BASE_URL}/auth/jwt/create/`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + email: credentials.email, + password: credentials.password, + }), + } + ); + + if (!response.ok) { + return null; + } + + const data = await response.json(); + + if (data?.access && data?.refresh) { + // Get user profile + const userResponse = await fetch( + `${API_BASE_URL}/auth/users/me/`, + { + headers: { + Authorization: `Bearer ${data.access}`, + }, + } + ); + + const userData = await userResponse.json(); + + return { + id: userData.id?.toString() || credentials.email, + email: userData.email, + name: `${userData.first_name || ""} ${userData.last_name || ""}`.trim(), + accessToken: data.access, + refreshToken: data.refresh, + accessTokenExpiry: Date.now() + 3600000, // 60 dakika + }; + } + + return null; + } catch (error) { + console.error("Authentication error:", error); + return null; + } + }, + }), + ], + callbacks: { + async jwt({ token, user, account }) { + // İlk login - Email/Password (Credentials Provider) + if (user && account?.provider === "credentials") { + token.accessToken = user.accessToken; + token.refreshToken = user.refreshToken; + token.accessTokenExpiry = user.accessTokenExpiry; + return token; + } + + // İlk login - Social Auth (Google/GitHub) + if (account && (account.provider === "google" || account.provider === "github")) { + try { + const providerMap: Record = { + google: "google-oauth2", + github: "github", + }; + + const djangoProvider = providerMap[account.provider]; + const socialData = await handleSocialAuth( + djangoProvider, + account.access_token! + ); + + token.accessToken = socialData.access; + token.refreshToken = socialData.refresh; + token.accessTokenExpiry = Date.now() + 3600000; + token.email = socialData.user.email; + token.name = `${socialData.user.first_name || ""} ${socialData.user.last_name || ""}`.trim(); + + return token; + } catch (error) { + console.error("Social auth error:", error); + token.error = "SocialAuthError"; + return token; + } + } + + // Token hala geçerliyse mevcut tokeni döndür + if (token.accessTokenExpiry && Date.now() < (token.accessTokenExpiry as number)) { + return token; + } + + // Token süresi dolmuşsa refresh et + return refreshAccessToken(token); + }, + async session({ session, token }) { + session.accessToken = token.accessToken as string; + session.refreshToken = token.refreshToken as string; + session.error = token.error as string | undefined; + + if (token.email) { + session.user = { + ...session.user, + email: token.email as string, + name: token.name as string, + }; + } + + return session; + }, + }, + pages: { + signIn: "/auth/login", + error: "/auth/error", + }, + session: { + strategy: "jwt", + maxAge: 7 * 24 * 60 * 60, // 7 gün (refresh token süresi) + }, + secret: process.env.NEXTAUTH_SECRET, +}; + +const handler = NextAuth(authOptions); + +export { handler as GET, handler as POST }; diff --git a/app/assistants/converters/images/page.tsx b/app/assistants/converters/images/page.tsx new file mode 100644 index 0000000..eaa1836 --- /dev/null +++ b/app/assistants/converters/images/page.tsx @@ -0,0 +1,837 @@ +"use client"; + +import { useSession, signOut } from "next-auth/react"; +import { useRouter } from "next/navigation"; +import { useState, useEffect, FormEvent, ChangeEvent } from "react"; +import { Images } from "@/Type/images"; +import PreloaderAndSearch from "@/components/PreloaderAndSearch"; +import Header from "@/components/Header"; +import CookieAlert from "@/components/CookieAlert"; +import Link from "next/link"; + +const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000/api/v1"; +const MEDIA_BASE_URL = process.env.NEXT_PUBLIC_MEDIA_BASE_URL || "http://localhost:8000/media"; + +export default function ImageUploadPage() { + const { data: session, status } = useSession(); + const router = useRouter(); + const [selectedFile, setSelectedFile] = useState(null); + const [formData, setFormData] = useState({ + title: "", + format: "avif", + width: "", + height: "", + quality: "", + }); + const [preview, setPreview] = useState(null); + const [uploading, setUploading] = useState(false); + const [error, setError] = useState(""); + const [success, setSuccess] = useState(false); + const [uploadedImage, setUploadedImage] = useState(null); + const [imagesList, setImagesList] = useState([]); + const [loadingImages, setLoadingImages] = useState(false); + const [loading, setLoading] = useState(true); + const [copiedUrl, setCopiedUrl] = useState(null); + + useEffect(() => { + if (status === "unauthenticated") { + router.push("/auth/login"); + } else if (status === "authenticated") { + setLoading(false); + } + }, [status, router]); + + useEffect(() => { + if (status === "authenticated" && session?.accessToken) { + fetchImages(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [status]); + + const fetchImages = async () => { + if (!session?.accessToken) { + console.log("No access token available"); + return; + } + + setLoadingImages(true); + try { + console.log("Fetching images from:", `${API_BASE_URL}/images/list/`); + const response = await fetch(`${API_BASE_URL}/images/list/`, { + method: "GET", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${session.accessToken}`, + }, + }); + + console.log("Response status:", response.status); + + if (response.ok) { + const data = await response.json(); + console.log("Fetched images data:", data); + // Eğer paginated response ise results'ı al, değilse direkt array + const images = Array.isArray(data) ? data : (data.results || []); + console.log("Processed images list:", images); + setImagesList(images); + } else if (response.status === 401) { + console.error("Unauthorized - signing out"); + signOut({ callbackUrl: "/auth/login" }); + } else { + const errorText = await response.text(); + console.error("Failed to fetch images:", response.status, errorText); + } + } catch (err) { + console.error("Error fetching images:", err); + } finally { + setLoadingImages(false); + } + }; + + const handleFileChange = (e: ChangeEvent) => { + const file = e.target.files?.[0]; + if (file) { + setSelectedFile(file); + setError(""); + + // Preview oluştur + const reader = new FileReader(); + reader.onloadend = () => { + setPreview(reader.result as string); + }; + reader.readAsDataURL(file); + + // Format seçimi kullanıcı tarafından yapılacak, varsayılan avif + // Dosya formatını sadece bilgi amaçlı tutuyoruz + + // Resim boyutlarını al + const img = new Image(); + img.onload = () => { + setFormData(prev => ({ + ...prev, + width: img.width.toString(), + height: img.height.toString(), + })); + }; + img.src = URL.createObjectURL(file); + } + }; + + const handleInputChange = (e: ChangeEvent) => { + const { name, value } = e.target; + setFormData(prev => ({ + ...prev, + [name]: value, + })); + }; + + const handleSubmit = async (e: FormEvent) => { + e.preventDefault(); + setError(""); + setSuccess(false); + setUploadedImage(null); + + if (!selectedFile) { + setError("Lütfen bir resim dosyası seçin."); + return; + } + + if (!formData.title || formData.title.trim() === "") { + setError("Başlık alanı zorunludur."); + return; + } + + if (!session?.accessToken) { + setError("Oturum açmanız gerekiyor."); + return; + } + + setUploading(true); + + try { + const uploadFormData = new FormData(); + uploadFormData.append("image", selectedFile); + uploadFormData.append("title", formData.title.trim()); + uploadFormData.append("format", formData.format); + + if (formData.width) { + uploadFormData.append("width", formData.width); + } + if (formData.height) { + uploadFormData.append("height", formData.height); + } + if (formData.quality) { + uploadFormData.append("quality", formData.quality); + } + + const response = await fetch(`${API_BASE_URL}/images/upload/`, { + method: "POST", + headers: { + Authorization: `Bearer ${session.accessToken}`, + }, + body: uploadFormData, + }); + + if (response.ok) { + const data = await response.json(); + setUploadedImage(data); + setSuccess(true); + setSelectedFile(null); + setPreview(null); + setFormData({ + title: "", + format: "avif", + width: "", + height: "", + quality: "", + }); + // Yeni yüklenen resmi listeye ekle + fetchImages(); + setTimeout(() => setSuccess(false), 5000); + } else if (response.status === 401) { + signOut({ callbackUrl: "/auth/login" }); + } else { + let errorMessage = "Resim yüklenirken bir hata oluştu."; + + try { + const contentType = response.headers.get("content-type"); + let errorData: any = {}; + + if (contentType && contentType.includes("application/json")) { + errorData = await response.json(); + console.error("API Error Response:", errorData); + } else { + const text = await response.text(); + console.error("API Error Response (text):", text); + try { + errorData = JSON.parse(text); + } catch { + errorMessage = text || `Sunucu hatası (${response.status})`; + } + } + + // Farklı hata formatlarını kontrol et + if (errorData.detail) { + errorMessage = Array.isArray(errorData.detail) + ? errorData.detail.join(', ') + : errorData.detail; + } else if (errorData.message) { + errorMessage = Array.isArray(errorData.message) + ? errorData.message.join(', ') + : errorData.message; + } else if (errorData.error) { + errorMessage = Array.isArray(errorData.error) + ? errorData.error.join(', ') + : errorData.error; + } else if (typeof errorData === 'string') { + errorMessage = errorData; + } else { + // Field-specific errors - Django REST Framework formatı + const errorMessages: string[] = []; + Object.keys(errorData).forEach(key => { + const value = errorData[key]; + if (Array.isArray(value)) { + errorMessages.push(`${key}: ${value.join(', ')}`); + } else if (typeof value === 'string') { + errorMessages.push(`${key}: ${value}`); + } else if (typeof value === 'object') { + errorMessages.push(`${key}: ${JSON.stringify(value)}`); + } + }); + if (errorMessages.length > 0) { + errorMessage = errorMessages.join(' | '); + } + } + } catch (parseError) { + console.error("Error parsing response:", parseError); + errorMessage = `Sunucu hatası (${response.status}): ${response.statusText}`; + } + + setError(errorMessage); + } + } catch (err) { + console.error("Upload error:", err); + const errorMessage = err instanceof Error + ? err.message + : "Bir hata oluştu. Lütfen tekrar deneyin."; + setError(errorMessage); + } finally { + setUploading(false); + } + }; + + if (status === "loading" || loading) { + return ( + <> + +
+
+
+
+ Yükleniyor... +
+

Yükleniyor...

+
+
+ + ); + } + + if (!session) { + return null; + } + + return ( + <> + +
+ + {/* Image Upload Section */} +
+
+
+
+
+
+

Resim Yükle

+

Resim dosyanızı yükleyin ve işleyin

+
+ + {error && ( +
+ + {error} +
+ )} + + {success && ( +
+ + Resim başarıyla yüklendi! +
+ )} + +
+
+
+ {/* File Input */} +
+ +
+ {preview ? ( +
+ Preview +

{selectedFile?.name}

+ +
+ ) : ( +
+
+ +
+ +

PNG, JPG, GIF, WEBP (MAX. 10MB)

+
+ )} +
+
+ + {/* Title */} +
+ + +
+ + {/* Format, Width, Height */} +
+
+ + +
+
+ + +
+
+ + +
+
+ + {/* Quality */} +
+ + +
+ + {/* Submit Buttons */} +
+ + +
+
+
+
+ + {/* Uploaded Image Info */} + {uploadedImage && ( +
+
+

+ + Yüklenen Resim Bilgileri +

+
+
+

+ ID: {uploadedImage.id} +

+
+
+

+ Başlık: {uploadedImage.title || "N/A"} +

+
+
+

+ Format: {uploadedImage.format} +

+
+
+

+ Boyut: + {uploadedImage.width} x {uploadedImage.height} px + +

+
+
+

+ Kalite: {uploadedImage.quality} +

+
+
+

+ Dosya Boyutu: + {(uploadedImage.size / 1024).toFixed(2)} KB + +

+
+ {uploadedImage.path && ( + + )} + {uploadedImage.processed_path && ( +
+

+ İşlenmiş Yol: +

+ + {uploadedImage.processed_path} + +
+ )} +
+
+
+ )} + + {/* Images List */} +
+
+
+

+ + Yüklenen Resimler +

+ +
+ + {loadingImages ? ( +
+
+ Yükleniyor... +
+

Resimler yükleniyor...

+
+ ) : imagesList.length === 0 ? ( +
+ +

Henüz resim yüklenmemiş.

+
+ ) : ( +
+ {imagesList.map((image) => { + // Path'leri tam URL'ye çevir + const getImageUrl = (path: string | null | undefined) => { + if (!path) return null; + // Eğer zaten tam URL ise (http ile başlıyorsa) direkt dön + if (path.startsWith('http://') || path.startsWith('https://')) { + return path; + } + // Eğer path sadece dosya adı ise (slash içermiyorsa), processed klasörü altında olabilir + if (!path.includes('/')) { + return `${MEDIA_BASE_URL}/processed/${path}`; + } + // Relative path ise media base URL ile birleştir + // Path zaten processed/ ile başlıyorsa direkt birleştir + return `${MEDIA_BASE_URL}/${path}`; + }; + + // path değeri genellikle processed/ ile başlar ve daha güvenilirdir + // processed_path ise bazen sadece dosya adı olabilir + const originalUrl = getImageUrl(image.path); + const processedUrl = getImageUrl(image.processed_path); + // Önce path'i kullan, yoksa processed_path'i dene + const imageUrl = originalUrl || processedUrl; + + console.log("Rendering image:", image.id, "path:", image.path, "processed_path:", image.processed_path, "Final URL:", imageUrl); + + return ( +
+
+ {imageUrl ? ( + {image.title window.open(imageUrl, '_blank')} + onError={(e) => { + console.error("Image load error for:", imageUrl, "Trying fallback..."); + // Eğer ilk URL çalışmazsa, diğer path'i dene + const fallbackUrl = originalUrl ? processedUrl : originalUrl; + if (fallbackUrl && fallbackUrl !== imageUrl) { + e.currentTarget.src = fallbackUrl; + } else { + e.currentTarget.style.display = 'none'; + } + }} + /> + ) : ( +
+ +
+ )} +
+
+ {image.title || 'Başlıksız'} +
+
+
+ + {image.width && image.height ? `${image.width} x ${image.height} px` : 'N/A'} +
+
+ + {image.format ? image.format.toUpperCase() : 'N/A'} +
+
+ + {image.size ? `${(image.size / 1024).toFixed(2)} KB` : 'N/A'} +
+
+
+ {originalUrl && ( + + + + )} + {processedUrl && ( + + + + )} + {imageUrl && ( + + )} +
+
+
+ + + {image.created_at ? new Date(image.created_at).toLocaleDateString('tr-TR') : 'N/A'} + +
+
+
+ ); + })} +
+ )} +
+
+
+
+
+
+
+ + {/* Footer */} + + + {/* Cookie Alert */} + + + {/* Scroll To Top */} + + + ); +} diff --git a/app/assistants/converters/jsontotype/hooks/useJsonToType.ts b/app/assistants/converters/jsontotype/hooks/useJsonToType.ts new file mode 100644 index 0000000..f2d1812 --- /dev/null +++ b/app/assistants/converters/jsontotype/hooks/useJsonToType.ts @@ -0,0 +1,167 @@ +import { useState } from 'react'; +import Swal from 'sweetalert2'; + +export interface UseJsonToTypeReturn { + jsonInput: string; + setJsonInput: (value: string) => void; + typeOutput: string; + setTypeOutput: (value: string) => void; + typeName: string; + setTypeName: (value: string) => void; + error: string; + setError: (value: string) => void; + copied: boolean; + setCopied: (value: boolean) => void; + convertJsonToType: () => void; + handleCopy: () => void; + handleClear: () => void; + loadExample: () => void; +} + +const capitalize = (str: string): string => { + return str.charAt(0).toUpperCase() + str.slice(1); +}; + +const getTypeFromValue = (value: unknown, key?: string): string => { + if (value === null) return 'null'; + if (Array.isArray(value)) { + if (value.length === 0) return 'any[]'; + const firstItem = value[0]; + const itemType = getTypeFromValue(firstItem); + return `${itemType}[]`; + } + + const type = typeof value; + if (type === 'object') { + return generateTypeDefinition(value, key ? capitalize(key) : 'NestedType', false); + } + + return type; +}; + +const generateTypeDefinition = (obj: unknown, name: string, isRoot: boolean = true): string => { + if (typeof obj !== 'object' || obj === null) { + return typeof obj; + } + + const entries = Object.entries(obj); + const properties = entries.map(([key, value]) => { + const typeStr = getTypeFromValue(value, key); + return ` ${key}: ${typeStr};`; + }).join('\n'); + + return `${isRoot ? 'export ' : ''}interface ${name} {\n${properties}\n}`; +}; + +const exampleJson = `{ + "id": 1, + "name": "John Doe", + "email": "john@example.com", + "age": 30, + "isActive": true, + "roles": ["admin", "user"], + "address": { + "street": "123 Main St", + "city": "New York", + "zipCode": "10001" + }, + "tags": ["developer", "designer"] +}`; + +export const useJsonToType = (): UseJsonToTypeReturn => { + const [jsonInput, setJsonInput] = useState(''); + const [typeOutput, setTypeOutput] = useState(''); + const [error, setError] = useState(''); + const [typeName, setTypeName] = useState('MyType'); + const [copied, setCopied] = useState(false); + + const convertJsonToType = () => { + try { + setError(''); + setCopied(false); + + if (!jsonInput.trim()) { + Swal.fire({ + icon: 'warning', + title: 'JSON Eksik', + text: 'Lütfen bir JSON girin', + confirmButtonText: 'Tamam', + }); + setError('Lütfen bir JSON girin'); + return; + } + + const parsed = JSON.parse(jsonInput); + const result = generateTypeDefinition(parsed, typeName || 'MyType'); + setTypeOutput(result); + + // Başarılı dönüştürme mesajı + Swal.fire({ + icon: 'success', + title: 'Dönüştürme Başarılı!', + text: 'TypeScript interface oluşturuldu', + timer: 1500, + showConfirmButton: false, + }); + } catch (err) { + const errorMsg = 'Geçersiz JSON formatı: ' + (err as Error).message; + setError(errorMsg); + setTypeOutput(''); + + Swal.fire({ + icon: 'error', + title: 'JSON Hatası', + text: errorMsg, + confirmButtonText: 'Tamam', + }); + } + }; + + const handleCopy = () => { + navigator.clipboard.writeText(typeOutput); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + + // Toast mesajı + Swal.fire({ + toast: true, + position: 'top-end', + icon: 'success', + title: 'Kopyalandı!', + showConfirmButton: false, + timer: 1500, + }); + }; + + const handleClear = () => { + setJsonInput(''); + setTypeOutput(''); + setError(''); + setTypeName('MyType'); + setCopied(false); + }; + + const loadExample = () => { + setJsonInput(exampleJson); + setError(''); + setCopied(false); + }; + + return { + jsonInput, + setJsonInput, + typeOutput, + setTypeOutput, + typeName, + setTypeName, + error, + setError, + copied, + setCopied, + convertJsonToType, + handleCopy, + handleClear, + loadExample, + }; +}; + diff --git a/app/assistants/converters/jsontotype/hooks/useJsonToTypeApi.ts b/app/assistants/converters/jsontotype/hooks/useJsonToTypeApi.ts new file mode 100644 index 0000000..7872e22 --- /dev/null +++ b/app/assistants/converters/jsontotype/hooks/useJsonToTypeApi.ts @@ -0,0 +1,153 @@ +import { useState } from 'react'; +import { useSession } from 'next-auth/react'; +import { useRouter } from 'next/navigation'; +import Swal from 'sweetalert2'; + +const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:8000/api/v1'; + +export interface UseJsonToTypeApiReturn { + title: string; + setTitle: (value: string) => void; + saveSuccess: string; + saveError: string; + isSaving: boolean; + handleSave: (jsonData: string, typeData: string) => Promise; + clearMessages: () => void; +} + +export const useJsonToTypeApi = (): UseJsonToTypeApiReturn => { + const { data: session, status } = useSession(); + const router = useRouter(); + + const [title, setTitle] = useState(''); + const [saveSuccess, setSaveSuccess] = useState(''); + const [saveError, setSaveError] = useState(''); + const [isSaving, setIsSaving] = useState(false); + + const clearMessages = () => { + setSaveSuccess(''); + setSaveError(''); + }; + + const handleSave = async (jsonData: string, typeData: string) => { + // Login kontrolü + if (status !== 'authenticated' || !session) { + Swal.fire({ + icon: 'warning', + title: 'Giriş Gerekli', + text: 'Kaydetmek için giriş yapmalısınız!', + confirmButtonText: 'Giriş Yap', + showCancelButton: true, + cancelButtonText: 'İptal', + }).then((result) => { + if (result.isConfirmed) { + router.push('/auth/login'); + } + }); + return; + } + + // Validation + if (!title.trim()) { + Swal.fire({ + icon: 'error', + title: 'Başlık Eksik', + text: 'Lütfen bir başlık girin', + confirmButtonText: 'Tamam', + }); + return; + } + + if (!jsonData.trim()) { + Swal.fire({ + icon: 'error', + title: 'JSON Eksik', + text: 'Lütfen JSON verisi girin', + confirmButtonText: 'Tamam', + }); + return; + } + + if (!typeData.trim()) { + Swal.fire({ + icon: 'error', + title: 'Dönüştürme Gerekli', + text: 'Lütfen önce JSON\'u TypeScript\'e dönüştürün', + confirmButtonText: 'Tamam', + }); + return; + } + + setIsSaving(true); + setSaveError(''); + setSaveSuccess(''); + + try { + const response = await fetch(`${API_BASE_URL}/utils/jasontotype/`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${session.accessToken}`, + }, + body: JSON.stringify({ + title: title.trim(), + json_data: jsonData.trim(), + type_data: typeData.trim(), + }), + }); + + const data = await response.json(); + + if (!response.ok) { + // Unique constraint hatası kontrolü + if (data.json_data || data.type_data) { + const errors = []; + if (data.json_data) errors.push('Bu JSON verisi zaten kayıtlı'); + if (data.type_data) errors.push('Bu TypeScript tipi zaten kayıtlı'); + + Swal.fire({ + icon: 'error', + title: 'Kayıt Mevcut', + text: errors.join(' ve '), + confirmButtonText: 'Tamam', + }); + return; + } + throw new Error(data.detail || data.error || 'Kaydetme başarısız'); + } + + // Başarılı kayıt + Swal.fire({ + icon: 'success', + title: 'Başarılı!', + text: 'Dönüştürme başarıyla kaydedildi', + timer: 2000, + showConfirmButton: false, + }); + + setSaveSuccess('Başarıyla kaydedildi!'); + setTimeout(() => setSaveSuccess(''), 3000); + } catch (err) { + Swal.fire({ + icon: 'error', + title: 'Hata', + text: (err as Error).message, + confirmButtonText: 'Tamam', + }); + setSaveError((err as Error).message); + } finally { + setIsSaving(false); + } + }; + + return { + title, + setTitle, + saveSuccess, + saveError, + isSaving, + handleSave, + clearMessages, + }; +}; + diff --git a/app/assistants/converters/jsontotype/hooks/useJsonToTypeList.ts b/app/assistants/converters/jsontotype/hooks/useJsonToTypeList.ts new file mode 100644 index 0000000..840cf3d --- /dev/null +++ b/app/assistants/converters/jsontotype/hooks/useJsonToTypeList.ts @@ -0,0 +1,201 @@ +import { useState, useEffect } from 'react'; +import { useSession } from 'next-auth/react'; +import Swal from 'sweetalert2'; + +const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:8000/api/v1'; + +export interface JsonToTypeItem { + id: number; + title: string; + json_data: string; + type_data: string; + created_at: string; + updated_at: string; +} + +export interface UseJsonToTypeListReturn { + items: JsonToTypeItem[]; + isLoading: boolean; + error: string; + fetchItems: () => Promise; + loadItem: (item: JsonToTypeItem) => void; + downloadJson: (item: JsonToTypeItem) => void; + downloadType: (item: JsonToTypeItem) => void; + deleteItem: (id: number) => Promise; +} + +export const useJsonToTypeList = ( + onLoad?: (jsonData: string, typeData: string, title: string) => void +): UseJsonToTypeListReturn => { + const { data: session, status } = useSession(); + const [items, setItems] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(''); + + const fetchItems = async () => { + if (status !== 'authenticated' || !session) { + console.log('Kullanıcı authenticated değil'); + return; + } + + setIsLoading(true); + setError(''); + + try { + console.log('API çağrısı yapılıyor:', `${API_BASE_URL}/utils/jasontotype/`); + const response = await fetch(`${API_BASE_URL}/utils/jasontotype/`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${session.accessToken}`, + }, + }); + + console.log('Response status:', response.status); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})); + console.error('API Error:', errorData); + throw new Error(errorData.detail || 'Veriler yüklenirken hata oluştu'); + } + + const data = await response.json(); + console.log('Gelen kayıt sayısı:', data.length); + console.log('Kayıtlar:', data); + setItems(Array.isArray(data) ? data : []); + } catch (err) { + console.error('Fetch error:', err); + setError((err as Error).message); + Swal.fire({ + icon: 'error', + title: 'Hata', + text: (err as Error).message, + confirmButtonText: 'Tamam', + }); + } finally { + setIsLoading(false); + } + }; + + // İlk yüklemede kayıtları getir + useEffect(() => { + if (status === 'authenticated') { + fetchItems(); + } + }, [status]); + + const loadItem = (item: JsonToTypeItem) => { + if (onLoad) { + onLoad(item.json_data, item.type_data, item.title); + } + + Swal.fire({ + toast: true, + position: 'top-end', + icon: 'success', + title: 'Kayıt yüklendi!', + showConfirmButton: false, + timer: 1500, + }); + }; + + const downloadJson = (item: JsonToTypeItem) => { + const blob = new Blob([item.json_data], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `${item.title.replace(/\s+/g, '_')}.json`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + + Swal.fire({ + toast: true, + position: 'top-end', + icon: 'success', + title: 'JSON indirildi!', + showConfirmButton: false, + timer: 1500, + }); + }; + + const downloadType = (item: JsonToTypeItem) => { + const blob = new Blob([item.type_data], { type: 'text/plain' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `${item.title.replace(/\s+/g, '_')}.ts`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + + Swal.fire({ + toast: true, + position: 'top-end', + icon: 'success', + title: 'TypeScript indirildi!', + showConfirmButton: false, + timer: 1500, + }); + }; + + const deleteItem = async (id: number) => { + const result = await Swal.fire({ + icon: 'warning', + title: 'Emin misiniz?', + text: 'Bu kaydı silmek istediğinizden emin misiniz?', + showCancelButton: true, + confirmButtonText: 'Evet, Sil', + cancelButtonText: 'İptal', + confirmButtonColor: '#d33', + }); + + if (!result.isConfirmed) { + return; + } + + try { + const response = await fetch(`${API_BASE_URL}/utils/jasontotype/${id}/`, { + method: 'DELETE', + headers: { + 'Authorization': `Bearer ${session?.accessToken}`, + }, + }); + + if (!response.ok) { + throw new Error('Silme işlemi başarısız'); + } + + setItems(items.filter(item => item.id !== id)); + + Swal.fire({ + icon: 'success', + title: 'Silindi!', + text: 'Kayıt başarıyla silindi', + timer: 1500, + showConfirmButton: false, + }); + } catch (err) { + Swal.fire({ + icon: 'error', + title: 'Hata', + text: (err as Error).message, + confirmButtonText: 'Tamam', + }); + } + }; + + return { + items, + isLoading, + error, + fetchItems, + loadItem, + downloadJson, + downloadType, + deleteItem, + }; +}; + diff --git a/app/assistants/converters/jsontotype/page.tsx b/app/assistants/converters/jsontotype/page.tsx new file mode 100644 index 0000000..879379e --- /dev/null +++ b/app/assistants/converters/jsontotype/page.tsx @@ -0,0 +1,430 @@ +'use client'; + +import React from 'react'; +import { useSession } from 'next-auth/react'; +//import 'bootstrap/dist/css/bootstrap.min.css'; +import { useJsonToType } from './hooks/useJsonToType'; +import { useJsonToTypeApi } from './hooks/useJsonToTypeApi'; +import { useJsonToTypeList } from './hooks/useJsonToTypeList'; +import PreloaderAndSearch from "@/components/PreloaderAndSearch"; +import Header from "@/components/Header"; + +export default function JsonToTypePage() { + const { status } = useSession(); + + // Converter logic + const { + jsonInput, + setJsonInput, + typeOutput, + setTypeOutput, + typeName, + setTypeName, + error, + copied, + convertJsonToType, + handleCopy, + handleClear: clearConverter, + loadExample, + } = useJsonToType(); + + // API logic + const { + title, + setTitle, + saveSuccess, + saveError, + isSaving, + handleSave: saveToApi, + clearMessages, + } = useJsonToTypeApi(); + + // List logic + const { + items, + isLoading, + fetchItems, + loadItem, + downloadJson, + downloadType, + deleteItem, + } = useJsonToTypeList((jsonData, typeData, itemTitle) => { + setJsonInput(jsonData); + setTypeOutput(typeData); + setTitle(itemTitle); + }); + + const handleClear = () => { + clearConverter(); + setTitle(''); + clearMessages(); + }; + + const handleSave = async () => { + await saveToApi(jsonInput, typeOutput); + // Kayıt başarılıysa listeyi yenile + fetchItems(); + }; + + return ( + <> + + +
+ + {/* Spacer for fixed header */} +
+ +
+
+
+
+
+

+ + JSON to TypeScript Converter +

+

+ JSON verilerinizi TypeScript interface tanımlarına kolayca dönüştürün +

+
+
+
+ +
+ {/* Input Section */} +
+
+
+
+ + JSON Input +
+
+
+
+ + setTitle(e.target.value)} + placeholder="Örn: User API Response Type" + /> + Kaydetmek için bir başlık girin +
+ +
+ + setTypeName(e.target.value)} + placeholder="MyType" + /> +
+ +
+ +