feat: update Next.js configuration and package dependencies
- Adjusted `reactStrictMode` in `next.config.ts` to reduce strict mode double mounts during development, addressing hydration issues. - Modified `dev` script in `package.json` to suppress hydration warnings, and added a `dev:strict` script for strict mode development. - Added new dependencies: `@polar-sh/better-auth` and `@polar-sh/sdk` for enhanced authentication and SDK functionalities. - Updated `pnpm-lock.yaml` to reflect new package versions and dependencies.
This commit is contained in:
@@ -6,7 +6,6 @@ import { useState } from "react";
|
||||
import { useTheme } from "next-themes";
|
||||
import { useMutation, useQuery } from "convex/react";
|
||||
import {
|
||||
ArrowUpRight,
|
||||
ChevronDown,
|
||||
Coins,
|
||||
LayoutTemplate,
|
||||
@@ -31,9 +30,9 @@ import {
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { api } from "@/convex/_generated/api";
|
||||
import { authClient } from "@/lib/auth-client";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { CreditOverview } from "@/components/dashboard/credit-overview";
|
||||
import { RecentTransactions } from "@/components/dashboard/recent-transactions";
|
||||
import CanvasCard from "@/components/dashboard/canvas-card";
|
||||
|
||||
|
||||
function getInitials(nameOrEmail: string) {
|
||||
@@ -212,24 +211,11 @@ export default function DashboardPage() {
|
||||
) : (
|
||||
<div className="grid gap-3 sm:grid-cols-3">
|
||||
{canvases.map((canvas) => (
|
||||
<button
|
||||
<CanvasCard
|
||||
key={canvas._id}
|
||||
type="button"
|
||||
onClick={() => router.push(`/canvas/${canvas._id}`)}
|
||||
className={cn(
|
||||
"group flex cursor-pointer items-center gap-4 rounded-xl border bg-card p-4 text-left shadow-sm shadow-foreground/3 transition-all",
|
||||
"hover:bg-muted/60 hover:shadow-md hover:shadow-foreground/4",
|
||||
)}
|
||||
>
|
||||
<div className="flex size-10 shrink-0 items-center justify-center rounded-lg bg-primary/8 text-sm font-semibold text-primary">
|
||||
{canvas.name.slice(0, 1).toUpperCase()}
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="truncate text-sm font-medium">{canvas.name}</p>
|
||||
<p className="mt-0.5 text-xs text-muted-foreground">Canvas</p>
|
||||
</div>
|
||||
<ArrowUpRight className="size-4 text-muted-foreground/0 transition-colors group-hover:text-muted-foreground" />
|
||||
</button>
|
||||
canvas={canvas}
|
||||
onNavigate={(id) => router.push(`/canvas/${id}`)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
149
components/dashboard/canvas-card.tsx
Normal file
149
components/dashboard/canvas-card.tsx
Normal file
@@ -0,0 +1,149 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useCallback, useRef } from "react";
|
||||
import { useMutation } from "convex/react";
|
||||
import { ArrowUpRight, MoreHorizontal, Pencil } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { api } from "@/convex/_generated/api";
|
||||
import type { Id } from "@/convex/_generated/dataModel";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface CanvasCardProps {
|
||||
canvas: { _id: Id<"canvases">; name: string };
|
||||
onNavigate: (id: Id<"canvases">) => void;
|
||||
}
|
||||
|
||||
export default function CanvasCard({ canvas, onNavigate }: CanvasCardProps) {
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const [editName, setEditName] = useState(canvas.name);
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const updateCanvas = useMutation(api.canvases.update);
|
||||
|
||||
const handleStartEdit = useCallback(() => {
|
||||
setEditName(canvas.name);
|
||||
setIsEditing(true);
|
||||
setTimeout(() => inputRef.current?.select(), 0);
|
||||
}, [canvas.name]);
|
||||
|
||||
const handleSave = useCallback(async () => {
|
||||
const trimmedName = editName.trim();
|
||||
if (!trimmedName) {
|
||||
toast.error("Name darf nicht leer sein");
|
||||
return;
|
||||
}
|
||||
if (trimmedName === canvas.name) {
|
||||
setIsEditing(false);
|
||||
return;
|
||||
}
|
||||
|
||||
setIsSaving(true);
|
||||
try {
|
||||
await updateCanvas({ canvasId: canvas._id, name: trimmedName });
|
||||
toast.success("Arbeitsbereich umbenannt");
|
||||
setIsEditing(false);
|
||||
} catch {
|
||||
toast.error("Fehler beim Umbenennen");
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
}, [editName, canvas.name, canvas._id, updateCanvas]);
|
||||
|
||||
const handleKeyDown = useCallback(
|
||||
(e: React.KeyboardEvent) => {
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
handleSave();
|
||||
} else if (e.key === "Escape") {
|
||||
e.preventDefault();
|
||||
setIsEditing(false);
|
||||
setEditName(canvas.name);
|
||||
}
|
||||
},
|
||||
[handleSave, canvas.name]
|
||||
);
|
||||
|
||||
// Prevent duplicate toast: only save on blur if still in editing mode
|
||||
const handleBlur = useCallback(() => {
|
||||
if (!isEditing) return;
|
||||
handleSave();
|
||||
}, [isEditing, handleSave]);
|
||||
|
||||
const handleCardClick = useCallback(() => {
|
||||
if (!isEditing) {
|
||||
onNavigate(canvas._id);
|
||||
}
|
||||
}, [isEditing, onNavigate, canvas._id]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"group relative flex cursor-pointer items-center gap-4 rounded-xl border bg-card p-4 text-left shadow-sm shadow-foreground/3 transition-all",
|
||||
"hover:bg-muted/60 hover:shadow-md hover:shadow-foreground/4",
|
||||
isEditing && "ring-2 ring-primary/50"
|
||||
)}
|
||||
onClick={handleCardClick}
|
||||
>
|
||||
{/* Avatar */}
|
||||
<div className="flex size-10 shrink-0 items-center justify-center rounded-lg bg-primary/8 text-sm font-semibold text-primary">
|
||||
{canvas.name.slice(0, 1).toUpperCase()}
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="min-w-0 flex-1">
|
||||
{isEditing ? (
|
||||
<Input
|
||||
ref={inputRef}
|
||||
value={editName}
|
||||
onChange={(e) => setEditName(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
onBlur={handleBlur}
|
||||
disabled={isSaving}
|
||||
autoFocus
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
className="h-auto py-0.5 text-sm font-medium bg-transparent border px-1.5 focus-visible:ring-1"
|
||||
/>
|
||||
) : (
|
||||
<p className="truncate text-sm font-medium">{canvas.name}</p>
|
||||
)}
|
||||
<p className="mt-0.5 text-xs text-muted-foreground">Canvas</p>
|
||||
</div>
|
||||
|
||||
{/* Actions - positioned to not overlap with content */}
|
||||
{!isEditing && (
|
||||
<div className="flex shrink-0 items-center gap-2 ml-2">
|
||||
<ArrowUpRight className="size-4 text-muted-foreground/0 transition-colors group-hover:text-muted-foreground" />
|
||||
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="size-7 shrink-0 opacity-0 group-hover:opacity-100 transition-opacity"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<MoreHorizontal className="size-4" />
|
||||
<span className="sr-only">Optionen</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem onSelect={handleStartEdit}>
|
||||
<Pencil className="size-4" />
|
||||
Umbenennen
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,11 @@
|
||||
import type { NextConfig } from "next";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
// Reduziert in der Entwicklung Strict-Mode-Doppel-Mounts (häufige Ursache für
|
||||
// „Hydration“-Lärm). Echte Server/Client-Mismatches können weiterhin auftreten;
|
||||
// dann `pnpm dev:strict` zum Debuggen oder Ursache beheben.
|
||||
reactStrictMode:
|
||||
process.env.NEXT_DEV_SUPPRESS_HYDRATION === "1" ? false : null,
|
||||
images: {
|
||||
remotePatterns: [
|
||||
{
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"dev": "NEXT_DEV_SUPPRESS_HYDRATION=1 next dev",
|
||||
"dev:strict": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "eslint"
|
||||
@@ -14,6 +15,8 @@
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
"@napi-rs/canvas": "^0.1.97",
|
||||
"@polar-sh/better-auth": "^1.8.3",
|
||||
"@polar-sh/sdk": "^0.46.7",
|
||||
"@xyflow/react": "^12.10.1",
|
||||
"better-auth": "^1.5.6",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
|
||||
555
pnpm-lock.yaml
generated
555
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user