fix: consulting slots display and auth reload redirect
- Group consulting slots by date in admin order detail modal - Show time range from first slot start to last slot end - Display session count badge for multi-slot orders - Fix page reload redirecting to main page by ensuring loading state is properly synchronized with Supabase session initialization - Add mounted flag to prevent state updates after unmount 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -21,31 +21,46 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
const [isAdmin, setIsAdmin] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const { data: { subscription } } = supabase.auth.onAuthStateChange(
|
||||
(event, session) => {
|
||||
setSession(session);
|
||||
setUser(session?.user ?? null);
|
||||
|
||||
if (session?.user) {
|
||||
setTimeout(() => {
|
||||
checkAdminRole(session.user.id);
|
||||
}, 0);
|
||||
} else {
|
||||
setIsAdmin(false);
|
||||
}
|
||||
}
|
||||
);
|
||||
let mounted = true;
|
||||
|
||||
// First, get the initial session
|
||||
supabase.auth.getSession().then(({ data: { session } }) => {
|
||||
if (!mounted) return;
|
||||
|
||||
setSession(session);
|
||||
setUser(session?.user ?? null);
|
||||
|
||||
if (session?.user) {
|
||||
checkAdminRole(session.user.id);
|
||||
}
|
||||
|
||||
// Only set loading to false after initial session is loaded
|
||||
setLoading(false);
|
||||
});
|
||||
|
||||
return () => subscription.unsubscribe();
|
||||
// Then listen for auth state changes
|
||||
const { data: { subscription } } = supabase.auth.onAuthStateChange(
|
||||
(event, session) => {
|
||||
if (!mounted) return;
|
||||
|
||||
setSession(session);
|
||||
setUser(session?.user ?? null);
|
||||
|
||||
if (session?.user) {
|
||||
checkAdminRole(session.user.id);
|
||||
} else {
|
||||
setIsAdmin(false);
|
||||
}
|
||||
|
||||
// Ensure loading is false after auth state changes
|
||||
setLoading(false);
|
||||
}
|
||||
);
|
||||
|
||||
return () => {
|
||||
mounted = false;
|
||||
subscription.unsubscribe();
|
||||
};
|
||||
}, []);
|
||||
|
||||
const checkAdminRole = async (userId: string) => {
|
||||
|
||||
@@ -508,29 +508,50 @@ export default function AdminOrders() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Consulting Slots */}
|
||||
{consultingSlots.length > 0 && (
|
||||
{/* Consulting Slots - Grouped by Date */}
|
||||
{consultingSlots.length > 0 && (() => {
|
||||
// Group slots by date
|
||||
const slotsByDate = consultingSlots.reduce((acc, slot) => {
|
||||
if (!acc[slot.date]) {
|
||||
acc[slot.date] = [];
|
||||
}
|
||||
acc[slot.date].push(slot);
|
||||
return acc;
|
||||
}, {} as Record<string, typeof consultingSlots>);
|
||||
|
||||
return (
|
||||
<div className="border-t border-border pt-4">
|
||||
<p className="font-medium mb-3 flex items-center gap-2">
|
||||
<Video className="w-4 h-4" />
|
||||
Jadwal Konsultasi
|
||||
</p>
|
||||
<div className="space-y-2">
|
||||
{consultingSlots.map((slot) => (
|
||||
<div key={slot.id} className="border-2 border-border rounded-lg p-3 bg-background">
|
||||
<div className="flex items-start justify-between gap-3 mb-2">
|
||||
<div className="space-y-3">
|
||||
{Object.entries(slotsByDate).map(([date, slots]) => {
|
||||
const firstSlot = slots[0];
|
||||
const lastSlot = slots[slots.length - 1];
|
||||
const allSlotsHaveMeetLink = slots.every(s => s.meet_link);
|
||||
const meetLink = firstSlot.meet_link;
|
||||
|
||||
return (
|
||||
<div key={date} className="border-2 border-border rounded-lg p-3 bg-background">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-2 mb-1 flex-wrap">
|
||||
<Badge variant={slot.status === "confirmed" ? "default" : "secondary"} className="text-xs">
|
||||
{slot.status === "confirmed" ? "Terkonfirmasi" : slot.status}
|
||||
<Badge variant={firstSlot.status === "confirmed" ? "default" : "secondary"} className="text-xs">
|
||||
{firstSlot.status === "confirmed" ? "Terkonfirmasi" : firstSlot.status}
|
||||
</Badge>
|
||||
{slot.topic_category && (
|
||||
{firstSlot.topic_category && (
|
||||
<Badge variant="outline" className="text-xs">
|
||||
{slot.topic_category}
|
||||
{firstSlot.topic_category}
|
||||
</Badge>
|
||||
)}
|
||||
{slots.length > 1 && (
|
||||
<Badge variant="outline" className="text-xs">
|
||||
{slots.length} sesi
|
||||
</Badge>
|
||||
)}
|
||||
{/* Meet Link Status */}
|
||||
{slot.meet_link ? (
|
||||
{allSlotsHaveMeetLink ? (
|
||||
<Badge variant="outline" className="text-xs gap-1 border-green-500 text-green-700">
|
||||
<CheckCircle className="w-3 h-3" />
|
||||
Meet Link Ready
|
||||
@@ -543,7 +564,7 @@ export default function AdminOrders() {
|
||||
)}
|
||||
</div>
|
||||
<p className="text-sm font-medium">
|
||||
{new Date(slot.date).toLocaleDateString("id-ID", {
|
||||
{new Date(date).toLocaleDateString("id-ID", {
|
||||
weekday: "short",
|
||||
day: "numeric",
|
||||
month: "short",
|
||||
@@ -551,19 +572,19 @@ export default function AdminOrders() {
|
||||
})}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{slot.start_time.substring(0, 5)} - {slot.end_time.substring(0, 5)} WIB
|
||||
{firstSlot.start_time.substring(0, 5)} - {lastSlot.end_time.substring(0, 5)} WIB
|
||||
</p>
|
||||
{slot.notes && (
|
||||
{firstSlot.notes && (
|
||||
<p className="text-xs text-muted-foreground mt-1 italic">
|
||||
Catatan: {slot.notes}
|
||||
Catatan: {firstSlot.notes}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
{slot.meet_link && (
|
||||
{meetLink && (
|
||||
<Button asChild variant="outline" size="sm" className="gap-1">
|
||||
<a
|
||||
href={slot.meet_link}
|
||||
href={meetLink}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
@@ -576,19 +597,21 @@ export default function AdminOrders() {
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => openMeetLinkDialog(slot.id, slot.meet_link)}
|
||||
onClick={() => openMeetLinkDialog(firstSlot.id, meetLink)}
|
||||
className="gap-1"
|
||||
>
|
||||
<LinkIcon className="w-3 h-3" />
|
||||
{slot.meet_link ? "Update" : "Buat"} Link
|
||||
{meetLink ? "Update" : "Buat"} Link
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
);
|
||||
})()}
|
||||
|
||||
<div className="flex flex-col sm:flex-row gap-2 pt-4">
|
||||
{canRefundOrder(selectedOrder.payment_status, selectedOrder.refunded_at) && (
|
||||
|
||||
Reference in New Issue
Block a user