fix: auth login flow, Next.js 16 upgrade, ESM/CJS compatibility
- Upgrade Next.js 15 → 16.1 and rename middleware.ts → proxy.ts
- Fix ESM/CJS compatibility: remove "type": "module" from config/shared packages, switch to commonjs
- Fix Better Auth integration: text IDs for sessions/accounts/verifications, wildcard route @All("**"), proper password hash for admin seed
- Fix auth-client to use window.location.origin instead of hardcoded localhost:4000
- Fix api-client, forgot-password, reset-password to use relative /api/ URLs (via Next.js proxy)
- Fix TimeoutInterceptor DI by using useValue instead of useClass
- Add Playwright E2E login tests for localhost and v2.sase.tr
- Add missing UI components: vin-input, vehicle-card, category-tree
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -70,7 +70,7 @@ import { HttpExceptionFilter } from "./common/filters/http-exception.filter";
|
||||
{ provide: APP_GUARD, useClass: RolesGuard },
|
||||
{ provide: APP_INTERCEPTOR, useClass: TransformInterceptor },
|
||||
{ provide: APP_INTERCEPTOR, useClass: LoggingInterceptor },
|
||||
{ provide: APP_INTERCEPTOR, useClass: TimeoutInterceptor },
|
||||
{ provide: APP_INTERCEPTOR, useValue: new TimeoutInterceptor(30000) },
|
||||
{ provide: APP_FILTER, useClass: HttpExceptionFilter },
|
||||
],
|
||||
})
|
||||
|
||||
@@ -2,10 +2,12 @@ import { All, Controller, Req, Res } from "@nestjs/common";
|
||||
import { Request, Response } from "express";
|
||||
import { getAuth } from "./auth";
|
||||
import { toNodeHandler } from "better-auth/node";
|
||||
import { Public } from "../common/decorators/public.decorator";
|
||||
|
||||
@Controller("auth")
|
||||
export class AuthController {
|
||||
@All("*path")
|
||||
@Public()
|
||||
@All("**")
|
||||
async handleAuth(@Req() req: Request, @Res() res: Response) {
|
||||
const auth = getAuth();
|
||||
const handler = toNodeHandler(auth);
|
||||
|
||||
@@ -56,7 +56,10 @@ export function createAuth(databaseUrl: string, secret: string, baseUrl: string)
|
||||
},
|
||||
},
|
||||
},
|
||||
trustedOrigins: (process.env.CORS_ORIGIN || "http://localhost:3000").split(","),
|
||||
trustedOrigins: [
|
||||
...(process.env.CORS_ORIGIN || "http://localhost:3000").split(","),
|
||||
"http://localhost:4000",
|
||||
],
|
||||
});
|
||||
|
||||
return authInstance;
|
||||
|
||||
@@ -36,7 +36,7 @@ export const users = pgTable(
|
||||
export const sessions = pgTable(
|
||||
"sessions",
|
||||
{
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
id: text("id").primaryKey(),
|
||||
userId: uuid("user_id")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
@@ -54,7 +54,7 @@ export const sessions = pgTable(
|
||||
export const accounts = pgTable(
|
||||
"accounts",
|
||||
{
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
id: text("id").primaryKey(),
|
||||
userId: uuid("user_id")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
@@ -77,7 +77,7 @@ export const accounts = pgTable(
|
||||
export const verifications = pgTable(
|
||||
"verifications",
|
||||
{
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
id: text("id").primaryKey(),
|
||||
identifier: text("identifier").notNull(),
|
||||
value: text("value").notNull(),
|
||||
expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(),
|
||||
|
||||
@@ -74,8 +74,7 @@ async function seed() {
|
||||
.returning();
|
||||
|
||||
if (adminUser) {
|
||||
// Create credential account for admin (password: Admin123!)
|
||||
// In production, use Better Auth's proper password hashing
|
||||
// Password: Sase2026 (hashed with Better Auth's hashPassword)
|
||||
await db
|
||||
.insert(accounts)
|
||||
.values({
|
||||
@@ -83,7 +82,7 @@ async function seed() {
|
||||
accountId: adminUser.id,
|
||||
providerId: "credential",
|
||||
password:
|
||||
"$2a$10$placeholder_hash_replace_with_better_auth",
|
||||
"5a51a76d4092d38c578b0b728aecac18:bb7877ca1148573e0980c7a825a10cda592fd01a13e7262811a8cee3d6174632ebe5392539b31d5c1f0de69620961d447cb9f929414cb1ec30ff66037a9c38f5",
|
||||
})
|
||||
.onConflictDoNothing();
|
||||
}
|
||||
@@ -91,7 +90,7 @@ async function seed() {
|
||||
console.log("Seed completed!");
|
||||
console.log(`- ${BRANDS_DATA.length} brands`);
|
||||
console.log(`- ${PLANS_DATA.length} plans`);
|
||||
console.log("- 1 admin user (admin@sase.tr)");
|
||||
console.log("- 1 admin user (admin@sase.tr / Sase2026)");
|
||||
|
||||
await client.end();
|
||||
process.exit(0);
|
||||
|
||||
43
apps/web/e2e/login-local-prod.spec.ts
Normal file
43
apps/web/e2e/login-local-prod.spec.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
|
||||
const BASE_URL = "http://localhost:3000";
|
||||
|
||||
test("localhost:3000 prod - login debug", async ({ page }) => {
|
||||
const allRequests: string[] = [];
|
||||
page.on("request", (req) => {
|
||||
allRequests.push(`${req.method()} ${req.url()}`);
|
||||
});
|
||||
|
||||
const consoleMessages: string[] = [];
|
||||
page.on("console", (msg) => {
|
||||
consoleMessages.push(`[${msg.type()}] ${msg.text()}`);
|
||||
});
|
||||
|
||||
await page.goto(`${BASE_URL}/login`);
|
||||
|
||||
await page.fill('input[type="email"]', "admin@sase.tr");
|
||||
await page.fill('input[type="password"]', "Sase2026");
|
||||
await page.click('button[type="submit"]');
|
||||
|
||||
await page.waitForTimeout(5000);
|
||||
|
||||
console.log("\n=== ALL REQUESTS ===");
|
||||
for (const r of allRequests) {
|
||||
if (r.includes("/api/") || r.includes("/auth/")) console.log(r);
|
||||
}
|
||||
|
||||
const cookies = await page.context().cookies();
|
||||
console.log("\n=== COOKIES ===");
|
||||
for (const c of cookies) {
|
||||
console.log(` ${c.name} = ${c.value.substring(0, 20)}... (domain: ${c.domain}, secure: ${c.secure})`);
|
||||
}
|
||||
|
||||
console.log("\n=== CONSOLE ===");
|
||||
for (const m of consoleMessages) {
|
||||
if (m.includes("error") || m.includes("Error") || m.includes("auth") || m.includes("fetch")) console.log(m);
|
||||
}
|
||||
|
||||
const currentUrl = page.url();
|
||||
console.log("\nCurrent URL:", currentUrl);
|
||||
expect(currentUrl).toContain("/dashboard");
|
||||
});
|
||||
95
apps/web/e2e/login-v2.spec.ts
Normal file
95
apps/web/e2e/login-v2.spec.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
|
||||
const BASE_URL = "https://v2.sase.tr";
|
||||
|
||||
test("v2.sase.tr - should login with admin@sase.tr / Sase2026", async ({ page }) => {
|
||||
// Capture ALL network requests/responses for debugging
|
||||
const allRequests: string[] = [];
|
||||
page.on("request", (req) => {
|
||||
if (req.url().includes("/auth/") || req.url().includes("/api/")) {
|
||||
allRequests.push(`${req.method()} ${req.url()}`);
|
||||
}
|
||||
});
|
||||
|
||||
const allResponses: { url: string; status: number; headers: Record<string, string>; body: string }[] = [];
|
||||
page.on("response", async (res) => {
|
||||
if (res.url().includes("/auth/")) {
|
||||
let body = "";
|
||||
try {
|
||||
body = await res.text();
|
||||
} catch {
|
||||
body = "(could not read body)";
|
||||
}
|
||||
const headers = res.headers();
|
||||
allResponses.push({
|
||||
url: res.url(),
|
||||
status: res.status(),
|
||||
headers: {
|
||||
"set-cookie": headers["set-cookie"] || "(none)",
|
||||
"content-type": headers["content-type"] || "(none)",
|
||||
"access-control-allow-origin": headers["access-control-allow-origin"] || "(none)",
|
||||
"access-control-allow-credentials": headers["access-control-allow-credentials"] || "(none)",
|
||||
},
|
||||
body: body.substring(0, 500),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Capture console errors
|
||||
const consoleMessages: string[] = [];
|
||||
page.on("console", (msg) => {
|
||||
consoleMessages.push(`[${msg.type()}] ${msg.text()}`);
|
||||
});
|
||||
|
||||
// Navigate to login page
|
||||
await page.goto(`${BASE_URL}/login`);
|
||||
console.log("Page loaded:", page.url());
|
||||
|
||||
// Fill email
|
||||
await page.fill('input[type="email"]', "admin@sase.tr");
|
||||
// Fill password
|
||||
await page.fill('input[type="password"]', "Sase2026");
|
||||
|
||||
// Click submit
|
||||
await page.click('button[type="submit"]');
|
||||
|
||||
// Wait for navigation or error
|
||||
await page.waitForTimeout(8000);
|
||||
|
||||
// Log debug info
|
||||
console.log("\n=== ALL API REQUESTS ===");
|
||||
for (const r of allRequests) console.log(r);
|
||||
|
||||
console.log("\n=== AUTH RESPONSES (with headers) ===");
|
||||
for (const r of allResponses) {
|
||||
console.log(`${r.status} ${r.url}`);
|
||||
console.log(` Headers:`, JSON.stringify(r.headers, null, 2));
|
||||
console.log(` Body: ${r.body}\n`);
|
||||
}
|
||||
|
||||
// Check cookies
|
||||
const cookies = await page.context().cookies();
|
||||
console.log("\n=== BROWSER COOKIES ===");
|
||||
for (const c of cookies) {
|
||||
console.log(` ${c.name} = ${c.value.substring(0, 30)}... (domain: ${c.domain}, secure: ${c.secure}, sameSite: ${c.sameSite}, path: ${c.path})`);
|
||||
}
|
||||
|
||||
// Current URL
|
||||
const currentUrl = page.url();
|
||||
console.log("\nCurrent URL:", currentUrl);
|
||||
|
||||
// Check for error toast
|
||||
const toast = page.locator('[data-sonner-toast]');
|
||||
const toastCount = await toast.count();
|
||||
if (toastCount > 0) {
|
||||
const toastText = await toast.first().textContent();
|
||||
console.log("Toast message:", toastText);
|
||||
}
|
||||
|
||||
// Console messages
|
||||
console.log("\n=== CONSOLE MESSAGES ===");
|
||||
for (const m of consoleMessages) console.log(m);
|
||||
|
||||
// Verify login succeeded
|
||||
expect(currentUrl).toContain("/dashboard");
|
||||
});
|
||||
75
apps/web/e2e/login.spec.ts
Normal file
75
apps/web/e2e/login.spec.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
|
||||
const BASE_URL = "http://localhost:3000";
|
||||
|
||||
test.describe("Login Flow", () => {
|
||||
test("should load login page", async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/login`);
|
||||
await expect(page.getByText("Giriş Yap", { exact: true }).first()).toBeVisible();
|
||||
await expect(page.locator('input[type="email"]')).toBeVisible();
|
||||
await expect(page.locator('input[type="password"]')).toBeVisible();
|
||||
});
|
||||
|
||||
test("should login with admin@sase.tr / Sase2026", async ({ page }) => {
|
||||
await page.goto(`${BASE_URL}/login`);
|
||||
|
||||
// Fill email
|
||||
await page.fill('input[type="email"]', "admin@sase.tr");
|
||||
// Fill password
|
||||
await page.fill('input[type="password"]', "Sase2026");
|
||||
|
||||
// Listen for network requests to debug
|
||||
const requests: string[] = [];
|
||||
page.on("request", (req) => {
|
||||
if (req.url().includes("/auth/")) {
|
||||
requests.push(`${req.method()} ${req.url()}`);
|
||||
}
|
||||
});
|
||||
|
||||
const responses: { url: string; status: number; body: string }[] = [];
|
||||
page.on("response", async (res) => {
|
||||
if (res.url().includes("/auth/")) {
|
||||
let body = "";
|
||||
try {
|
||||
body = await res.text();
|
||||
} catch {
|
||||
body = "(could not read body)";
|
||||
}
|
||||
responses.push({ url: res.url(), status: res.status(), body: body.substring(0, 500) });
|
||||
}
|
||||
});
|
||||
|
||||
// Click submit
|
||||
await page.click('button[type="submit"]');
|
||||
|
||||
// Wait for navigation or error
|
||||
await page.waitForTimeout(5000);
|
||||
|
||||
// Log debug info
|
||||
console.log("\n=== AUTH REQUESTS ===");
|
||||
for (const r of requests) console.log(r);
|
||||
console.log("\n=== AUTH RESPONSES ===");
|
||||
for (const r of responses) console.log(`${r.status} ${r.url}\n Body: ${r.body}\n`);
|
||||
|
||||
// Check current URL
|
||||
const currentUrl = page.url();
|
||||
console.log("Current URL:", currentUrl);
|
||||
|
||||
// Check for error toast
|
||||
const toast = page.locator('[data-sonner-toast]');
|
||||
const toastCount = await toast.count();
|
||||
if (toastCount > 0) {
|
||||
const toastText = await toast.first().textContent();
|
||||
console.log("Toast message:", toastText);
|
||||
}
|
||||
|
||||
// Check console errors
|
||||
const consoleErrors: string[] = [];
|
||||
page.on("console", (msg) => {
|
||||
if (msg.type() === "error") consoleErrors.push(msg.text());
|
||||
});
|
||||
|
||||
// Verify login succeeded (should redirect to /dashboard/search)
|
||||
expect(currentUrl).toContain("/dashboard");
|
||||
});
|
||||
});
|
||||
2
apps/web/next-env.d.ts
vendored
2
apps/web/next-env.d.ts
vendored
@@ -1,6 +1,6 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
/// <reference path="./.next/types/routes.d.ts" />
|
||||
import "./.next/types/routes.d.ts";
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
"better-auth": "^1.2.0",
|
||||
"clsx": "^2.1.0",
|
||||
"lucide-react": "^0.468.0",
|
||||
"next": "^15.1.0",
|
||||
"next": "^16.1.0",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"sonner": "^1.7.0",
|
||||
@@ -28,6 +28,7 @@
|
||||
"zustand": "^5.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.58.2",
|
||||
"@tailwindcss/postcss": "^4.0.0",
|
||||
"@testing-library/jest-dom": "^6.9.1",
|
||||
"@testing-library/react": "^16.3.2",
|
||||
|
||||
@@ -18,7 +18,7 @@ export default function ForgotPasswordPage() {
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
await fetch(`${process.env.NEXT_PUBLIC_API_URL || "http://localhost:4000/api"}/auth/forget-password`, {
|
||||
await fetch("/api/auth/forget-password", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ email, redirectTo: "/reset-password" }),
|
||||
|
||||
@@ -21,7 +21,7 @@ function ResetPasswordForm() {
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
await fetch(`${process.env.NEXT_PUBLIC_API_URL || "http://localhost:4000/api"}/auth/reset-password`, {
|
||||
await fetch("/api/auth/reset-password", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ newPassword: password, token }),
|
||||
|
||||
108
apps/web/src/components/categories/category-tree.tsx
Normal file
108
apps/web/src/components/categories/category-tree.tsx
Normal file
@@ -0,0 +1,108 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { ChevronRight, ChevronDown, FolderOpen, Folder } from "lucide-react";
|
||||
|
||||
interface Category {
|
||||
id: string;
|
||||
name: string;
|
||||
children?: Category[];
|
||||
partCount?: number;
|
||||
}
|
||||
|
||||
interface CategoryTreeProps {
|
||||
categories: Category[];
|
||||
vehicleId: string;
|
||||
basePath?: string;
|
||||
}
|
||||
|
||||
export function CategoryTree({ categories, vehicleId, basePath }: CategoryTreeProps) {
|
||||
if (!categories || categories.length === 0) {
|
||||
return (
|
||||
<p className="py-4 text-center text-sm text-muted-foreground">
|
||||
Kategori bulunamadi.
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-1">
|
||||
{categories.map((category) => (
|
||||
<CategoryNode
|
||||
key={category.id}
|
||||
category={category}
|
||||
vehicleId={vehicleId}
|
||||
basePath={basePath}
|
||||
level={0}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface CategoryNodeProps {
|
||||
category: Category;
|
||||
vehicleId: string;
|
||||
basePath?: string;
|
||||
level: number;
|
||||
}
|
||||
|
||||
function CategoryNode({ category, vehicleId, basePath, level }: CategoryNodeProps) {
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const hasChildren = category.children && category.children.length > 0;
|
||||
const href = basePath
|
||||
? `${basePath}/${category.id}`
|
||||
: `/vehicles/${vehicleId}/categories/${category.id}`;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
className="flex items-center gap-2 rounded-md px-2 py-1.5 text-sm hover:bg-accent"
|
||||
style={{ paddingLeft: `${level * 16 + 8}px` }}
|
||||
>
|
||||
{hasChildren ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setExpanded(!expanded)}
|
||||
className="flex h-5 w-5 items-center justify-center rounded hover:bg-muted"
|
||||
>
|
||||
{expanded ? (
|
||||
<ChevronDown className="h-3.5 w-3.5" />
|
||||
) : (
|
||||
<ChevronRight className="h-3.5 w-3.5" />
|
||||
)}
|
||||
</button>
|
||||
) : (
|
||||
<span className="h-5 w-5" />
|
||||
)}
|
||||
{expanded ? (
|
||||
<FolderOpen className="h-4 w-4 text-muted-foreground" />
|
||||
) : (
|
||||
<Folder className="h-4 w-4 text-muted-foreground" />
|
||||
)}
|
||||
<Link href={href} className="flex-1 truncate hover:underline">
|
||||
{category.name}
|
||||
</Link>
|
||||
{category.partCount != null && category.partCount > 0 && (
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{category.partCount}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{hasChildren && expanded && (
|
||||
<div>
|
||||
{category.children!.map((child) => (
|
||||
<CategoryNode
|
||||
key={child.id}
|
||||
category={child}
|
||||
vehicleId={vehicleId}
|
||||
basePath={basePath}
|
||||
level={level + 1}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
36
apps/web/src/components/vehicles/vehicle-card.tsx
Normal file
36
apps/web/src/components/vehicles/vehicle-card.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@sase/ui";
|
||||
import { Badge } from "@sase/ui";
|
||||
|
||||
interface VehicleCardProps {
|
||||
id: string;
|
||||
vin: string;
|
||||
brandName: string;
|
||||
model: string;
|
||||
year?: number | string | null;
|
||||
href?: string;
|
||||
}
|
||||
|
||||
export function VehicleCard({ id, vin, brandName, model, year, href }: VehicleCardProps) {
|
||||
const linkHref = href || `/dashboard/vehicles/${id}`;
|
||||
|
||||
return (
|
||||
<Link href={linkHref}>
|
||||
<Card className="cursor-pointer transition-shadow hover:shadow-md">
|
||||
<CardHeader className="pb-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="text-base">
|
||||
{brandName} {model}
|
||||
</CardTitle>
|
||||
{year && <Badge variant="secondary">{year}</Badge>}
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="font-mono text-sm text-muted-foreground">{vin}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
55
apps/web/src/components/vehicles/vin-input.tsx
Normal file
55
apps/web/src/components/vehicles/vin-input.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { Button } from "@sase/ui";
|
||||
import { Input } from "@sase/ui";
|
||||
import { Search } from "lucide-react";
|
||||
import { isValidVin } from "@sase/shared";
|
||||
|
||||
interface VinInputProps {
|
||||
onSubmit: (vin: string) => void;
|
||||
loading?: boolean;
|
||||
error?: string | null;
|
||||
}
|
||||
|
||||
export function VinInput({ onSubmit, loading, error }: VinInputProps) {
|
||||
const [vin, setVin] = useState("");
|
||||
|
||||
function handleSubmit(e: React.FormEvent) {
|
||||
e.preventDefault();
|
||||
const cleanVin = vin.toUpperCase().trim();
|
||||
if (!isValidVin(cleanVin)) return;
|
||||
onSubmit(cleanVin);
|
||||
}
|
||||
|
||||
const vinUpper = vin.toUpperCase().trim();
|
||||
const isInvalid = vinUpper.length === 17 && !isValidVin(vinUpper);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<form onSubmit={handleSubmit} className="flex gap-3">
|
||||
<Input
|
||||
placeholder="VIN numarasini girin (17 karakter)"
|
||||
value={vin}
|
||||
onChange={(e) => setVin(e.target.value.toUpperCase())}
|
||||
maxLength={17}
|
||||
className="font-mono text-lg tracking-wider"
|
||||
/>
|
||||
<Button type="submit" disabled={loading || vin.length !== 17 || isInvalid}>
|
||||
{loading ? (
|
||||
<span className="animate-spin">...</span>
|
||||
) : (
|
||||
<Search className="h-4 w-4" />
|
||||
)}
|
||||
Ara
|
||||
</Button>
|
||||
</form>
|
||||
{isInvalid && (
|
||||
<p className="mt-2 text-sm text-destructive">
|
||||
Gecersiz VIN. 17 karakter olmali, I, O, Q harfleri kullanilamaz.
|
||||
</p>
|
||||
)}
|
||||
{error && <p className="mt-2 text-sm text-destructive">{error}</p>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
const API_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:4000/api";
|
||||
const API_URL = "/api";
|
||||
|
||||
type RequestOptions = {
|
||||
method?: string;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { createAuthClient } from "better-auth/react";
|
||||
|
||||
export const authClient = createAuthClient({
|
||||
baseURL: process.env.NEXT_PUBLIC_API_URL?.replace("/api", "") || "http://localhost:4000",
|
||||
baseURL: typeof window !== "undefined" ? window.location.origin : (process.env.NEXT_PUBLIC_APP_URL || "http://localhost:3000"),
|
||||
basePath: "/api/auth",
|
||||
});
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { NextRequest } from "next/server";
|
||||
|
||||
const publicPaths = ["/", "/pricing", "/login", "/register", "/forgot-password", "/reset-password"];
|
||||
|
||||
export function middleware(request: NextRequest) {
|
||||
export function proxy(request: NextRequest) {
|
||||
const { pathname } = request.nextUrl;
|
||||
|
||||
// Allow public paths
|
||||
@@ -1,7 +1,11 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"lib": ["dom", "dom.iterable", "ES2022"],
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"ES2022"
|
||||
],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
@@ -11,13 +15,27 @@
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"jsx": "react-jsx",
|
||||
"incremental": true,
|
||||
"plugins": [{ "name": "next" }],
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
"@/*": [
|
||||
"./src/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts",
|
||||
".next/dev/types/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user