diff --git a/apps/dimentorin/src/app/(public)/_components/header.tsx b/apps/dimentorin/src/app/(public)/_components/header.tsx
index 5d78641..93a418d 100644
--- a/apps/dimentorin/src/app/(public)/_components/header.tsx
+++ b/apps/dimentorin/src/app/(public)/_components/header.tsx
@@ -8,6 +8,7 @@ import { Link, NavLink, useLocation } from "react-router-dom";
const MENUS: { label: string; href: string }[] = [
{ label: "Home", href: "/" },
{ label: 'Mentoring', href: '/mentoring' },
+ { label: 'Sesi Saya', href: '/mentoring/my-sessions' },
{ label: 'Resources', href: '/resources' },
{ label: 'Articles', href: '/articles' },
]
diff --git a/apps/dimentorin/src/app/(public)/mentoring/[id]/_components/modals/appointment/steps/qris-payement.tsx b/apps/dimentorin/src/app/(public)/mentoring/[id]/_components/modals/appointment/steps/qris-payement.tsx
index 97bd96b..8965a61 100644
--- a/apps/dimentorin/src/app/(public)/mentoring/[id]/_components/modals/appointment/steps/qris-payement.tsx
+++ b/apps/dimentorin/src/app/(public)/mentoring/[id]/_components/modals/appointment/steps/qris-payement.tsx
@@ -47,9 +47,9 @@ export const QrisPaymentStep: FC = ({ payment }) => {
Rp. {(payment?.total ?? 52000).toLocaleString("id-ID")}
-
+
- Ref: {payment?.external_ref}
+ Ref: {payment?.externalRef}
@@ -60,10 +60,19 @@ export const QrisPaymentStep: FC = ({ payment }) => {
QR Code Pembayaran
-
+
}
+ >
+
+
- Berlaku hingga {payment ? new Date(payment.expires_at).toLocaleString("id-ID") : "-"}
+ Berlaku hingga {payment ? new Date(payment.expiresAt).toLocaleString("id-ID") : "-"}
diff --git a/apps/dimentorin/src/app/(public)/mentoring/[id]/_components/modals/appointment/steps/va-payment.tsx b/apps/dimentorin/src/app/(public)/mentoring/[id]/_components/modals/appointment/steps/va-payment.tsx
index ad1c4b0..2f15406 100644
--- a/apps/dimentorin/src/app/(public)/mentoring/[id]/_components/modals/appointment/steps/va-payment.tsx
+++ b/apps/dimentorin/src/app/(public)/mentoring/[id]/_components/modals/appointment/steps/va-payment.tsx
@@ -59,14 +59,14 @@ export const VAPaymentStep: FC = ({ payment }) => {
Kode Virtual Account
- {payment?.external_ref || "8091239861969812"}
+ {payment?.externalRef || "8091239861969812"}
A/N IMPHNEN
-
+
- Berlaku hingga {new Date(payment!.expires_at).toLocaleString("id-ID")}
+ Berlaku hingga {new Date(payment!.expiresAt).toLocaleString("id-ID")}
diff --git a/apps/dimentorin/src/app/(public)/mentoring/mentor-dashboard/page.tsx b/apps/dimentorin/src/app/(public)/mentoring/mentor-dashboard/page.tsx
new file mode 100644
index 0000000..a3d3ac8
--- /dev/null
+++ b/apps/dimentorin/src/app/(public)/mentoring/mentor-dashboard/page.tsx
@@ -0,0 +1,220 @@
+import { FC, ReactElement } from 'react';
+import { Link } from 'react-router-dom';
+import { Button } from '@imphnen-frontend-service/ui/atoms';
+import { For, Show, cn } from '@imphnen-frontend-service/utils';
+import {
+ useGetMentorMe,
+ useGetMentorSessions,
+ useGetSessionPayments,
+ usePostConfirmPayment,
+ usePostRefreshPayment,
+} from '@imphnen-frontend-service/service';
+import { ReloadOutlined, CheckOutlined } from '@ant-design/icons';
+import { toast } from 'sonner';
+
+const STATUS_STYLE: Record = {
+ pending: 'bg-amber-100 text-amber-700',
+ confirmed: 'bg-emerald-100 text-emerald-700',
+ completed: 'bg-primary-100 text-primary-700',
+ cancelled: 'bg-red-100 text-red-600',
+};
+
+const PAYMENT_STYLE: Record = {
+ pending: 'bg-amber-100 text-amber-700',
+ paid: 'bg-emerald-100 text-emerald-700',
+ expired: 'bg-neutral-200 text-neutral-600',
+};
+
+const formatDate = (iso: string) => {
+ try {
+ return new Date(iso).toLocaleString('id-ID', {
+ day: 'numeric',
+ month: 'short',
+ year: 'numeric',
+ hour: '2-digit',
+ minute: '2-digit',
+ });
+ } catch {
+ return iso;
+ }
+};
+
+const formatRupiah = (n: number) =>
+ new Intl.NumberFormat('id-ID', {
+ style: 'currency',
+ currency: 'IDR',
+ maximumFractionDigits: 0,
+ }).format(n);
+
+const SessionCard: FC<{ sessionId: string }> = ({ sessionId }) => {
+ const { data: payments } = useGetSessionPayments(sessionId);
+ const refreshPayment = usePostRefreshPayment();
+ const confirmPayment = usePostConfirmPayment();
+
+ const payment = (payments ?? [])[0];
+
+ return (
+
+
+
+ Pembayaran: {payment?.status}
+
+
{payment ? formatRupiah(payment.total) : ''}
+
+
+ {
+ try {
+ if (payment) await refreshPayment.mutateAsync(payment.id);
+ toast.success('Status pembayaran diperbarui');
+ } catch {
+ toast.error('Gagal memperbarui status');
+ }
+ }}
+ >
+ Refresh
+
+
+ {
+ try {
+ if (payment) await confirmPayment.mutateAsync(payment.id);
+ toast.success('Pembayaran dikonfirmasi โ
');
+ } catch {
+ toast.error('Gagal mengonfirmasi pembayaran');
+ }
+ }}
+ >
+ Konfirmasi
+
+
+
+
+ );
+};
+
+export const Components: FC = (): ReactElement => {
+ const { data: mentor, isLoading: loadingMentor } = useGetMentorMe();
+ const mentorId = mentor?.id ?? '';
+ const { data: sessions, isLoading } = useGetMentorSessions(mentorId);
+
+ return (
+
+
+
+
+ Dashboard Mentor
+
+
+ Permintaan sesi yang masuk ke kamu beserta status pembayarannya.
+
+
+
+
0}
+ fallback={
+
+
+ Belum ada permintaan sesi mentoring.
+
+
+ }
+ >
+
+
+ {(session) => (
+
+
+
+ {session.topic}
+
+
+ {session.status}
+
+
+
+
+ ๐
{formatDate(session.scheduled_at)}
+
+
+ ๐งโ๐{' '}
+ {session.mentee_fullname ?? session.mentee_email ?? 'Mentee'}
+ {' ยท '}
+ {session.session_type === 'video_call'
+ ? 'Video Call'
+ : 'Chat'}
+
+
+
+
+
+
+
+ Lihat Detail
+
+
+
+
+ )}
+
+
+
+ }
+ >
+
+ {[0, 1].map((i) => (
+
+ ))}
+
+
+ }
+ >
+
+
+ Kamu belum terdaftar sebagai mentor. Daftar dulu untuk menerima
+ permintaan sesi.
+
+
+
Daftar Jadi Mentor
+
+
+
+
+
+ );
+};
+
+export default Components;
diff --git a/apps/dimentorin/src/app/(public)/mentoring/my-sessions/page.tsx b/apps/dimentorin/src/app/(public)/mentoring/my-sessions/page.tsx
new file mode 100644
index 0000000..4714b24
--- /dev/null
+++ b/apps/dimentorin/src/app/(public)/mentoring/my-sessions/page.tsx
@@ -0,0 +1,191 @@
+import { FC, ReactElement } from 'react';
+import { Link } from 'react-router-dom';
+import { Button } from '@imphnen-frontend-service/ui/atoms';
+import { For, Show, cn } from '@imphnen-frontend-service/utils';
+import {
+ useGetMySessions,
+ useGetMyPayments,
+ usePostRefreshPayment,
+} from '@imphnen-frontend-service/service';
+import { ReloadOutlined } from '@ant-design/icons';
+import { toast } from 'sonner';
+
+const STATUS_STYLE: Record = {
+ pending: 'bg-amber-100 text-amber-700',
+ confirmed: 'bg-emerald-100 text-emerald-700',
+ completed: 'bg-primary-100 text-primary-700',
+ cancelled: 'bg-red-100 text-red-600',
+};
+
+const PAYMENT_STYLE: Record = {
+ pending: 'bg-amber-100 text-amber-700',
+ paid: 'bg-emerald-100 text-emerald-700',
+ expired: 'bg-neutral-200 text-neutral-600',
+};
+
+const formatDate = (iso: string) => {
+ try {
+ return new Date(iso).toLocaleString('id-ID', {
+ day: 'numeric',
+ month: 'short',
+ year: 'numeric',
+ hour: '2-digit',
+ minute: '2-digit',
+ });
+ } catch {
+ return iso;
+ }
+};
+
+const formatRupiah = (n: number) =>
+ new Intl.NumberFormat('id-ID', {
+ style: 'currency',
+ currency: 'IDR',
+ maximumFractionDigits: 0,
+ }).format(n);
+
+export const Components: FC = (): ReactElement => {
+ const { data: sessions, isLoading } = useGetMySessions();
+ const { data: payments } = useGetMyPayments();
+ const refreshPayment = usePostRefreshPayment();
+
+ const paymentBySession = new Map(
+ (payments ?? []).map((p) => [p.sessionId, p])
+ );
+
+ const handleRefresh = async (paymentId: string) => {
+ try {
+ await refreshPayment.mutateAsync(paymentId);
+ const updated = paymentBySession.get(
+ (payments ?? []).find((p) => p.id === paymentId)?.sessionId ?? ''
+ );
+ toast.success(
+ updated?.status === 'paid'
+ ? 'Pembayaran berhasil dikonfirmasi otomatis โ
'
+ : 'Status pembayaran masih menunggu pembayaran'
+ );
+ } catch {
+ toast.error('Gagal memperbarui status pembayaran');
+ }
+ };
+
+ return (
+
+
+
+
+ Sesi Mentoring Saya
+
+
+ Pantau jadwal, status pembayaran, dan konfirmasi sesi kamu.
+
+
+
+
0}
+ fallback={
+
+
+ Kamu belum memiliki sesi mentoring.
+
+
+
Cari Mentor
+
+
+ }
+ >
+
+ {(sessions?.sessions ?? []).map((session) => {
+ if (!session) return null;
+ const payment = paymentBySession.get(session.id);
+ return (
+
+
+
+ {session.topic}
+
+
+ {session.status}
+
+
+
+
+ ๐
{formatDate(session.scheduled_at)}
+
+
+ โฑ๏ธ {session.duration_minutes} menit ยท{' '}
+ {session.session_type === 'video_call'
+ ? 'Video Call'
+ : 'Chat'}
+
+
+
+
+
+ {payment?.status === 'paid' ? 'Lunas' : 'Menunggu pembayaran'}
+
+ {payment ? formatRupiah(payment.total) : ''}
+
+
+
+
+
+ payment && handleRefresh(payment.id)}
+ >
+ Cek Status
+
+
+
+
+
+ Detail Mentor
+
+
+
+
+
+ );
+ })}
+
+
+ }
+ >
+
+ {[0, 1].map((i) => (
+
+ ))}
+
+
+
+
+ );
+};
+
+export default Components;
diff --git a/libs/service/src/api/dimentorin/index.ts b/libs/service/src/api/dimentorin/index.ts
index f60b464..a8281b6 100644
--- a/libs/service/src/api/dimentorin/index.ts
+++ b/libs/service/src/api/dimentorin/index.ts
@@ -185,6 +185,16 @@ export const getPaymentById = async (id: string): Promise => {
return data.data;
};
+export const getSessionPayments = async (
+ sessionId: string
+): Promise => {
+ const { data } = await api({
+ method: 'GET',
+ url: `/dimentorin/payments/session/${sessionId}`,
+ });
+ return data.data;
+};
+
export const postConfirmPayment = async (
id: string
): Promise => {
@@ -194,3 +204,13 @@ export const postConfirmPayment = async (
});
return data;
};
+
+export const postRefreshPayment = async (
+ id: string
+): Promise => {
+ const { data } = await api({
+ method: 'POST',
+ url: `/dimentorin/payments/${id}/refresh`,
+ });
+ return data.data;
+};
diff --git a/libs/service/src/hooks/dimentorin/index.ts b/libs/service/src/hooks/dimentorin/index.ts
index 83659c3..c701bd3 100644
--- a/libs/service/src/hooks/dimentorin/index.ts
+++ b/libs/service/src/hooks/dimentorin/index.ts
@@ -1,4 +1,4 @@
-import { useMutation, useQuery, UseQueryResult } from '@tanstack/react-query';
+import { useMutation, useQuery, useQueryClient, UseQueryResult } from '@tanstack/react-query';
import {
getArticleById,
getArticleBySlug,
@@ -14,9 +14,11 @@ import {
getMyPayments,
getMySessions,
getPaymentById,
+ getSessionPayments,
postBookSession,
postConfirmPayment,
postCreatePayment,
+ postRefreshPayment,
postRegisterMentor,
postSessionFeedback,
} from '../../api/dimentorin';
@@ -168,10 +170,39 @@ export const useGetPaymentById = (
});
};
+export const useGetSessionPayments = (
+ sessionId: string
+): UseQueryResult => {
+ return useQuery({
+ queryKey: ['get-session-payments', sessionId],
+ queryFn: async () => await getSessionPayments(sessionId),
+ enabled: !!sessionId,
+ });
+};
+
export const usePostConfirmPayment = () => {
+ const queryClient = useQueryClient();
return useMutation({
mutationKey: ['post-confirm-payment'],
mutationFn: async (id: string) => await postConfirmPayment(id),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ['get-my-payments'] });
+ queryClient.invalidateQueries({ queryKey: ['get-my-sessions'] });
+ queryClient.invalidateQueries({ queryKey: ['get-mentor-sessions'] });
+ },
+ });
+};
+
+export const usePostRefreshPayment = () => {
+ const queryClient = useQueryClient();
+ return useMutation({
+ mutationKey: ['post-refresh-payment'],
+ mutationFn: async (id: string) => await postRefreshPayment(id),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ['get-my-payments'] });
+ queryClient.invalidateQueries({ queryKey: ['get-my-sessions'] });
+ queryClient.invalidateQueries({ queryKey: ['get-mentor-sessions'] });
+ },
});
};
diff --git a/libs/service/src/types/dimentorin/index.ts b/libs/service/src/types/dimentorin/index.ts
index 807683d..61a9857 100644
--- a/libs/service/src/types/dimentorin/index.ts
+++ b/libs/service/src/types/dimentorin/index.ts
@@ -165,17 +165,18 @@ export type TMentorRegisterRequest = {
export type TPayment = {
id: string;
- session_id: string;
- mentor_id: string;
+ sessionId: string;
+ mentorId: string;
amount: number;
- service_fee: number;
+ serviceFee: number;
total: number;
method: string;
provider: string;
status: string;
- external_ref?: string | null;
- expires_at: string;
- created_at: string;
+ externalRef?: string | null;
+ providerOrderId?: string | null;
+ expiresAt: string;
+ createdAt: string;
};
export type TCreatePaymentRequest = {