Implement post-implementation refinements
Features implemented: 1. Expired QRIS order handling with dual-path approach - Product orders: QR regeneration button - Consulting orders: Immediate cancellation with slot release 2. Standardized status badge wording to "Pending" 3. Fixed TypeScript error in MemberDashboard 4. Dynamic badge colors from branding settings 5. Dynamic page title from branding settings 6. Logo/favicon file upload with auto-delete 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -11,6 +11,7 @@ import { formatIDR } from "@/lib/format";
|
||||
import { Video, Calendar, BookOpen, ArrowRight, Package, Receipt, ShoppingBag } from "lucide-react";
|
||||
import { WhatsAppBanner } from "@/components/WhatsAppBanner";
|
||||
import { ConsultingHistory } from "@/components/reviews/ConsultingHistory";
|
||||
import { UnpaidOrderAlert } from "@/components/UnpaidOrderAlert";
|
||||
|
||||
interface UserAccess {
|
||||
id: string;
|
||||
@@ -31,11 +32,17 @@ interface Order {
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
interface UnpaidConsultingOrder {
|
||||
order_id: string;
|
||||
qr_expires_at: string;
|
||||
}
|
||||
|
||||
export default function MemberDashboard() {
|
||||
const { user, loading: authLoading } = useAuth();
|
||||
const navigate = useNavigate();
|
||||
const [access, setAccess] = useState<UserAccess[]>([]);
|
||||
const [recentOrders, setRecentOrders] = useState<Order[]>([]);
|
||||
const [unpaidConsultingOrders, setUnpaidConsultingOrders] = useState<UnpaidConsultingOrder[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [hasWhatsApp, setHasWhatsApp] = useState(true);
|
||||
|
||||
@@ -44,6 +51,57 @@ export default function MemberDashboard() {
|
||||
else if (user) fetchData();
|
||||
}, [user, authLoading]);
|
||||
|
||||
// Fetch unpaid consulting orders
|
||||
useEffect(() => {
|
||||
if (!user) return;
|
||||
|
||||
const fetchUnpaidOrders = async () => {
|
||||
const { data, error } = await supabase
|
||||
.from('consulting_slots')
|
||||
.select(`
|
||||
order_id,
|
||||
orders (
|
||||
id,
|
||||
payment_status,
|
||||
qr_expires_at
|
||||
)
|
||||
`)
|
||||
.eq('orders.payment_status', 'pending')
|
||||
.eq('status', 'pending_payment')
|
||||
.gt('orders.qr_expires_at', new Date().toISOString())
|
||||
.order('created_at', { ascending: false });
|
||||
|
||||
if (!error && data) {
|
||||
// Get unique order IDs
|
||||
const uniqueOrders = Array.from(
|
||||
new Set(data.map((item: any) => item.order_id))
|
||||
).map((orderId) => {
|
||||
// Find the corresponding order data
|
||||
const orderData = data.find((item: any) => item.order_id === orderId);
|
||||
return {
|
||||
order_id: orderId,
|
||||
qr_expires_at: (orderData as any)?.orders?.qr_expires_at || ''
|
||||
};
|
||||
});
|
||||
setUnpaidConsultingOrders(uniqueOrders);
|
||||
}
|
||||
};
|
||||
|
||||
fetchUnpaidOrders();
|
||||
}, [user]);
|
||||
|
||||
// Auto-hide expired orders every 30 seconds
|
||||
useEffect(() => {
|
||||
const checkExpiry = () => {
|
||||
setUnpaidConsultingOrders(prev =>
|
||||
prev.filter(order => new Date(order.qr_expires_at) > new Date())
|
||||
);
|
||||
};
|
||||
|
||||
const interval = setInterval(checkExpiry, 30000); // Check every 30s
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
const fetchData = async () => {
|
||||
const [accessRes, ordersRes, paidOrdersRes, profileRes] = await Promise.all([
|
||||
supabase
|
||||
@@ -123,6 +181,16 @@ export default function MemberDashboard() {
|
||||
<h1 className="text-4xl font-bold mb-2">Dashboard</h1>
|
||||
<p className="text-muted-foreground mb-8">Selamat datang kembali!</p>
|
||||
|
||||
{/* Unpaid Order Alert - shown when user has unpaid consulting orders */}
|
||||
{unpaidConsultingOrders.length > 0 && (
|
||||
<div className="mb-6">
|
||||
<UnpaidOrderAlert
|
||||
orderId={unpaidConsultingOrders[0].order_id}
|
||||
expiresAt={unpaidConsultingOrders[0].qr_expires_at}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!hasWhatsApp && <WhatsAppBanner />}
|
||||
|
||||
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3 mb-8">
|
||||
@@ -225,7 +293,7 @@ export default function MemberDashboard() {
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
<Badge className={order.payment_status === "paid" ? "bg-accent" : "bg-muted"}>
|
||||
<Badge className={order.payment_status === "paid" ? "bg-brand-accent text-white" : "bg-muted text-primary"}>
|
||||
{order.payment_status === "paid" ? "Lunas" : "Pending"}
|
||||
</Badge>
|
||||
<span className="font-bold">{formatIDR(order.total_amount)}</span>
|
||||
|
||||
Reference in New Issue
Block a user