mirror of
https://github.com/Motion-Games/MotionWebStudio.git
synced 2026-04-21 17:10:54 +02:00
509 lines
26 KiB
TypeScript
509 lines
26 KiB
TypeScript
|
|
import React, { useEffect, useState } from 'react';
|
|
import { useAuth } from '../context/AuthContext';
|
|
import { Button } from '../components/Button';
|
|
import { LogOut, User, Settings as SettingsIcon, CreditCard, Layout, Cake, ShieldAlert, Clock, Activity, CheckCircle, XCircle, MessageSquare, ArrowRight, Edit2, Download, FileText, ExternalLink, History, RefreshCw, FileDown, ShieldCheck, Calendar } from 'lucide-react';
|
|
import { Link, useNavigate, useSearchParams } from 'react-router-dom';
|
|
import { supabase, isSupabaseConfigured } from '../lib/supabaseClient';
|
|
import { SettingsModal } from '../components/SettingsModal';
|
|
import { FeedbackModal } from '../components/FeedbackModal';
|
|
import { Invoice, MaintenanceSubscription } from '../types';
|
|
|
|
interface UserProfile {
|
|
id: string;
|
|
email: string;
|
|
first_name?: string;
|
|
last_name?: string;
|
|
date_of_birth: string;
|
|
}
|
|
|
|
interface OrderHistoryEntry {
|
|
id: string;
|
|
status: string;
|
|
changed_at: string;
|
|
}
|
|
|
|
interface Bill {
|
|
id: string;
|
|
order_id: string;
|
|
type: 'advance' | 'final';
|
|
file_url: string;
|
|
created_at: string;
|
|
}
|
|
|
|
interface UserOrder {
|
|
id: string;
|
|
created_at: string;
|
|
package: string;
|
|
status: string;
|
|
amount: string;
|
|
displayId?: string;
|
|
details?: any;
|
|
history?: OrderHistoryEntry[];
|
|
bills?: Bill[];
|
|
subscription?: MaintenanceSubscription;
|
|
}
|
|
|
|
export const Dashboard: React.FC = () => {
|
|
const { user, signOut, isAdmin } = useAuth();
|
|
const navigate = useNavigate();
|
|
const [searchParams] = useSearchParams();
|
|
const [profile, setProfile] = useState<UserProfile | null>(null);
|
|
const [orders, setOrders] = useState<UserOrder[]>([]);
|
|
const [invoices, setInvoices] = useState<Invoice[]>([]);
|
|
const [loadingOrders, setLoadingOrders] = useState(true);
|
|
const [showSettings, setShowSettings] = useState(false);
|
|
|
|
const [feedbackModalOpen, setFeedbackModalOpen] = useState(false);
|
|
const [selectedOrderId, setSelectedOrderId] = useState<string | null>(null);
|
|
const [feedbackLoading, setFeedbackLoading] = useState(false);
|
|
|
|
// Check for payment success/cancel query params
|
|
useEffect(() => {
|
|
const paymentSuccess = searchParams.get('payment_success');
|
|
const orderIdParam = searchParams.get('order_id');
|
|
|
|
if (paymentSuccess === 'true' && orderIdParam) {
|
|
// Opcionális: Itt lehetne egy sikeres fizetés modált megjeleníteni vagy toast üzenetet
|
|
// A státusz frissítést (pl. 'pending_feedback' -> 'in_progress') a webhook végezné ideális esetben,
|
|
// de kliens oldalon is jelezhetjük a sikert.
|
|
// Mivel a redirect után újratölt az oldal, a fetchData friss adatokat hoz.
|
|
// Töröljük a query paramokat, hogy ne maradjanak ott
|
|
window.history.replaceState({}, '', '#/dashboard');
|
|
alert("Sikeres fizetés! Köszönjük.");
|
|
}
|
|
}, [searchParams]);
|
|
|
|
const fetchOrderHistory = async (orderId: string) => {
|
|
if (!isSupabaseConfigured) return [];
|
|
try {
|
|
const { data } = await supabase
|
|
.from('order_status_history')
|
|
.select('*')
|
|
.eq('order_id', orderId)
|
|
.order('changed_at', { ascending: false });
|
|
return data || [];
|
|
} catch (e) {
|
|
return [];
|
|
}
|
|
};
|
|
|
|
const fetchOrderBills = async (orderId: string) => {
|
|
if (!isSupabaseConfigured) return [];
|
|
try {
|
|
const { data } = await supabase
|
|
.from('bills')
|
|
.select('*')
|
|
.eq('order_id', orderId)
|
|
.order('created_at', { ascending: false });
|
|
return data || [];
|
|
} catch (e) {
|
|
return [];
|
|
}
|
|
};
|
|
|
|
const fetchSubscription = async (orderId: string) => {
|
|
if (!isSupabaseConfigured) return undefined;
|
|
try {
|
|
const { data } = await supabase
|
|
.from('maintenance_subscriptions')
|
|
.select('*')
|
|
.eq('order_id', orderId)
|
|
.maybeSingle();
|
|
return data as MaintenanceSubscription | undefined;
|
|
} catch (e) {
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
const fetchData = async () => {
|
|
if (!user) return;
|
|
setLoadingOrders(true);
|
|
|
|
if (!isSupabaseConfigured) {
|
|
const meta = user.user_metadata || {};
|
|
setProfile({ id: user.id, email: user.email || '', first_name: meta.first_name || '', last_name: meta.last_name || '', date_of_birth: meta.date_of_birth || '1990-01-01' });
|
|
setOrders([{
|
|
id: 'demo-order-1',
|
|
created_at: new Date().toISOString(),
|
|
package: 'Pro Web',
|
|
status: 'pending_feedback',
|
|
amount: '350.000 Ft',
|
|
displayId: 'DEMO-123',
|
|
details: {
|
|
demoUrl: 'https://example.com',
|
|
payment_summary: {
|
|
total: 350000,
|
|
advance: 80000,
|
|
remaining: 270000,
|
|
currency: 'HUF'
|
|
}
|
|
},
|
|
history: [{ id: 'h1', status: 'new', changed_at: new Date().toISOString() }],
|
|
bills: []
|
|
}]);
|
|
setLoadingOrders(false);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const { data: profileData } = await supabase.from('profiles').select('*').eq('id', user.id).maybeSingle();
|
|
if (profileData) setProfile(profileData);
|
|
|
|
const { data: orderData } = await supabase.from('orders').select('*').eq('user_id', user.id).order('created_at', { ascending: false });
|
|
|
|
if (orderData) {
|
|
const enrichedOrders = await Promise.all(orderData.map(async (o) => {
|
|
const history = await fetchOrderHistory(o.id);
|
|
const bills = await fetchOrderBills(o.id);
|
|
const sub = await fetchSubscription(o.id);
|
|
return {
|
|
...o,
|
|
displayId: o.id.substring(0, 8).toUpperCase(),
|
|
history: history,
|
|
bills: bills,
|
|
subscription: sub
|
|
};
|
|
}));
|
|
setOrders(enrichedOrders as UserOrder[]);
|
|
}
|
|
|
|
const { data: invoiceData } = await supabase.from('invoices').select('*').eq('user_id', user.id).order('created_at', { ascending: false });
|
|
if (invoiceData) setInvoices(invoiceData as Invoice[]);
|
|
|
|
} catch (err) {
|
|
console.error("Unexpected error fetching data:", err);
|
|
} finally {
|
|
setLoadingOrders(false);
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
fetchData();
|
|
}, [user]);
|
|
|
|
const handleLogout = async () => {
|
|
await signOut();
|
|
navigate('/');
|
|
};
|
|
|
|
const openFeedbackModal = (orderId: string) => {
|
|
setSelectedOrderId(orderId);
|
|
setFeedbackModalOpen(true);
|
|
};
|
|
|
|
const handleSubmitFeedback = async (feedbackData: any) => {
|
|
if (!selectedOrderId) return;
|
|
setFeedbackLoading(true);
|
|
|
|
try {
|
|
if (!isSupabaseConfigured) {
|
|
await new Promise(r => setTimeout(r, 1000));
|
|
setOrders(prev => prev.map(o => o.id === selectedOrderId ? { ...o, status: 'in_progress' } : o));
|
|
setFeedbackModalOpen(false);
|
|
setFeedbackLoading(false);
|
|
return;
|
|
}
|
|
|
|
// 1. Fetch fresh order data to get payment details
|
|
const { data: currentOrder } = await supabase.from('orders').select('*').eq('id', selectedOrderId).single();
|
|
if (!currentOrder) throw new Error("Rendelés nem található");
|
|
|
|
const isApproved = feedbackData.decision === 'approved';
|
|
const paymentSummary = currentOrder.details?.payment_summary;
|
|
const isStandardPackage = ['Landing Page', 'Pro Web'].includes(currentOrder.package);
|
|
|
|
// Check if payment is needed: Approved + Standard Package + Not Custom Price + Has Remaining Balance
|
|
// Note: paymentSummary.remaining > 0 check is implicitly handled by Logic in FeedbackModal (it shows payment button only if true)
|
|
// But we double check here for security.
|
|
const needsPayment = isApproved && isStandardPackage && !paymentSummary?.is_custom && (paymentSummary?.remaining > 0);
|
|
|
|
if (needsPayment) {
|
|
// Initiate Stripe Checkout for Final Payment
|
|
const { data: checkoutData, error: checkoutError } = await supabase.functions.invoke('create-checkout-session', {
|
|
body: {
|
|
order_id: selectedOrderId,
|
|
package_name: currentOrder.package,
|
|
payment_type: 'final',
|
|
customer_email: user?.email
|
|
}
|
|
});
|
|
|
|
if (checkoutError) {
|
|
console.error("Supabase Invoke Error:", checkoutError);
|
|
let msg = "Ismeretlen hiba";
|
|
if (checkoutError && typeof checkoutError === 'object' && 'message' in checkoutError) {
|
|
msg = checkoutError.message;
|
|
}
|
|
throw new Error(`Fizetési rendszer hiba: ${msg}`);
|
|
}
|
|
|
|
if (checkoutData?.url) {
|
|
// Update details with feedback content BEFORE redirecting
|
|
// This ensures we save the "Approval" state even if they drop off payment (status remains pending_feedback though)
|
|
const updatedDetails = {
|
|
...(currentOrder.details || {}),
|
|
latestFeedback: feedbackData,
|
|
feedbackDate: new Date().toISOString()
|
|
};
|
|
await supabase.from('orders').update({ details: updatedDetails }).eq('id', selectedOrderId);
|
|
|
|
// Redirect to Stripe
|
|
window.location.href = checkoutData.url;
|
|
return; // Stop execution to allow redirect
|
|
} else {
|
|
throw new Error("Nem sikerült létrehozni a fizetési linket.");
|
|
}
|
|
}
|
|
|
|
// If no payment needed (e.g. revision request, or custom price, or enterprise), update status directly
|
|
const updatedDetails = {
|
|
...(currentOrder.details || {}),
|
|
latestFeedback: feedbackData,
|
|
feedbackDate: new Date().toISOString()
|
|
};
|
|
|
|
const newStatus = 'in_progress'; // Send back to dev in both cases (to finalize or revise)
|
|
|
|
const { error: updateError } = await supabase.from('orders').update({
|
|
status: newStatus,
|
|
details: updatedDetails
|
|
}).eq('id', selectedOrderId);
|
|
|
|
if (updateError) throw updateError;
|
|
|
|
await supabase.from('order_status_history').insert({ order_id: selectedOrderId, status: newStatus });
|
|
|
|
await fetchData();
|
|
setFeedbackModalOpen(false);
|
|
alert("Visszajelzés sikeresen elküldve!");
|
|
|
|
} catch (err: any) {
|
|
console.error("Feedback submit error:", err);
|
|
alert("Hiba: " + (err.message || "Ismeretlen hiba történt."));
|
|
} finally {
|
|
setFeedbackLoading(false);
|
|
}
|
|
};
|
|
|
|
const getFullName = () => profile?.last_name && profile?.first_name ? `${profile.last_name} ${profile.first_name}` : user?.email?.split('@')[0] || 'Felhasználó';
|
|
|
|
const getStatusConfig = (status: string) => {
|
|
switch (status) {
|
|
case 'new': return { label: 'Beérkezett', color: 'bg-blue-100 text-blue-800', icon: Clock };
|
|
case 'in_progress':
|
|
case 'progress': return { label: 'Fejlesztés', color: 'bg-yellow-100 text-yellow-800', icon: Activity };
|
|
case 'pending_feedback':
|
|
case 'waiting_feedback':
|
|
case 'feedback': return { label: 'Visszajelzés', color: 'bg-purple-100 text-purple-800', icon: MessageSquare };
|
|
case 'completed': return { label: 'Kész', color: 'bg-green-100 text-green-800', icon: CheckCircle };
|
|
case 'cancelled': return { label: 'Törölve', color: 'bg-gray-100 text-gray-500', icon: XCircle };
|
|
default: return { label: 'Beérkezett', color: 'bg-gray-100 text-gray-800', icon: Clock };
|
|
}
|
|
};
|
|
|
|
const selectedOrder = orders.find(o => o.id === selectedOrderId);
|
|
|
|
return (
|
|
<div className="pt-24 bg-gray-50 min-h-screen pb-12">
|
|
<SettingsModal isOpen={showSettings} onClose={() => setShowSettings(false)} userProfile={profile} onUpdate={fetchData} />
|
|
<FeedbackModal
|
|
isOpen={feedbackModalOpen}
|
|
onClose={() => setFeedbackModalOpen(false)}
|
|
onSubmit={handleSubmitFeedback}
|
|
loading={feedbackLoading}
|
|
order={selectedOrder}
|
|
/>
|
|
|
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
<div className="bg-white rounded-[24px] shadow-lg border border-gray-100 overflow-hidden mb-8">
|
|
<div className="bg-gradient-to-r from-gray-900 to-gray-800 p-6 md:p-8 text-white flex flex-col md:flex-row justify-between items-center gap-4 text-center md:text-left">
|
|
<div>
|
|
<h1 className="text-2xl md:text-3xl font-bold mb-2">Fiók Áttekintése</h1>
|
|
<p className="text-gray-300 text-sm md:text-base">Üdvözöljük, {getFullName()}!</p>
|
|
</div>
|
|
<div className="h-12 w-12 bg-white/10 rounded-full flex items-center justify-center border border-white/20 shrink-0">
|
|
<User className="h-6 w-6 text-white" />
|
|
</div>
|
|
</div>
|
|
|
|
<div className="p-6 md:p-8">
|
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8 mb-8">
|
|
{/* ORDERS COLUMN */}
|
|
<div className="lg:col-span-2 space-y-6">
|
|
<div className="flex justify-between items-center">
|
|
<h3 className="font-bold text-gray-900 flex items-center gap-2 text-lg md:text-xl">
|
|
<Layout className="w-5 h-5 text-primary" /> Aktív Projektjeim
|
|
</h3>
|
|
</div>
|
|
|
|
{loadingOrders ? (
|
|
<div className="flex justify-center py-10"><RefreshCw className="animate-spin text-primary" /></div>
|
|
) : orders.length > 0 ? (
|
|
<div className="space-y-6">
|
|
{orders.map(order => {
|
|
const statusConfig = getStatusConfig(order.status);
|
|
const needsFeedback = ['pending_feedback', 'waiting_feedback', 'feedback'].includes(order.status);
|
|
|
|
return (
|
|
<div key={order.id} className="bg-white rounded-2xl border border-gray-100 shadow-sm overflow-hidden hover:shadow-md transition-shadow">
|
|
<div className="p-5 md:p-6 flex flex-col sm:flex-row justify-between gap-4 border-b border-gray-50 bg-gray-50/30">
|
|
<div>
|
|
<p className="text-[10px] font-black text-primary uppercase tracking-widest mb-1">{order.package}</p>
|
|
<h4 className="text-lg md:text-xl font-bold text-gray-900">ID: {order.displayId}</h4>
|
|
</div>
|
|
<div className="flex flex-wrap items-center gap-3">
|
|
{needsFeedback && (
|
|
<>
|
|
<a href={order.details?.demoUrl || '#'} target="_blank" rel="noreferrer" className="flex-grow sm:flex-grow-0 text-[10px] px-4 py-2.5 rounded-xl font-black bg-blue-600 text-white flex items-center justify-center gap-2 shadow-lg shadow-blue-200">
|
|
<ExternalLink className="w-3 h-3" /> DEMÓ MEGNYITÁSA
|
|
</a>
|
|
<button onClick={() => openFeedbackModal(order.id)} className="flex-grow sm:flex-grow-0 text-[10px] px-4 py-2.5 rounded-xl font-black bg-purple-100 text-purple-700 hover:bg-purple-200 uppercase tracking-wider">
|
|
Visszajelzés
|
|
</button>
|
|
</>
|
|
)}
|
|
{!needsFeedback && (
|
|
<span className={`text-[10px] px-4 py-2.5 rounded-xl font-black flex items-center justify-center gap-2 ${statusConfig.color} uppercase tracking-wider`}>
|
|
<statusConfig.icon className="w-3.5 h-3.5" /> {statusConfig.label}
|
|
</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 divide-y md:divide-y-0 md:divide-x divide-gray-100">
|
|
{/* PROJECT TIMELINE */}
|
|
<div className="p-5 md:p-6 bg-white">
|
|
<div className="flex items-center gap-2 mb-4 text-[10px] font-black text-gray-400 uppercase tracking-[0.2em]">
|
|
<History className="w-3 h-3" /> Projekt Történet
|
|
</div>
|
|
<div className="flex flex-col gap-4 relative before:absolute before:left-[7px] before:top-2 before:bottom-2 before:w-0.5 before:bg-gray-50">
|
|
{order.history && order.history.length > 0 ? (
|
|
order.history.map((h, idx) => (
|
|
<div key={h.id} className="relative pl-6 flex justify-between items-center group">
|
|
<div className={`absolute left-0 top-1.5 w-3.5 h-3.5 rounded-full border-2 border-white shadow-sm z-10 ${idx === 0 ? 'bg-primary' : 'bg-gray-200'}`}></div>
|
|
<div className="flex flex-col">
|
|
<span className={`text-xs font-bold ${idx === 0 ? 'text-gray-900' : 'text-gray-500'}`}>
|
|
{getStatusConfig(h.status).label}
|
|
</span>
|
|
<span className="text-[10px] text-gray-400 font-medium">
|
|
{new Date(h.changed_at).toLocaleString('hu-HU')}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
))
|
|
) : (
|
|
<p className="text-xs text-gray-400 italic pl-6">Nincs korábbi bejegyzés.</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* BILLS & DOCUMENTS / MAINTENANCE */}
|
|
<div className="p-5 md:p-6 bg-white flex flex-col justify-between">
|
|
<div>
|
|
<div className="flex items-center gap-2 mb-4 text-[10px] font-black text-gray-400 uppercase tracking-[0.2em]">
|
|
<FileText className="w-3 h-3" /> Dokumentumok
|
|
</div>
|
|
<div className="space-y-3">
|
|
{order.bills && order.bills.length > 0 ? (
|
|
order.bills.map((bill) => (
|
|
<a
|
|
key={bill.id}
|
|
href={bill.file_url}
|
|
target="_blank"
|
|
rel="noreferrer"
|
|
className="flex items-center justify-between p-3 rounded-xl border border-gray-100 hover:border-primary/30 hover:bg-purple-50/30 transition-all group"
|
|
>
|
|
<div className="flex items-center gap-3">
|
|
<div className={`w-8 h-8 rounded-lg flex items-center justify-center ${bill.type === 'advance' ? 'bg-blue-50 text-blue-600' : 'bg-green-50 text-green-600'}`}>
|
|
<CreditCard className="w-4 h-4" />
|
|
</div>
|
|
<div>
|
|
<p className="text-xs font-bold text-gray-900">{bill.type === 'advance' ? 'Előleg számla' : 'Végszámla'}</p>
|
|
<p className="text-[9px] text-gray-400 font-black uppercase tracking-widest">{new Date(bill.created_at).toLocaleDateString('hu-HU')}</p>
|
|
</div>
|
|
</div>
|
|
<FileDown className="w-4 h-4 text-gray-300 group-hover:text-primary group-hover:translate-y-0.5 transition-all flex-shrink-0" />
|
|
</a>
|
|
))
|
|
) : (
|
|
<div className="p-4 text-center bg-gray-50/50 rounded-2xl border border-dashed border-gray-100">
|
|
<p className="text-[10px] font-black text-gray-400 uppercase tracking-widest leading-relaxed">Még nincs számla.</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Maintenance Subscription Status (If Completed) */}
|
|
{order.subscription && (
|
|
<div className="mt-6 pt-4 border-t border-gray-50">
|
|
<div className="flex items-center gap-2 mb-3 text-[10px] font-black text-gray-400 uppercase tracking-[0.2em]">
|
|
<ShieldCheck className="w-3 h-3 text-primary" /> Karbantartás
|
|
</div>
|
|
<div className={`p-3 rounded-xl border flex items-center justify-between ${order.subscription.status === 'overdue' ? 'bg-red-50 border-red-100' : 'bg-green-50 border-green-100'}`}>
|
|
<div className="flex items-center gap-2">
|
|
<Calendar className={`w-4 h-4 ${order.subscription.status === 'overdue' ? 'text-red-500' : 'text-green-500'}`} />
|
|
<div>
|
|
<p className={`text-[10px] font-black uppercase tracking-wider ${order.subscription.status === 'overdue' ? 'text-red-700' : 'text-green-700'}`}>
|
|
{order.subscription.status === 'active' ? 'Aktív szolgáltatás' : 'Esedékes díj'}
|
|
</p>
|
|
<p className="text-xs font-bold text-gray-700">Fordulónap: {new Date(order.subscription.next_billing_date).toLocaleDateString('hu-HU')}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
) : (
|
|
<div className="bg-white p-10 rounded-3xl border-2 border-dashed border-gray-100 text-center">
|
|
<p className="text-gray-400 mb-6 italic">Még nincsenek leadott rendelései.</p>
|
|
<Link to="/#rendeles"><Button>Új projekt indítása <ArrowRight className="w-4 h-4 ml-2" /></Button></Link>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* SETTINGS COLUMN */}
|
|
<div className="space-y-6 md:space-y-8">
|
|
<div className="bg-white p-6 md:p-8 rounded-[32px] border border-gray-100 shadow-sm">
|
|
<div className="flex justify-between items-center mb-6">
|
|
<h3 className="font-bold text-gray-900">Beállítások</h3>
|
|
<button onClick={() => setShowSettings(true)} className="p-2 hover:bg-gray-50 rounded-xl transition-colors"><SettingsIcon className="w-5 h-5 text-gray-400" /></button>
|
|
</div>
|
|
<div className="space-y-4">
|
|
<div className="bg-gray-50 p-4 rounded-2xl overflow-hidden">
|
|
<p className="text-[10px] font-black text-gray-400 uppercase tracking-widest mb-1">E-mail</p>
|
|
<p className="text-sm font-bold text-gray-900 truncate">{user?.email}</p>
|
|
</div>
|
|
<Button fullWidth variant="outline" size="sm" onClick={() => setShowSettings(true)}>Profil szerkesztése</Button>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="bg-gradient-to-br from-primary/10 to-secondary/10 p-6 md:p-8 rounded-[32px] border border-primary/10 shadow-sm">
|
|
<h3 className="font-bold text-gray-900 mb-4 flex items-center gap-2 text-lg"><CheckCircle className="w-5 h-5 text-primary" /> Ügyfélközpont</h3>
|
|
<p className="text-xs text-gray-600 leading-relaxed mb-6">
|
|
Bármilyen kérdése van a számlázással vagy a fejlesztéssel kapcsolatban, írjon nekünk közvetlenül.
|
|
</p>
|
|
<a href="mailto:motionstudiohq@gmail.com" className="block text-center py-3 bg-white border border-gray-200 rounded-xl text-xs font-black text-primary uppercase tracking-widest hover:shadow-md transition-all shadow-sm">
|
|
Kapcsolatfelvétel
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="border-t border-gray-100 pt-6 flex justify-center md:justify-end">
|
|
<Button variant="outline" onClick={handleLogout} className="w-full md:w-auto border-red-200 text-red-600 hover:bg-red-50 hover:text-red-700 hover:border-red-300">
|
|
<LogOut className="w-4 h-4 mr-2" /> Kijelentkezés
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|