[dyad] Giriş hatalarını doğrula

This commit is contained in:
[dyad] committed 2026-08-01 13:19:17 +02:00
1 parent 45b6fb7385
commit 64c950886a
5 files changed
+77 -111

No files matched your search

+6
View File
@@ -81,3 +81,9 @@ Caddyfile
# lock files (optional - some projects include them) # lock files (optional - some projects include them)
bun.lock bun.lock
.dyad/ .dyad/
# Playwright test artifacts
/test-results/
/playwright-report/
/playwright/.cache/
.last-run.json
+3 -1
View File
@@ -27,7 +27,8 @@
"db:generate": "prisma generate", "db:generate": "prisma generate",
"db:migrate": "prisma migrate dev", "db:migrate": "prisma migrate dev",
"db:reset": "prisma migrate reset", "db:reset": "prisma migrate reset",
"db:seed": "bun prisma/seed.ts" "db:seed": "bun prisma/seed.ts",
"test": "playwright test --config playwright-dyad.config.ts"
}, },
"dependencies": { "dependencies": {
"@dnd-kit/core": "^6.3.1", "@dnd-kit/core": "^6.3.1",
@@ -102,6 +103,7 @@
"zustand": "^5.0.6" "zustand": "^5.0.6"
}, },
"devDependencies": { "devDependencies": {
"@playwright/test": "^1.62.1",
"@tailwindcss/postcss": "^4", "@tailwindcss/postcss": "^4",
"@types/bcryptjs": "^3.0.0", "@types/bcryptjs": "^3.0.0",
"@types/react": "^19", "@types/react": "^19",
+20
View File
@@ -0,0 +1,20 @@
import { defineConfig } from "@playwright/test";
// Generated by Dyad. The dev server is started separately by Dyad's
// preview, so we point baseURL at the already-running proxy URL (passed via
// env) rather than using Playwright's `webServer` (which would double-start
// the app).
// // Uses your installed Google Chrome (no extra browser download).
export default defineConfig({
testDir: "./e2e-tests",
// Run serially against the single dev server.
workers: 1,
fullyParallel: false,
reporter: [["json", { outputFile: "test-results/results.json" }]],
use: {
baseURL: process.env.DYAD_TEST_BASE_URL || "http://localhost:32100",
channel: "chrome",
screenshot: "only-on-failure",
trace: "retain-on-failure",
},
});
+47 -8
View File
@@ -148,13 +148,13 @@ importers:
version: 0.525.0(react@19.2.8) version: 0.525.0(react@19.2.8)
next: next:
specifier: ^16.1.1 specifier: ^16.1.1
version: 16.2.12(@babel/core@7.29.7)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) version: 16.2.12(@babel/core@7.29.7)(@playwright/test@1.62.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
next-auth: next-auth:
specifier: ^4.24.11 specifier: ^4.24.11
version: 4.24.15(next@16.2.12(@babel/core@7.29.7)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react@19.2.8) version: 4.24.15(next@16.2.12(@babel/core@7.29.7)(@playwright/test@1.62.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
next-intl: next-intl:
specifier: ^4.3.4 specifier: ^4.3.4
version: 4.13.4(next@16.2.12(@babel/core@7.29.7)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react@19.2.8)(typescript@5.9.3) version: 4.13.4(next@16.2.12(@babel/core@7.29.7)(@playwright/test@1.62.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react@19.2.8)(typescript@5.9.3)
next-themes: next-themes:
specifier: ^0.4.6 specifier: ^0.4.6
version: 0.4.6(react-dom@19.2.8(react@19.2.8))(react@19.2.8) version: 0.4.6(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
@@ -219,6 +219,9 @@ importers:
specifier: ^5.0.6 specifier: ^5.0.6
version: 5.0.14(@types/react@19.2.17)(react@19.2.8)(use-sync-external-store@1.6.0(react@19.2.8)) version: 5.0.14(@types/react@19.2.17)(react@19.2.8)(use-sync-external-store@1.6.0(react@19.2.8))
devDependencies: devDependencies:
'@playwright/test':
specifier: ^1.62.1
version: 1.62.1
'@tailwindcss/postcss': '@tailwindcss/postcss':
specifier: ^4 specifier: ^4
version: 4.3.3 version: 4.3.3
@@ -1118,6 +1121,11 @@ packages:
resolution: {integrity: sha512-7FNeNl8NCE7aINx7WXiKQrPYZWC/hvrTsmk6zmxbI7LTXE7hVek/n8AfVgpe2y82zl3w0HvCHN0bVKMBoJcC0w==} resolution: {integrity: sha512-7FNeNl8NCE7aINx7WXiKQrPYZWC/hvrTsmk6zmxbI7LTXE7hVek/n8AfVgpe2y82zl3w0HvCHN0bVKMBoJcC0w==}
engines: {node: '>= 10.0.0'} engines: {node: '>= 10.0.0'}
'@playwright/test@1.62.1':
resolution: {integrity: sha512-DTcUc8qii+cpHvtOwggMtBRMjKZHXYWdw8syRYu2vtzuq4Wxphqq4NfCs5Zt44L6mA8rfDfj+PHnxFc/FeK6mQ==}
engines: {node: '>=20'}
hasBin: true
'@prisma/client@6.19.3': '@prisma/client@6.19.3':
resolution: {integrity: sha512-mKq3jQFhjvko5LTJFHGilsuQs+W+T3Gm451NzuTDGQxwCzwXHYnIu2zGkRoW+Exq3Rob7yp2MfzSrdIiZVhrBg==} resolution: {integrity: sha512-mKq3jQFhjvko5LTJFHGilsuQs+W+T3Gm451NzuTDGQxwCzwXHYnIu2zGkRoW+Exq3Rob7yp2MfzSrdIiZVhrBg==}
engines: {node: '>=18.18'} engines: {node: '>=18.18'}
@@ -3035,6 +3043,11 @@ packages:
react-dom: react-dom:
optional: true optional: true
fsevents@2.3.2:
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin]
function-bind@1.1.2: function-bind@1.1.2:
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
@@ -3932,6 +3945,16 @@ packages:
pkg-types@2.3.1: pkg-types@2.3.1:
resolution: {integrity: sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==} resolution: {integrity: sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==}
playwright-core@1.62.1:
resolution: {integrity: sha512-wPYSwEBJY9GHraISXqyqtx0na0LpO3XEX7jNDhntbex7tzUS7kLnZsOlFruFJB4Hi/rhDMjXGqHewDZ68nYZVw==}
engines: {node: '>=20'}
hasBin: true
playwright@1.62.1:
resolution: {integrity: sha512-0M+L3LAD8/nm554LOla9Ayx0j0tmFZ0FBcoQ7F1VuVHpM/XpiC8RcDzBQB8W5+hA8L22THxELzeF+2WcUzvcLg==}
engines: {node: '>=20'}
hasBin: true
po-parser@2.1.1: po-parser@2.1.1:
resolution: {integrity: sha512-ECF4zHLbUItpUgE3OTtLKlPjeBN+fKEczj2zYjDfCGOzicNs0GK3Vg2IoAYwx7LH/XYw43fZQP6xnZ4TkNxSLQ==} resolution: {integrity: sha512-ECF4zHLbUItpUgE3OTtLKlPjeBN+fKEczj2zYjDfCGOzicNs0GK3Vg2IoAYwx7LH/XYw43fZQP6xnZ4TkNxSLQ==}
@@ -5718,6 +5741,10 @@ snapshots:
'@parcel/watcher-win32-arm64': 2.6.0 '@parcel/watcher-win32-arm64': 2.6.0
'@parcel/watcher-win32-x64': 2.6.0 '@parcel/watcher-win32-x64': 2.6.0
'@playwright/test@1.62.1':
dependencies:
playwright: 1.62.1
'@prisma/client@6.19.3(prisma@6.19.3(typescript@5.9.3))(typescript@5.9.3)': '@prisma/client@6.19.3(prisma@6.19.3(typescript@5.9.3))(typescript@5.9.3)':
optionalDependencies: optionalDependencies:
prisma: 6.19.3(typescript@5.9.3) prisma: 6.19.3(typescript@5.9.3)
@@ -7772,6 +7799,9 @@ snapshots:
react: 19.2.8 react: 19.2.8
react-dom: 19.2.8(react@19.2.8) react-dom: 19.2.8(react@19.2.8)
fsevents@2.3.2:
optional: true
function-bind@1.1.2: {} function-bind@1.1.2: {}
function.prototype.name@1.2.0: function.prototype.name@1.2.0:
@@ -8715,13 +8745,13 @@ snapshots:
negotiator@1.0.0: {} negotiator@1.0.0: {}
next-auth@4.24.15(next@16.2.12(@babel/core@7.29.7)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react@19.2.8): next-auth@4.24.15(next@16.2.12(@babel/core@7.29.7)(@playwright/test@1.62.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react@19.2.8):
dependencies: dependencies:
'@babel/runtime': 7.29.7 '@babel/runtime': 7.29.7
'@panva/hkdf': 1.2.1 '@panva/hkdf': 1.2.1
cookie: 0.7.2 cookie: 0.7.2
jose: 4.15.9 jose: 4.15.9
next: 16.2.12(@babel/core@7.29.7)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) next: 16.2.12(@babel/core@7.29.7)(@playwright/test@1.62.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
oauth: 0.9.15 oauth: 0.9.15
openid-client: 5.7.1 openid-client: 5.7.1
preact: 10.29.7(preact-render-to-string@5.2.6) preact: 10.29.7(preact-render-to-string@5.2.6)
@@ -8732,14 +8762,14 @@ snapshots:
next-intl-swc-plugin-extractor@4.13.4: {} next-intl-swc-plugin-extractor@4.13.4: {}
next-intl@4.13.4(next@16.2.12(@babel/core@7.29.7)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react@19.2.8)(typescript@5.9.3): next-intl@4.13.4(next@16.2.12(@babel/core@7.29.7)(@playwright/test@1.62.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react@19.2.8)(typescript@5.9.3):
dependencies: dependencies:
'@formatjs/intl-localematcher': 0.8.13 '@formatjs/intl-localematcher': 0.8.13
'@parcel/watcher': 2.6.0 '@parcel/watcher': 2.6.0
'@swc/core': 1.15.47 '@swc/core': 1.15.47
icu-minify: 4.13.4 icu-minify: 4.13.4
negotiator: 1.0.0 negotiator: 1.0.0
next: 16.2.12(@babel/core@7.29.7)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) next: 16.2.12(@babel/core@7.29.7)(@playwright/test@1.62.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
next-intl-swc-plugin-extractor: 4.13.4 next-intl-swc-plugin-extractor: 4.13.4
po-parser: 2.1.1 po-parser: 2.1.1
react: 19.2.8 react: 19.2.8
@@ -8756,7 +8786,7 @@ snapshots:
next-tick@1.1.0: {} next-tick@1.1.0: {}
next@16.2.12(@babel/core@7.29.7)(react-dom@19.2.8(react@19.2.8))(react@19.2.8): next@16.2.12(@babel/core@7.29.7)(@playwright/test@1.62.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8):
dependencies: dependencies:
'@next/env': 16.2.12 '@next/env': 16.2.12
'@swc/helpers': 0.5.15 '@swc/helpers': 0.5.15
@@ -8775,6 +8805,7 @@ snapshots:
'@next/swc-linux-x64-musl': 16.2.12 '@next/swc-linux-x64-musl': 16.2.12
'@next/swc-win32-arm64-msvc': 16.2.12 '@next/swc-win32-arm64-msvc': 16.2.12
'@next/swc-win32-x64-msvc': 16.2.12 '@next/swc-win32-x64-msvc': 16.2.12
'@playwright/test': 1.62.1
sharp: 0.34.5 sharp: 0.34.5
transitivePeerDependencies: transitivePeerDependencies:
- '@babel/core' - '@babel/core'
@@ -8927,6 +8958,14 @@ snapshots:
exsolve: 1.1.1 exsolve: 1.1.1
pathe: 2.0.3 pathe: 2.0.3
playwright-core@1.62.1: {}
playwright@1.62.1:
dependencies:
playwright-core: 1.62.1
optionalDependencies:
fsevents: 2.3.2
po-parser@2.1.1: {} po-parser@2.1.1: {}
possible-typed-array-names@1.1.0: {} possible-typed-array-names@1.1.0: {}
+1 -102
View File
@@ -2,7 +2,7 @@
import React, { useState, useMemo } from 'react'; import React, { useState, useMemo } from 'react';
import { motion, AnimatePresence } from 'framer-motion'; import { motion, AnimatePresence } from 'framer-motion';
import { BookOpen, Mail, Lock, User, ArrowRight, GraduationCap, Heart, Sparkles, Flower2, Eye, EyeOff, KeyRound, Info, Shield, Leaf, Users as UsersIcon, HelpCircle, BarChart3, LockKeyhole, GitBranch, CheckCircle2, XCircle, Globe, Github, Chrome } from 'lucide-react'; import { BookOpen, Mail, Lock, User, ArrowRight, GraduationCap, Heart, Sparkles, Flower2, Eye, EyeOff, KeyRound, Info, Shield, Leaf, Users as UsersIcon, HelpCircle, LockKeyhole, GitBranch, CheckCircle2, XCircle } from 'lucide-react';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input'; import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label'; import { Label } from '@/components/ui/label';
@@ -275,56 +275,6 @@ function LeftIllustration({ loginRole }: { loginRole: LoginRole }) {
); );
} }
/* ─── Features section below form ───────────────────────────── */
function FeaturesSection() {
const features = [
{ icon: BarChart3, labelKey: 'auth.feature_grading', descKey: 'auth.feature_grading_desc', color: 'emerald' },
{ icon: GraduationCap, labelKey: 'auth.feature_attendance', descKey: 'auth.feature_attendance_desc', color: 'teal' },
{ icon: Sparkles, labelKey: 'auth.feature_ai', descKey: 'auth.feature_ai_desc', color: 'violet' },
{ icon: Heart, labelKey: 'auth.feature_collaboration', descKey: 'auth.feature_collaboration_desc', color: 'amber' },
];
const colorClasses: Record<string, { bg: string; text: string; border: string; iconBg: string }> = {
emerald: { bg: 'bg-emerald-50 dark:bg-emerald-900/20', text: 'text-emerald-600 dark:text-emerald-400', border: 'border-emerald-200/60 dark:border-emerald-800/40', iconBg: 'bg-gradient-to-br from-emerald-400 to-emerald-500 text-white' },
teal: { bg: 'bg-teal-50 dark:bg-teal-900/20', text: 'text-teal-600 dark:text-teal-400', border: 'border-teal-200/60 dark:border-teal-800/40', iconBg: 'bg-gradient-to-br from-teal-400 to-teal-500 text-white' },
amber: { bg: 'bg-amber-50 dark:bg-amber-900/20', text: 'text-amber-600 dark:text-amber-400', border: 'border-amber-200/60 dark:border-amber-800/40', iconBg: 'bg-gradient-to-br from-amber-400 to-amber-500 text-white' },
violet: { bg: 'bg-violet-50 dark:bg-violet-900/20', text: 'text-violet-600 dark:text-violet-400', border: 'border-violet-200/60 dark:border-violet-800/40', iconBg: 'bg-gradient-to-br from-violet-400 to-violet-500 text-white' },
};
return (
<motion.div
initial={{ opacity: 0, y: 16 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.6 }}
className="mt-6"
>
<p className="text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider mb-3 text-center">{t('auth.feature_showcase')}</p>
<div className="grid grid-cols-2 gap-3">
{features.map((f, i) => {
const c = colorClasses[f.color];
return (
<motion.div
key={f.labelKey}
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3, delay: 0.7 + i * 0.08 }}
className={`flex items-start gap-2.5 px-3 py-3 rounded-xl ${c.bg} border ${c.border}`}
>
<div className={`flex items-center justify-center w-7 h-7 rounded-lg ${c.iconBg} shrink-0 shadow-sm`}>
<f.icon className="w-3.5 h-3.5" />
</div>
<div className="min-w-0">
<p className="text-xs font-semibold text-gray-800 dark:text-gray-200 leading-tight">{t(f.labelKey)}</p>
<p className="text-[10px] text-gray-500 dark:text-gray-400 leading-tight mt-0.5">{t(f.descKey)}</p>
</div>
</motion.div>
);
})}
</div>
</motion.div>
);
}
/* ─── Main Auth View ────────────────────────────────────────── */ /* ─── Main Auth View ────────────────────────────────────────── */
export default function AuthView() { export default function AuthView() {
const [mode, setMode] = useState<'login' | 'register'>('login'); const [mode, setMode] = useState<'login' | 'register'>('login');
@@ -953,57 +903,6 @@ export default function AuthView() {
</Card> </Card>
</motion.div> </motion.div>
{/* Social login buttons (visual only) */}
{mode === 'login' && (
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.5, duration: 0.4 }}
className="mt-5"
>
<div className="flex items-center gap-3 mb-4">
<div className="flex-1 h-px bg-gray-200 dark:bg-gray-700" />
<span className="text-xs text-gray-400 dark:text-gray-500 font-medium">{t('auth.or_continue_with') || 'or continue with'}</span>
<div className="flex-1 h-px bg-gray-200 dark:bg-gray-700" />
</div>
<div className="grid grid-cols-3 gap-3">
<motion.button
type="button"
whileHover={{ scale: 1.03 }}
whileTap={{ scale: 0.97 }}
className="social-btn flex items-center justify-center gap-2 h-11 rounded-xl border border-gray-200 dark:border-gray-700 bg-white/60 dark:bg-gray-800/60 backdrop-blur-sm text-gray-700 dark:text-gray-300 font-medium text-xs hover:border-gray-300 dark:hover:border-gray-600"
onClick={() => toast.info(t('auth.social_coming_soon') || 'Social login coming soon')}
>
<Chrome className="h-4 w-4 text-blue-500" />
<span className="hidden sm:inline">Google</span>
</motion.button>
<motion.button
type="button"
whileHover={{ scale: 1.03 }}
whileTap={{ scale: 0.97 }}
className="social-btn flex items-center justify-center gap-2 h-11 rounded-xl border border-gray-200 dark:border-gray-700 bg-white/60 dark:bg-gray-800/60 backdrop-blur-sm text-gray-700 dark:text-gray-300 font-medium text-xs hover:border-gray-300 dark:hover:border-gray-600"
onClick={() => toast.info(t('auth.social_coming_soon') || 'Social login coming soon')}
>
<Github className="h-4 w-4" />
<span className="hidden sm:inline">GitHub</span>
</motion.button>
<motion.button
type="button"
whileHover={{ scale: 1.03 }}
whileTap={{ scale: 0.97 }}
className="social-btn flex items-center justify-center gap-2 h-11 rounded-xl border border-gray-200 dark:border-gray-700 bg-white/60 dark:bg-gray-800/60 backdrop-blur-sm text-gray-700 dark:text-gray-300 font-medium text-xs hover:border-gray-300 dark:hover:border-gray-600"
onClick={() => toast.info(t('auth.social_coming_soon') || 'Social login coming soon')}
>
<Globe className="h-4 w-4 text-emerald-500" />
<span className="hidden sm:inline">Moodle</span>
</motion.button>
</div>
</motion.div>
)}
{/* Features section below form */}
<FeaturesSection />
{/* Footer */} {/* Footer */}
<motion.div <motion.div
initial={{ opacity: 0 }} initial={{ opacity: 0 }}