refactor: replace react-hot-toast with custom toast component, introduce SubmitButton for forms, and migrate status messages to toasts

This commit is contained in:
poyrazavsever
2026-06-09 00:18:46 +03:00
parent b838c94be2
commit 6b48da58bf
12 changed files with 3452 additions and 882 deletions
+1 -1
View File
@@ -6,7 +6,7 @@ import { DefaultChatTransport, type UIMessage } from "ai";
import { Brain, Loader2, MessageSquare, Plus, Send, Trash2 } from "lucide-react"; import { Brain, Loader2, MessageSquare, Plus, Send, Trash2 } from "lucide-react";
import { Button } from "poyraz-ui/atoms"; import { Button } from "poyraz-ui/atoms";
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import toast from "react-hot-toast"; import { toast } from "poyraz-ui/molecules";
type ChatSession = { type ChatSession = {
id: string; id: string;
@@ -7,7 +7,7 @@ import { Card, CardContent, Badge, Button, Input, Textarea, Label } from "poyraz
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger, DialogFooter, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, DialogDescription } from "poyraz-ui/molecules"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger, DialogFooter, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, DialogDescription } from "poyraz-ui/molecules";
import { Phone, Mail, ExternalLink, Calendar, Plus, MessageSquare, Briefcase, FileText, UserPlus, Loader2 } from "lucide-react"; import { Phone, Mail, ExternalLink, Calendar, Plus, MessageSquare, Briefcase, FileText, UserPlus, Loader2 } from "lucide-react";
import Link from "next/link"; import Link from "next/link";
import toast from "react-hot-toast"; import { toast } from "poyraz-ui/molecules";
import { addClientActivity } from "./actions"; import { addClientActivity } from "./actions";
export type ClientDetailData = { export type ClientDetailData = {
+11 -18
View File
@@ -5,6 +5,7 @@ import { AlertTriangle, Blocks, Brain, Key, Save, Shield, User } from "lucide-re
import { updatePassword, updateProfile } from "./actions"; import { updatePassword, updateProfile } from "./actions";
import { createClient } from "@/lib/supabase/client"; import { createClient } from "@/lib/supabase/client";
import { Button, Card, CardContent, Input, Label } from "poyraz-ui/atoms"; import { Button, Card, CardContent, Input, Label } from "poyraz-ui/atoms";
import { toast } from "poyraz-ui/molecules";
type AiProvider = "groq" | "ollama" | "openai" | "gemini"; type AiProvider = "groq" | "ollama" | "openai" | "gemini";
@@ -15,16 +16,13 @@ export default function SettingsPage() {
const [firstName, setFirstName] = useState(""); const [firstName, setFirstName] = useState("");
const [lastName, setLastName] = useState(""); const [lastName, setLastName] = useState("");
const [avatarUrl, setAvatarUrl] = useState(""); const [avatarUrl, setAvatarUrl] = useState("");
const [profileSaveStatus, setProfileSaveStatus] = useState("");
// Security States // Security States
const [passwordSaveStatus, setPasswordSaveStatus] = useState("");
const formRef = useRef<HTMLFormElement>(null); const formRef = useRef<HTMLFormElement>(null);
// AI States // AI States
const [aiProvider, setAiProvider] = useState<AiProvider>("gemini"); const [aiProvider, setAiProvider] = useState<AiProvider>("gemini");
const [apiKey, setApiKey] = useState(""); const [apiKey, setApiKey] = useState("");
const [aiSaveStatus, setAiSaveStatus] = useState("");
// Supabase // Supabase
const [supabase] = useState(() => createClient()); const [supabase] = useState(() => createClient());
@@ -63,11 +61,11 @@ export default function SettingsPage() {
.single(); .single();
if (settings && isActive) { if (settings && isActive) {
setAiProvider((settings.ai_model as AiProvider) || "gemini"); setAiProvider((settings.ai_provider as AiProvider) || "gemini");
setApiKey(settings.api_key || ""); setApiKey(settings.api_key || "");
// Also sync to local storage for existing API route calls if they use it // Also sync to local storage for existing API route calls if they use it
localStorage.setItem("mindspace_ai_provider", settings.ai_model); localStorage.setItem("mindspace_ai_provider", settings.ai_provider || "gemini");
localStorage.setItem("mindspace_api_key", settings.api_key || ""); localStorage.setItem("mindspace_api_key", settings.api_key || "");
} }
}; };
@@ -79,24 +77,22 @@ export default function SettingsPage() {
const handleProfileAction = async (formData: FormData) => { const handleProfileAction = async (formData: FormData) => {
const response = await updateProfile(formData); const response = await updateProfile(formData);
if (response?.error) { if (response?.error) {
setProfileSaveStatus(`Hata: ${response.error}`); toast.error(`Hata: ${response.error}`);
} else { } else {
setProfileSaveStatus("Profil güncellendi!"); toast.success("Profil güncellendi!");
const avatar = formData.get("avatar"); const avatar = formData.get("avatar");
if (avatar instanceof File && avatar.size > 0) window.location.reload(); if (avatar instanceof File && avatar.size > 0) window.location.reload();
} }
setTimeout(() => setProfileSaveStatus(""), 3000);
}; };
const handlePasswordAction = async (formData: FormData) => { const handlePasswordAction = async (formData: FormData) => {
const response = await updatePassword(formData); const response = await updatePassword(formData);
if (response?.error) { if (response?.error) {
setPasswordSaveStatus(`Hata: ${response.error}`); toast.error(`Hata: ${response.error}`);
} else { } else {
setPasswordSaveStatus("Şifre güncellendi!"); toast.success("Şifre güncellendi!");
formRef.current?.reset(); formRef.current?.reset();
} }
setTimeout(() => setPasswordSaveStatus(""), 3000);
}; };
const handleSaveAI = async () => { const handleSaveAI = async () => {
@@ -109,7 +105,8 @@ export default function SettingsPage() {
.from("app_settings") .from("app_settings")
.upsert({ .upsert({
user_id: user.id, user_id: user.id,
ai_model: aiProvider, ai_provider: aiProvider,
ai_model: null, // Reset to allow default model fallback
api_key: apiKey, api_key: apiKey,
updated_at: new Date().toISOString() updated_at: new Date().toISOString()
}, { onConflict: 'user_id' }); }, { onConflict: 'user_id' });
@@ -120,12 +117,11 @@ export default function SettingsPage() {
localStorage.setItem("mindspace_ai_provider", aiProvider); localStorage.setItem("mindspace_ai_provider", aiProvider);
localStorage.setItem("mindspace_api_key", apiKey); localStorage.setItem("mindspace_api_key", apiKey);
setAiSaveStatus("Yapay Zeka ayarları kaydedildi!"); toast.success("Yapay Zeka ayarları kaydedildi!");
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
setAiSaveStatus("Hata oluştu, veritabanına kaydedilemedi."); toast.error("Hata oluştu, veritabanına kaydedilemedi.");
} }
setTimeout(() => setAiSaveStatus(""), 3000);
}; };
return ( return (
@@ -204,7 +200,6 @@ export default function SettingsPage() {
<Button type="submit" className="gap-2"> <Button type="submit" className="gap-2">
<Save className="h-4 w-4" /> Profili Kaydet <Save className="h-4 w-4" /> Profili Kaydet
</Button> </Button>
{profileSaveStatus && <span className="text-sm font-medium text-emerald-500">{profileSaveStatus}</span>}
</div> </div>
</form> </form>
</CardContent> </CardContent>
@@ -224,7 +219,6 @@ export default function SettingsPage() {
<Button type="submit" className="gap-2"> <Button type="submit" className="gap-2">
<Save className="h-4 w-4" /> Şifreyi Güncelle <Save className="h-4 w-4" /> Şifreyi Güncelle
</Button> </Button>
{passwordSaveStatus && <span className="text-sm font-medium text-emerald-500">{passwordSaveStatus}</span>}
</div> </div>
</form> </form>
</CardContent> </CardContent>
@@ -285,7 +279,6 @@ export default function SettingsPage() {
<Button onClick={handleSaveAI} className="gap-2"> <Button onClick={handleSaveAI} className="gap-2">
<Save className="h-4 w-4" /> Ayarları Kaydet <Save className="h-4 w-4" /> Ayarları Kaydet
</Button> </Button>
{aiSaveStatus && <span className="text-sm font-medium text-emerald-500">{aiSaveStatus}</span>}
</div> </div>
</div> </div>
</CardContent> </CardContent>
+2 -2
View File
@@ -2,7 +2,7 @@ import type { Metadata, Viewport } from "next";
import "./globals.css"; import "./globals.css";
import { Geist } from "next/font/google"; import { Geist } from "next/font/google";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { Toaster } from "react-hot-toast"; import { Toaster } from "poyraz-ui/molecules";
import { OfflineIndicator } from "@/components/ui/offline-indicator"; import { OfflineIndicator } from "@/components/ui/offline-indicator";
const geist = Geist({ subsets: ["latin"], variable: "--font-sans" }); const geist = Geist({ subsets: ["latin"], variable: "--font-sans" });
@@ -36,7 +36,7 @@ export default function RootLayout({
<body> <body>
{children} {children}
<OfflineIndicator /> <OfflineIndicator />
<Toaster position="top-right" /> <Toaster />
</body> </body>
</html> </html>
); );
+3 -2
View File
@@ -5,6 +5,7 @@ import { getFirstAdminSetupState } from "@/lib/auth/first-admin-setup";
import { LockKeyhole, LogIn, Mail } from "lucide-react"; import { LockKeyhole, LogIn, Mail } from "lucide-react";
import Link from "next/link"; import Link from "next/link";
import { Button, Input, Label } from "poyraz-ui/atoms"; import { Button, Input, Label } from "poyraz-ui/atoms";
import { SubmitButton } from "@/components/auth/submit-button";
export default async function LoginPage({ export default async function LoginPage({
searchParams, searchParams,
@@ -65,10 +66,10 @@ export default async function LoginPage({
</div> </div>
</div> </div>
<Button formAction={login} className="h-11 w-full gap-2"> <SubmitButton formAction={login} className="h-11 w-full gap-2" pendingText="Giriş yapılıyor...">
<LogIn className="h-4 w-4" /> <LogIn className="h-4 w-4" />
Giriş yap Giriş yap
</Button> </SubmitButton>
</form> </form>
} }
secondaryAction={null} secondaryAction={null}
@@ -6,7 +6,7 @@ import { tr } from "date-fns/locale";
import { Card, CardContent, Badge, Button, Textarea, Label } from "poyraz-ui/atoms"; import { Card, CardContent, Badge, Button, Textarea, Label } from "poyraz-ui/atoms";
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger, DialogFooter } from "poyraz-ui/molecules"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger, DialogFooter } from "poyraz-ui/molecules";
import { CheckCircle2, Clock, MessageSquare, Loader2, RefreshCw } from "lucide-react"; import { CheckCircle2, Clock, MessageSquare, Loader2, RefreshCw } from "lucide-react";
import toast from "react-hot-toast"; import { toast } from "poyraz-ui/molecules";
import { createRevisionRequest } from "./actions"; import { createRevisionRequest } from "./actions";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "poyraz-ui/molecules"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "poyraz-ui/molecules";
+3 -2
View File
@@ -6,6 +6,7 @@ import { LockKeyhole, Mail, UserPlus } from "lucide-react";
import Link from "next/link"; import Link from "next/link";
import { redirect } from "next/navigation"; import { redirect } from "next/navigation";
import { Button, Input, Label } from "poyraz-ui/atoms"; import { Button, Input, Label } from "poyraz-ui/atoms";
import { SubmitButton } from "@/components/auth/submit-button";
export default async function RegisterPage({ export default async function RegisterPage({
searchParams, searchParams,
@@ -69,10 +70,10 @@ export default async function RegisterPage({
</div> </div>
</div> </div>
<Button formAction={signup} className="h-11 w-full gap-2"> <SubmitButton formAction={signup} className="h-11 w-full gap-2" pendingText="Oluşturuluyor...">
<UserPlus className="h-4 w-4" /> <UserPlus className="h-4 w-4" />
Admin hesabını oluştur Admin hesabını oluştur
</Button> </SubmitButton>
</form> </form>
} }
secondaryAction={null} secondaryAction={null}
+1 -1
View File
@@ -115,7 +115,7 @@ export function AuthPageShell({
<div className="w-full relative z-10 p-10 text-sm text-primary-foreground/78"> <div className="w-full relative z-10 p-10 text-sm text-primary-foreground/78">
<span>Açık kaynak ve self-host edilebilir.</span>{" "} <span>Açık kaynak ve self-host edilebilir.</span>{" "}
<Link <Link
href="https://github.com/poyrazavsever/cognis" href="https://github.com/poyrazavsever/revanios"
className="font-semibold text-primary-foreground underline-offset-4 hover:underline" className="font-semibold text-primary-foreground underline-offset-4 hover:underline"
target="_blank" target="_blank"
> >
+31
View File
@@ -0,0 +1,31 @@
"use client";
import { useFormStatus } from "react-dom";
import { Button } from "poyraz-ui/atoms";
import { Loader2 } from "lucide-react";
import React from "react";
interface SubmitButtonProps extends React.ComponentProps<typeof Button> {
pendingText?: string;
}
export function SubmitButton({
children,
pendingText,
...props
}: SubmitButtonProps) {
const { pending } = useFormStatus();
return (
<Button disabled={pending} {...props}>
{pending ? (
<>
<Loader2 className="h-4 w-4 animate-spin" />
{pendingText || children}
</>
) : (
children
)}
</Button>
);
}
+1 -1
View File
@@ -1,7 +1,7 @@
'use client' 'use client'
import { useEffect } from 'react' import { useEffect } from 'react'
import { toast } from 'react-hot-toast' import { toast } from 'poyraz-ui/molecules'
export function ErrorToaster({ message }: { message: string }) { export function ErrorToaster({ message }: { message: string }) {
useEffect(() => { useEffect(() => {
+3397 -852
View File
File diff suppressed because it is too large Load Diff
-1
View File
@@ -45,7 +45,6 @@
"react": "^19.2.7", "react": "^19.2.7",
"react-dom": "^19.2.7", "react-dom": "^19.2.7",
"react-hook-form": "^7.77.0", "react-hook-form": "^7.77.0",
"react-hot-toast": "^2.6.0",
"recharts": "^2.15.4", "recharts": "^2.15.4",
"shadcn": "^4.10.0", "shadcn": "^4.10.0",
"tailwind-merge": "^3.6.0", "tailwind-merge": "^3.6.0",