mirror of
https://github.com/Motion-Games/MotionWebStudio.git
synced 2026-04-21 09:00:53 +02:00
stripe changes
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { X, Info, CheckCircle, AlertTriangle, MessageSquare, Calendar, ChevronDown, ChevronUp, Wallet, ArrowLeft, RefreshCw, CreditCard } from 'lucide-react';
|
import { X, Info, CheckCircle, AlertTriangle, MessageSquare, Calendar, ChevronDown, ChevronUp, Wallet, ArrowLeft, RefreshCw, CreditCard, Globe } from 'lucide-react';
|
||||||
import { Button } from './Button';
|
import { Button } from './Button';
|
||||||
|
|
||||||
interface FeedbackModalProps {
|
interface FeedbackModalProps {
|
||||||
@@ -18,6 +18,7 @@ export const FeedbackModal: React.FC<FeedbackModalProps> = ({ isOpen, onClose, o
|
|||||||
|
|
||||||
// Approval Flow
|
// Approval Flow
|
||||||
const [approvalConfirmed, setApprovalConfirmed] = useState(false);
|
const [approvalConfirmed, setApprovalConfirmed] = useState(false);
|
||||||
|
const [approvedDomain, setApprovedDomain] = useState('');
|
||||||
|
|
||||||
// Revision Flow
|
// Revision Flow
|
||||||
const [designCheckboxes, setDesignCheckboxes] = useState<string[]>([]);
|
const [designCheckboxes, setDesignCheckboxes] = useState<string[]>([]);
|
||||||
@@ -47,9 +48,10 @@ export const FeedbackModal: React.FC<FeedbackModalProps> = ({ isOpen, onClose, o
|
|||||||
setShowPayment(false);
|
setShowPayment(false);
|
||||||
setApprovalConfirmed(false);
|
setApprovalConfirmed(false);
|
||||||
setRevisionConfirmed(false);
|
setRevisionConfirmed(false);
|
||||||
|
setApprovedDomain(order?.details?.domainName || '');
|
||||||
// Reset other fields if needed
|
// Reset other fields if needed
|
||||||
}
|
}
|
||||||
}, [isOpen]);
|
}, [isOpen, order]);
|
||||||
|
|
||||||
if (!isOpen) return null;
|
if (!isOpen) return null;
|
||||||
|
|
||||||
@@ -95,7 +97,8 @@ export const FeedbackModal: React.FC<FeedbackModalProps> = ({ isOpen, onClose, o
|
|||||||
submittedAt: new Date().toISOString(),
|
submittedAt: new Date().toISOString(),
|
||||||
approval: mainDecision === 'approved' ? {
|
approval: mainDecision === 'approved' ? {
|
||||||
confirmed: approvalConfirmed,
|
confirmed: approvalConfirmed,
|
||||||
paymentComplete: showPayment // Flag that payment step was shown
|
paymentComplete: showPayment, // Flag that payment step was shown
|
||||||
|
domain: approvedDomain // Pass the domain
|
||||||
} : null,
|
} : null,
|
||||||
revision: isRevision ? {
|
revision: isRevision ? {
|
||||||
design: { selected: designCheckboxes, comment: designText },
|
design: { selected: designCheckboxes, comment: designText },
|
||||||
@@ -270,21 +273,40 @@ export const FeedbackModal: React.FC<FeedbackModalProps> = ({ isOpen, onClose, o
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{mainDecision === 'approved' && (
|
{mainDecision === 'approved' && (
|
||||||
<div className="animate-fade-in bg-green-50 p-6 rounded-2xl border-2 border-green-200 shadow-inner">
|
<div className="space-y-6">
|
||||||
<h3 className="text-green-900 font-black mb-4 flex items-center gap-2 uppercase text-sm tracking-widest">
|
<div className="animate-fade-in bg-green-50 p-6 rounded-2xl border-2 border-green-200 shadow-inner">
|
||||||
<CheckCircle className="w-5 h-5" /> Megerősítés
|
<h3 className="text-green-900 font-black mb-4 flex items-center gap-2 uppercase text-sm tracking-widest">
|
||||||
</h3>
|
<CheckCircle className="w-5 h-5" /> Megerősítés
|
||||||
<label className="flex items-start cursor-pointer group">
|
</h3>
|
||||||
<input
|
<label className="flex items-start cursor-pointer group">
|
||||||
type="checkbox"
|
<input
|
||||||
checked={approvalConfirmed}
|
type="checkbox"
|
||||||
onChange={(e) => setApprovalConfirmed(e.target.checked)}
|
checked={approvalConfirmed}
|
||||||
className="mt-1 w-6 h-6 text-green-600 rounded-lg focus:ring-green-500 border-green-300"
|
onChange={(e) => setApprovalConfirmed(e.target.checked)}
|
||||||
/>
|
className="mt-1 w-6 h-6 text-green-600 rounded-lg focus:ring-green-500 border-green-300"
|
||||||
<span className="ml-4 text-sm text-green-800 font-bold leading-relaxed">
|
/>
|
||||||
Tudomásul veszem, hogy a jóváhagyással elfogadom a jelenlegi állapotot, és a folyamat a végleges elszámolással folytatódik.
|
<span className="ml-4 text-sm text-green-800 font-bold leading-relaxed">
|
||||||
</span>
|
Tudomásul veszem, hogy a jóváhagyással elfogadom a jelenlegi állapotot, és a folyamat a végleges elszámolással folytatódik.
|
||||||
</label>
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="animate-fade-in">
|
||||||
|
<label className="block text-xs font-black text-gray-900 uppercase tracking-widest mb-2 ml-1">Végleges Domain Név</label>
|
||||||
|
<div className="relative">
|
||||||
|
<Globe className="absolute left-4 top-3.5 w-5 h-5 text-gray-400" />
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={approvedDomain}
|
||||||
|
onChange={(e) => setApprovedDomain(e.target.value)}
|
||||||
|
placeholder="pl. mintaweboldal.hu"
|
||||||
|
className="w-full pl-12 pr-4 py-3 rounded-xl border border-gray-300 bg-white text-black focus:ring-4 focus:ring-green-100 focus:border-green-500 outline-none transition-all font-bold"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<p className="text-[10px] text-gray-500 mt-2 ml-1 font-medium leading-relaxed">
|
||||||
|
Kérlek add meg, milyen domain címen szeretnéd elérni a weboldalt. Átadás előtt ellenőrizzük a foglalhatóságot/elérhetőséget, és ha probléma merül fel, felvesszük veled a kapcsolatot.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
|
|
||||||
import React, { useState, useEffect, useRef } from 'react';
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
import { Menu, X, Code2, User, LogIn, UserPlus, LogOut, LayoutDashboard, ShieldAlert } from 'lucide-react';
|
import { Menu, X, Code2, User, LogIn, UserPlus, LogOut, LayoutDashboard, ShieldAlert, Crown } from 'lucide-react';
|
||||||
import { Link, useLocation, useNavigate } from 'react-router-dom';
|
import { Link, useLocation, useNavigate } from 'react-router-dom';
|
||||||
import { Button } from './Button';
|
import { Button } from './Button';
|
||||||
import { useAuth } from '../context/AuthContext';
|
import { useAuth } from '../context/AuthContext';
|
||||||
@@ -114,7 +115,11 @@ export const Navbar: React.FC = () => {
|
|||||||
aria-label="Felhasználói fiók"
|
aria-label="Felhasználói fiók"
|
||||||
>
|
>
|
||||||
<User className="w-6 h-6" />
|
<User className="w-6 h-6" />
|
||||||
{isAdmin && <div className="absolute top-1 right-1 w-2.5 h-2.5 bg-red-500 rounded-full border-2 border-white"></div>}
|
{isAdmin && (
|
||||||
|
<div className="absolute -top-1 -right-1 bg-white rounded-full p-0.5 shadow-sm border border-gray-100">
|
||||||
|
<Crown className="w-3.5 h-3.5 text-yellow-500 fill-yellow-500" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{isProfileOpen && (
|
{isProfileOpen && (
|
||||||
@@ -233,4 +238,4 @@ export const Navbar: React.FC = () => {
|
|||||||
)}
|
)}
|
||||||
</nav>
|
</nav>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
205
pages/Admin.tsx
205
pages/Admin.tsx
@@ -9,7 +9,8 @@ import {
|
|||||||
AlertTriangle, Archive, Send, Layout, History, MessageCircle, Info, ChevronDown, ChevronUp,
|
AlertTriangle, Archive, Send, Layout, History, MessageCircle, Info, ChevronDown, ChevronUp,
|
||||||
Upload, FileDown, Receipt, CreditCard, DollarSign, Plus, Trash2, Building2, User as UserIcon,
|
Upload, FileDown, Receipt, CreditCard, DollarSign, Plus, Trash2, Building2, User as UserIcon,
|
||||||
Palette, Zap, Lightbulb, Link as LinkIcon, ExternalLink, Target, FileStack, Star, Check,
|
Palette, Zap, Lightbulb, Link as LinkIcon, ExternalLink, Target, FileStack, Star, Check,
|
||||||
Copy, Cpu, Wand2, Eye, EyeOff, ShieldCheck, Calendar, Search, ArrowUpDown, Filter
|
Copy, Cpu, Wand2, Eye, EyeOff, ShieldCheck, Calendar, Search, ArrowUpDown, Filter,
|
||||||
|
TrendingUp, ArrowRight
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { Button } from '../components/Button';
|
import { Button } from '../components/Button';
|
||||||
import { supabase, isSupabaseConfigured } from '../lib/supabaseClient';
|
import { supabase, isSupabaseConfigured } from '../lib/supabaseClient';
|
||||||
@@ -57,6 +58,7 @@ interface AdminOrder {
|
|||||||
status: string;
|
status: string;
|
||||||
date: string;
|
date: string;
|
||||||
amount: string;
|
amount: string;
|
||||||
|
created_at: string;
|
||||||
details?: any;
|
details?: any;
|
||||||
history?: OrderHistoryEntry[];
|
history?: OrderHistoryEntry[];
|
||||||
emailLogs?: EmailLogEntry[];
|
emailLogs?: EmailLogEntry[];
|
||||||
@@ -157,8 +159,8 @@ const getEmailTemplate = (type: string, data: {
|
|||||||
body: `<div style="${baseStyle}">
|
body: `<div style="${baseStyle}">
|
||||||
<div style="${headerStyle}">MotionWeb</div>
|
<div style="${headerStyle}">MotionWeb</div>
|
||||||
<p>Kedves <strong>${data.customer}</strong>!</p>
|
<p>Kedves <strong>${data.customer}</strong>!</p>
|
||||||
<p>Köszönöm a visszajelzését a <strong>${data.package}</strong> demó verziójával kapcsolatban.</p>
|
<p>Köszönöm a visszajelzéseit! Megkezdtem a kért módosítások átvezetését a weboldalon.</p>
|
||||||
<p>A kért módosításokat feldolgoztam, és megkezdtem azok átvezetését a weboldalon. Amint elkészültem a frissített verzióval, e-mailben értesítem.</p>
|
<p>Hamarosan jelentkezem a frissített verzióval.</p>
|
||||||
<div style="${footerStyle}">Üdvözlettel,<br><strong>Balogh Bence Benedek</strong><br>MotionWeb | motionweb.hu</div>
|
<div style="${footerStyle}">Üdvözlettel,<br><strong>Balogh Bence Benedek</strong><br>MotionWeb | motionweb.hu</div>
|
||||||
</div>`
|
</div>`
|
||||||
},
|
},
|
||||||
@@ -495,9 +497,7 @@ export const Admin: React.FC = () => {
|
|||||||
try {
|
try {
|
||||||
if (typeof e === 'string') errorMessage = e;
|
if (typeof e === 'string') errorMessage = e;
|
||||||
else if (e instanceof Error) errorMessage = e.message;
|
else if (e instanceof Error) errorMessage = e.message;
|
||||||
else if (typeof e === 'object' && e !== null) {
|
else if (typeof e === 'object') errorMessage = JSON.stringify(e);
|
||||||
errorMessage = e.message || e.error_description || JSON.stringify(e);
|
|
||||||
}
|
|
||||||
} catch (jsonErr) {
|
} catch (jsonErr) {
|
||||||
errorMessage = "Nem szerializálható hiba objektum";
|
errorMessage = "Nem szerializálható hiba objektum";
|
||||||
}
|
}
|
||||||
@@ -583,22 +583,35 @@ export const Admin: React.FC = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Generate Stripe Link
|
||||||
|
const { data: checkoutData, error: checkoutError } = await supabase.functions.invoke('create-checkout-session', {
|
||||||
|
body: {
|
||||||
|
order_id: sub.order_id,
|
||||||
|
package_name: 'Maintenance',
|
||||||
|
payment_type: 'annual',
|
||||||
|
customer_email: sub.client_email
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (checkoutError || !checkoutData?.url) {
|
||||||
|
throw new Error('Nem sikerült generálni a fizetési linket.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const paymentLink = checkoutData.url;
|
||||||
|
|
||||||
const daysLeft = getDaysRemaining(sub.next_billing_date);
|
const daysLeft = getDaysRemaining(sub.next_billing_date);
|
||||||
const emailType = 'Előfizetés emlékeztető';
|
const emailType = 'Előfizetés emlékeztető';
|
||||||
const nowIso = new Date().toISOString();
|
const nowIso = new Date().toISOString();
|
||||||
|
|
||||||
// Placeholder payment link
|
|
||||||
const paymentLink = "https://stripe.com/payment_placeholder";
|
|
||||||
|
|
||||||
const orderData = sub.order as any; // Cast to avoid TS issues if types aren't perfect
|
const orderData = sub.order as any; // Cast to avoid TS issues if types aren't perfect
|
||||||
const customerName = orderData?.customer_name || 'Ügyfél';
|
const customerName = orderData?.customer_name || 'Ügyfél';
|
||||||
const customerEmail = sub.client_email || orderData?.customer_email;
|
const customerEmail = sub.client_email || orderData?.customer_email;
|
||||||
const domain = orderData?.details?.domainName;
|
const domain = orderData?.details?.domainName;
|
||||||
const amount = "59 990 Ft"; // Or use orderData.amount if applicable
|
const amount = "59 990 Ft";
|
||||||
|
|
||||||
const template = getEmailTemplate(emailType, {
|
const template = getEmailTemplate(emailType, {
|
||||||
customer: customerName,
|
customer: customerName,
|
||||||
package: 'Fenntartás', // Dummy package name for template type signature
|
package: 'Fenntartás',
|
||||||
domain: domain,
|
domain: domain,
|
||||||
daysLeft: daysLeft,
|
daysLeft: daysLeft,
|
||||||
paymentLink: paymentLink,
|
paymentLink: paymentLink,
|
||||||
@@ -927,22 +940,162 @@ export const Admin: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="animate-fade-in">
|
<div className="animate-fade-in">
|
||||||
{activeTab === 'overview' && (
|
{activeTab === 'overview' && (() => {
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 md:gap-8">
|
// -- Statistics Logic Calculation inside render for Overview Tab --
|
||||||
<div className="bg-white p-8 rounded-[32px] border border-gray-100 shadow-sm">
|
const orderStats = {
|
||||||
<p className="text-xs font-bold text-gray-400 uppercase mb-3 tracking-widest">Látogatók</p>
|
new: orders.filter(o => o.status === 'new').length,
|
||||||
<h3 className="text-4xl md:text-5xl font-black text-gray-900">{visitorStats.month}</h3>
|
inProgress: orders.filter(o => o.status === 'in_progress').length,
|
||||||
|
feedback: orders.filter(o => ['pending_feedback', 'waiting_feedback'].includes(o.status)).length,
|
||||||
|
completed: orders.filter(o => o.status === 'completed').length
|
||||||
|
};
|
||||||
|
const activeSubs = subscriptions.filter(s => s.status === 'active').length;
|
||||||
|
|
||||||
|
// Generate Yearly Chart Data
|
||||||
|
const currentYear = new Date().getFullYear();
|
||||||
|
const monthlyOrders = Array(12).fill(0);
|
||||||
|
orders.forEach(o => {
|
||||||
|
const d = new Date(o.created_at);
|
||||||
|
if (d.getFullYear() === currentYear) {
|
||||||
|
monthlyOrders[d.getMonth()]++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const maxOrderCount = Math.max(...monthlyOrders, 1); // Avoid div by zero
|
||||||
|
const monthLabels = ['Jan', 'Feb', 'Már', 'Ápr', 'Máj', 'Jún', 'Júl', 'Aug', 'Szep', 'Okt', 'Nov', 'Dec'];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-8">
|
||||||
|
<div className="flex items-center gap-3 mb-2">
|
||||||
|
<h3 className="text-xl font-black text-gray-900 tracking-tight">Statisztika & Elemzés</h3>
|
||||||
|
<div className="h-px bg-gray-200 flex-grow"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Visitors Section */}
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||||
|
<div className="bg-gradient-to-br from-blue-50 to-white p-6 rounded-[28px] border border-blue-100 shadow-sm relative overflow-hidden group">
|
||||||
|
<div className="absolute right-0 top-0 w-24 h-24 bg-blue-100 rounded-full blur-2xl -mr-10 -mt-10 group-hover:bg-blue-200 transition-colors"></div>
|
||||||
|
<div className="relative z-10">
|
||||||
|
<div className="flex items-center gap-3 mb-4">
|
||||||
|
<div className="p-2 bg-blue-100 rounded-xl text-blue-600"><Users className="w-5 h-5" /></div>
|
||||||
|
<span className="text-xs font-black text-blue-800 uppercase tracking-widest">Heti Látogatók</span>
|
||||||
|
</div>
|
||||||
|
<h3 className="text-4xl font-black text-gray-900">{visitorStats.week}</h3>
|
||||||
|
<p className="text-[10px] text-gray-400 font-bold mt-2 uppercase tracking-wide">Aktuális hét</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="bg-gradient-to-br from-purple-50 to-white p-6 rounded-[28px] border border-purple-100 shadow-sm relative overflow-hidden group">
|
||||||
|
<div className="absolute right-0 top-0 w-24 h-24 bg-purple-100 rounded-full blur-2xl -mr-10 -mt-10 group-hover:bg-purple-200 transition-colors"></div>
|
||||||
|
<div className="relative z-10">
|
||||||
|
<div className="flex items-center gap-3 mb-4">
|
||||||
|
<div className="p-2 bg-purple-100 rounded-xl text-purple-600"><Calendar className="w-5 h-5" /></div>
|
||||||
|
<span className="text-xs font-black text-purple-800 uppercase tracking-widest">Havi Látogatók</span>
|
||||||
|
</div>
|
||||||
|
<h3 className="text-4xl font-black text-gray-900">{visitorStats.month}</h3>
|
||||||
|
<p className="text-[10px] text-gray-400 font-bold mt-2 uppercase tracking-wide">Becsült aktivitás</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="bg-gradient-to-br from-gray-50 to-white p-6 rounded-[28px] border border-gray-200 shadow-sm relative overflow-hidden group">
|
||||||
|
<div className="absolute right-0 top-0 w-24 h-24 bg-gray-100 rounded-full blur-2xl -mr-10 -mt-10 group-hover:bg-gray-200 transition-colors"></div>
|
||||||
|
<div className="relative z-10">
|
||||||
|
<div className="flex items-center gap-3 mb-4">
|
||||||
|
<div className="p-2 bg-gray-200 rounded-xl text-gray-600"><Globe className="w-5 h-5" /></div>
|
||||||
|
<span className="text-xs font-black text-gray-600 uppercase tracking-widest">Összes Látogató</span>
|
||||||
|
</div>
|
||||||
|
<h3 className="text-4xl font-black text-gray-900">{visitorStats.month}</h3>
|
||||||
|
<p className="text-[10px] text-gray-400 font-bold mt-2 uppercase tracking-wide">Indulás óta</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Orders Funnel & Active Subs */}
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-4 gap-6">
|
||||||
|
{/* Funnel */}
|
||||||
|
<div className="lg:col-span-3 bg-white p-8 rounded-[32px] border border-gray-100 shadow-sm">
|
||||||
|
<h4 className="text-sm font-black text-gray-900 uppercase tracking-widest mb-6 flex items-center gap-2">
|
||||||
|
<Activity className="w-4 h-4 text-primary" /> Rendelési Folyamat
|
||||||
|
</h4>
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
|
||||||
|
<div className="p-5 rounded-2xl bg-blue-50 border border-blue-100 flex flex-col justify-between h-32 relative overflow-hidden">
|
||||||
|
<div className="absolute right-2 top-2 opacity-10"><ShoppingCart className="w-16 h-16 text-blue-600" /></div>
|
||||||
|
<span className="text-xs font-black text-blue-600 uppercase tracking-widest">Új Rendelés</span>
|
||||||
|
<div className="flex items-end justify-between">
|
||||||
|
<h3 className="text-3xl font-black text-blue-900">{orderStats.new}</h3>
|
||||||
|
<ArrowRight className="w-5 h-5 text-blue-300" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="p-5 rounded-2xl bg-yellow-50 border border-yellow-100 flex flex-col justify-between h-32 relative overflow-hidden">
|
||||||
|
<div className="absolute right-2 top-2 opacity-10"><RefreshCw className="w-16 h-16 text-yellow-600" /></div>
|
||||||
|
<span className="text-xs font-black text-yellow-600 uppercase tracking-widest">Folyamatban</span>
|
||||||
|
<div className="flex items-end justify-between">
|
||||||
|
<h3 className="text-3xl font-black text-yellow-900">{orderStats.inProgress}</h3>
|
||||||
|
<ArrowRight className="w-5 h-5 text-yellow-300" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="p-5 rounded-2xl bg-purple-50 border border-purple-100 flex flex-col justify-between h-32 relative overflow-hidden">
|
||||||
|
<div className="absolute right-2 top-2 opacity-10"><MessageSquare className="w-16 h-16 text-purple-600" /></div>
|
||||||
|
<span className="text-xs font-black text-purple-600 uppercase tracking-widest">Visszajelzés</span>
|
||||||
|
<div className="flex items-end justify-between">
|
||||||
|
<h3 className="text-3xl font-black text-purple-900">{orderStats.feedback}</h3>
|
||||||
|
<CheckCircle className="w-5 h-5 text-purple-300" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Active Subs */}
|
||||||
|
<div className="bg-[#0f172a] p-8 rounded-[32px] text-white flex flex-col justify-between relative overflow-hidden shadow-xl">
|
||||||
|
<div className="absolute top-0 right-0 w-32 h-32 bg-primary/20 rounded-full blur-3xl pointer-events-none"></div>
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center gap-2 text-primary mb-4">
|
||||||
|
<ShieldCheck className="w-5 h-5" />
|
||||||
|
<span className="text-xs font-black uppercase tracking-widest">Aktív Előfizetések</span>
|
||||||
|
</div>
|
||||||
|
<h3 className="text-5xl font-black mb-1">{activeSubs}</h3>
|
||||||
|
<p className="text-xs text-gray-400 font-bold uppercase tracking-wide">Karbantartott oldalak</p>
|
||||||
|
</div>
|
||||||
|
<div className="mt-6 pt-6 border-t border-gray-800">
|
||||||
|
<div className="flex justify-between items-center text-xs font-bold text-gray-300">
|
||||||
|
<span>Becsült Bevétel (Havi)</span>
|
||||||
|
<span className="text-primary">{Math.round(activeSubs * (59990/12)).toLocaleString()} Ft</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Yearly Chart */}
|
||||||
|
<div className="bg-white p-8 rounded-[32px] border border-gray-100 shadow-sm">
|
||||||
|
<div className="flex justify-between items-center mb-8">
|
||||||
|
<h4 className="text-sm font-black text-gray-900 uppercase tracking-widest flex items-center gap-2">
|
||||||
|
<TrendingUp className="w-4 h-4 text-primary" /> Rendelések alakulása ({currentYear})
|
||||||
|
</h4>
|
||||||
|
<div className="bg-gray-100 px-3 py-1 rounded-lg text-[10px] font-bold text-gray-500 uppercase">Havi bontás</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="h-64 w-full flex items-end justify-between gap-2 sm:gap-4 px-2">
|
||||||
|
{monthlyOrders.map((count, idx) => {
|
||||||
|
const heightPercentage = maxOrderCount > 0 ? (count / maxOrderCount) * 100 : 0;
|
||||||
|
return (
|
||||||
|
<div key={idx} className="flex-1 flex flex-col items-center gap-2 group">
|
||||||
|
<div className="w-full relative flex items-end justify-center h-48 bg-gray-50 rounded-t-xl rounded-b-md overflow-hidden">
|
||||||
|
<div
|
||||||
|
className="w-full bg-primary/80 group-hover:bg-primary transition-all duration-500 rounded-t-lg relative min-h-[4px]"
|
||||||
|
style={{ height: `${heightPercentage}%` }}
|
||||||
|
>
|
||||||
|
{count > 0 && (
|
||||||
|
<div className="absolute -top-8 left-1/2 -translate-x-1/2 bg-gray-900 text-white text-[10px] font-bold px-2 py-1 rounded opacity-0 group-hover:opacity-100 transition-opacity">
|
||||||
|
{count} db
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span className="text-[10px] font-bold text-gray-400 uppercase tracking-wide group-hover:text-primary transition-colors">{monthLabels[idx]}</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-white p-8 rounded-[32px] border border-gray-100 shadow-sm">
|
);
|
||||||
<p className="text-xs font-bold text-gray-400 uppercase mb-3 tracking-widest">Rendelések</p>
|
})()}
|
||||||
<h3 className="text-4xl md:text-5xl font-black text-gray-900">{orders.length}</h3>
|
|
||||||
</div>
|
|
||||||
<div className="bg-white p-8 rounded-[32px] border border-gray-100 shadow-sm">
|
|
||||||
<p className="text-xs font-bold text-gray-400 uppercase mb-3 tracking-widest">Aktív Előfizetések</p>
|
|
||||||
<h3 className="text-4xl md:text-5xl font-black text-gray-900">{subscriptions.filter(s => s.status === 'active').length}</h3>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{activeTab === 'users' && (
|
{activeTab === 'users' && (
|
||||||
<div className="bg-white rounded-[32px] border border-gray-100 shadow-sm overflow-hidden">
|
<div className="bg-white rounded-[32px] border border-gray-100 shadow-sm overflow-hidden">
|
||||||
|
|||||||
@@ -213,6 +213,18 @@ export const Dashboard: React.FC = () => {
|
|||||||
const paymentSummary = currentOrder.details?.payment_summary;
|
const paymentSummary = currentOrder.details?.payment_summary;
|
||||||
const isStandardPackage = ['Landing Page', 'Pro Web'].includes(currentOrder.package);
|
const isStandardPackage = ['Landing Page', 'Pro Web'].includes(currentOrder.package);
|
||||||
|
|
||||||
|
// Extract the new domain if provided during approval
|
||||||
|
const approvedDomain = feedbackData.approval?.domain;
|
||||||
|
|
||||||
|
// Construct base updated details
|
||||||
|
const updatedDetails = {
|
||||||
|
...(currentOrder.details || {}),
|
||||||
|
latestFeedback: feedbackData,
|
||||||
|
feedbackDate: new Date().toISOString(),
|
||||||
|
// If approved domain is provided, update the main domainName field so it's visible in Admin
|
||||||
|
...(approvedDomain ? { domainName: approvedDomain } : {})
|
||||||
|
};
|
||||||
|
|
||||||
// Check if payment is needed: Approved + Standard Package + Not Custom Price + Has Remaining Balance
|
// 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)
|
// 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.
|
// But we double check here for security.
|
||||||
@@ -240,12 +252,7 @@ export const Dashboard: React.FC = () => {
|
|||||||
|
|
||||||
if (checkoutData?.url) {
|
if (checkoutData?.url) {
|
||||||
// Update details with feedback content BEFORE redirecting
|
// 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)
|
// This ensures we save the "Approval" and the new domain 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);
|
await supabase.from('orders').update({ details: updatedDetails }).eq('id', selectedOrderId);
|
||||||
|
|
||||||
// Redirect to Stripe
|
// Redirect to Stripe
|
||||||
@@ -257,12 +264,6 @@ export const Dashboard: React.FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If no payment needed (e.g. revision request, or custom price, or enterprise), update status directly
|
// 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 newStatus = 'in_progress'; // Send back to dev in both cases (to finalize or revise)
|
||||||
|
|
||||||
const { error: updateError } = await supabase.from('orders').update({
|
const { error: updateError } = await supabase.from('orders').update({
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ serve(async (req) => {
|
|||||||
priceId = 'price_1SiIif25dc768V7UqsfSi345'; // Pro Web - Fennmaradó
|
priceId = 'price_1SiIif25dc768V7UqsfSi345'; // Pro Web - Fennmaradó
|
||||||
}
|
}
|
||||||
} else if (pkg === 'Maintenance' || pkg === 'Fenntartás') {
|
} else if (pkg === 'Maintenance' || pkg === 'Fenntartás') {
|
||||||
priceId = 'price_1SiIm025dc768V7UDFMZHDox'; // Éves fenntartás
|
priceId = 'price_1SiMK525dc768V7UDxZugw0Y'; // Éves fenntartás
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Validate Logic
|
// 4. Validate Logic
|
||||||
|
|||||||
Reference in New Issue
Block a user