fix ui style
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import Link from "next/link";
|
||||
import { notFound, redirect } from "next/navigation";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { ArrowLeft, CalendarDays, CircleAlert, MapPin } from "lucide-react";
|
||||
import { authOptions } from "@/lib/auth";
|
||||
import { isAdminEmail } from "@/lib/admin";
|
||||
import { bookingRepo } from "@/server/repositories/booking.repo";
|
||||
@@ -69,8 +70,12 @@ export default async function AdminBookingDetailPage({ params }: PageProps) {
|
||||
return (
|
||||
<div className="mx-auto max-w-4xl px-4 py-8 sm:py-12">
|
||||
<div className="mb-4 flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-neutral-500">
|
||||
<Link href="/admin" className="hover:text-primary-600">
|
||||
← Dashboard
|
||||
<Link
|
||||
href="/admin"
|
||||
className="inline-flex items-center gap-1 hover:text-primary-600"
|
||||
>
|
||||
<ArrowLeft size={14} strokeWidth={2} aria-hidden />
|
||||
Dashboard
|
||||
</Link>
|
||||
<Link
|
||||
href={`/admin/trips/${booking.tripId}`}
|
||||
@@ -87,9 +92,22 @@ export default async function AdminBookingDetailPage({ params }: PageProps) {
|
||||
<h1 className="mt-0.5 text-xl font-bold text-neutral-900 sm:text-2xl">
|
||||
{booking.trip.title}
|
||||
</h1>
|
||||
<p className="mt-1 text-sm text-neutral-500">
|
||||
📅 {formatTripCalendarDateRangeLong(booking.trip.date, booking.trip.endDate)}{" "}
|
||||
· 📍 {booking.trip.destination}, {booking.trip.location}
|
||||
<p className="mt-1 flex flex-wrap items-center gap-1 text-sm text-neutral-500">
|
||||
<CalendarDays
|
||||
size={14}
|
||||
strokeWidth={1.75}
|
||||
aria-hidden
|
||||
className="shrink-0"
|
||||
/>
|
||||
{formatTripCalendarDateRangeLong(booking.trip.date, booking.trip.endDate)}
|
||||
<span aria-hidden>·</span>
|
||||
<MapPin
|
||||
size={14}
|
||||
strokeWidth={1.75}
|
||||
aria-hidden
|
||||
className="shrink-0"
|
||||
/>
|
||||
{booking.trip.destination}, {booking.trip.location}
|
||||
</p>
|
||||
|
||||
<div className="mt-4 grid gap-3 sm:grid-cols-2">
|
||||
@@ -300,8 +318,14 @@ function PaymentEventCard({
|
||||
</p>
|
||||
)}
|
||||
{payment.rejectionReason && (
|
||||
<p className="text-red-700">
|
||||
⚠️ {payment.rejectionReason}
|
||||
<p className="flex items-center gap-1 text-red-700">
|
||||
<CircleAlert
|
||||
size={14}
|
||||
strokeWidth={2}
|
||||
aria-hidden
|
||||
className="shrink-0"
|
||||
/>
|
||||
{payment.rejectionReason}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { redirect } from "next/navigation";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { Lock } from "lucide-react";
|
||||
import { authOptions } from "@/lib/auth";
|
||||
import { AdminSidebar } from "@/components/admin/admin-sidebar";
|
||||
|
||||
@@ -31,7 +32,12 @@ export default async function AdminLayout({
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-neutral-50 px-4">
|
||||
<div className="max-w-md rounded-2xl border border-neutral-200 bg-white p-8 text-center shadow-sm">
|
||||
<p className="text-2xl">🔒</p>
|
||||
<Lock
|
||||
size={28}
|
||||
strokeWidth={1.75}
|
||||
aria-hidden
|
||||
className="mx-auto text-neutral-500"
|
||||
/>
|
||||
<h1 className="mt-2 text-base font-bold text-neutral-900">
|
||||
Halaman khusus admin
|
||||
</h1>
|
||||
|
||||
+4
-2
@@ -1,6 +1,7 @@
|
||||
import { redirect } from "next/navigation";
|
||||
import Link from "next/link";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { ChevronRight } from "lucide-react";
|
||||
import { authOptions } from "@/lib/auth";
|
||||
import { organizerRepo } from "@/server/repositories/organizer.repo";
|
||||
import { refundRepo } from "@/server/repositories/refund.repo";
|
||||
@@ -155,8 +156,9 @@ export default async function AdminDashboardPage() {
|
||||
>
|
||||
{s.label}
|
||||
</span>
|
||||
<span className="text-xs text-neutral-400 group-hover:text-primary-600">
|
||||
Buka →
|
||||
<span className="inline-flex items-center gap-1 text-xs text-neutral-400 group-hover:text-primary-600">
|
||||
Buka
|
||||
<ChevronRight size={14} strokeWidth={2} aria-hidden />
|
||||
</span>
|
||||
</div>
|
||||
<p className="mt-3 text-3xl font-bold text-neutral-900">{s.value}</p>
|
||||
|
||||
+33
-14
@@ -1,6 +1,12 @@
|
||||
import Link from "next/link";
|
||||
import { redirect } from "next/navigation";
|
||||
import { getServerSession } from "next-auth";
|
||||
import {
|
||||
ArrowUpRight,
|
||||
CircleAlert,
|
||||
CircleCheck,
|
||||
CircleX,
|
||||
} from "lucide-react";
|
||||
import { authOptions } from "@/lib/auth";
|
||||
import { isAdminEmail } from "@/lib/admin";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
@@ -112,8 +118,9 @@ export default async function AdminSystemPage() {
|
||||
|
||||
{hasAnyStale && (
|
||||
<section className="mb-6 rounded-2xl border border-amber-300 bg-amber-50 p-4 sm:p-5">
|
||||
<h2 className="mb-2 text-sm font-bold text-amber-900">
|
||||
⚠️ Stale State Alerts
|
||||
<h2 className="mb-2 flex items-center gap-1.5 text-sm font-bold text-amber-900">
|
||||
<CircleAlert size={16} strokeWidth={2} aria-hidden />
|
||||
Stale State Alerts
|
||||
</h2>
|
||||
<ul className="space-y-1 text-xs text-amber-900">
|
||||
{stale.stalePaymentsCount > 0 && (
|
||||
@@ -137,9 +144,10 @@ export default async function AdminSystemPage() {
|
||||
cron history di bawah.{" "}
|
||||
<Link
|
||||
href="/admin/payouts?tab=HELD"
|
||||
className="font-semibold text-amber-700 hover:underline"
|
||||
className="inline-flex items-center gap-1 font-semibold text-amber-700 hover:underline"
|
||||
>
|
||||
Lihat HELD →
|
||||
Lihat HELD
|
||||
<ArrowUpRight size={14} strokeWidth={2} aria-hidden />
|
||||
</Link>
|
||||
</li>
|
||||
)}
|
||||
@@ -149,9 +157,10 @@ export default async function AdminSystemPage() {
|
||||
> 7 hari belum di-process.{" "}
|
||||
<Link
|
||||
href="/admin/refunds?tab=APPROVED"
|
||||
className="font-semibold text-amber-700 hover:underline"
|
||||
className="inline-flex items-center gap-1 font-semibold text-amber-700 hover:underline"
|
||||
>
|
||||
Lihat APPROVED →
|
||||
Lihat APPROVED
|
||||
<ArrowUpRight size={14} strokeWidth={2} aria-hidden />
|
||||
</Link>
|
||||
</li>
|
||||
)}
|
||||
@@ -162,9 +171,10 @@ export default async function AdminSystemPage() {
|
||||
manual.{" "}
|
||||
<Link
|
||||
href="/admin/emails?tab=failed"
|
||||
className="font-semibold text-amber-700 hover:underline"
|
||||
className="inline-flex items-center gap-1 font-semibold text-amber-700 hover:underline"
|
||||
>
|
||||
Lihat email gagal →
|
||||
Lihat email gagal
|
||||
<ArrowUpRight size={14} strokeWidth={2} aria-hidden />
|
||||
</Link>
|
||||
</li>
|
||||
)}
|
||||
@@ -187,16 +197,23 @@ export default async function AdminSystemPage() {
|
||||
: "border-red-200 bg-red-50/50";
|
||||
const badge =
|
||||
health === "ok"
|
||||
? { label: "🟢 OK", cls: "bg-emerald-100 text-emerald-800" }
|
||||
? {
|
||||
label: "OK",
|
||||
icon: CircleCheck,
|
||||
cls: "bg-emerald-100 text-emerald-800",
|
||||
}
|
||||
: health === "stale"
|
||||
? {
|
||||
label: "🟡 STALE",
|
||||
label: "STALE",
|
||||
icon: CircleAlert,
|
||||
cls: "bg-amber-100 text-amber-800",
|
||||
}
|
||||
: {
|
||||
label: "🔴 FAILED",
|
||||
label: "FAILED",
|
||||
icon: CircleX,
|
||||
cls: "bg-red-100 text-red-800",
|
||||
};
|
||||
const BadgeIcon = badge.icon;
|
||||
return (
|
||||
<div
|
||||
key={s.jobName}
|
||||
@@ -212,8 +229,9 @@ export default async function AdminSystemPage() {
|
||||
</p>
|
||||
</div>
|
||||
<span
|
||||
className={`rounded-full px-2 py-0.5 text-[10px] font-bold uppercase tracking-wide ${badge.cls}`}
|
||||
className={`inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[10px] font-bold uppercase tracking-wide ${badge.cls}`}
|
||||
>
|
||||
<BadgeIcon size={12} strokeWidth={2.25} aria-hidden />
|
||||
{badge.label}
|
||||
</span>
|
||||
</div>
|
||||
@@ -276,9 +294,10 @@ export default async function AdminSystemPage() {
|
||||
<p className="mt-2 text-xs text-neutral-500">
|
||||
<Link
|
||||
href="/admin/emails"
|
||||
className="font-semibold text-primary-600 hover:underline"
|
||||
className="inline-flex items-center gap-1 font-semibold text-primary-600 hover:underline"
|
||||
>
|
||||
Buka Email Log →
|
||||
Buka Email Log
|
||||
<ArrowUpRight size={14} strokeWidth={2} aria-hidden />
|
||||
</Link>
|
||||
</p>
|
||||
</section>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import Link from "next/link";
|
||||
import { notFound, redirect } from "next/navigation";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { ArrowLeft, CalendarDays, MapPin } from "lucide-react";
|
||||
import { authOptions } from "@/lib/auth";
|
||||
import { isAdminEmail } from "@/lib/admin";
|
||||
import { tripService } from "@/server/services/trip.service";
|
||||
@@ -67,8 +68,12 @@ export default async function AdminTripDetailPage({ params }: PageProps) {
|
||||
return (
|
||||
<div className="mx-auto max-w-5xl px-4 py-8 sm:py-12">
|
||||
<div className="mb-4 text-xs text-neutral-500">
|
||||
<Link href="/admin/trips" className="hover:text-primary-600">
|
||||
← Kembali ke list trips
|
||||
<Link
|
||||
href="/admin/trips"
|
||||
className="inline-flex items-center gap-1 hover:text-primary-600"
|
||||
>
|
||||
<ArrowLeft size={14} strokeWidth={2} aria-hidden />
|
||||
Kembali ke list trips
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
@@ -84,9 +89,22 @@ export default async function AdminTripDetailPage({ params }: PageProps) {
|
||||
<h1 className="text-xl font-bold text-neutral-900 sm:text-2xl">
|
||||
{trip.title}
|
||||
</h1>
|
||||
<p className="mt-1 text-sm text-neutral-500">
|
||||
📅 {formatTripCalendarDateRangeLong(trip.date, trip.endDate)} ·
|
||||
📍 {trip.destination}, {trip.location}
|
||||
<p className="mt-1 flex flex-wrap items-center gap-1 text-sm text-neutral-500">
|
||||
<CalendarDays
|
||||
size={14}
|
||||
strokeWidth={1.75}
|
||||
aria-hidden
|
||||
className="shrink-0"
|
||||
/>
|
||||
{formatTripCalendarDateRangeLong(trip.date, trip.endDate)}
|
||||
<span aria-hidden>·</span>
|
||||
<MapPin
|
||||
size={14}
|
||||
strokeWidth={1.75}
|
||||
aria-hidden
|
||||
className="shrink-0"
|
||||
/>
|
||||
{trip.destination}, {trip.location}
|
||||
</p>
|
||||
<p className="mt-1 text-xs text-neutral-500">
|
||||
Organizer:{" "}
|
||||
@@ -220,8 +238,9 @@ export default async function AdminTripDetailPage({ params }: PageProps) {
|
||||
{p.user.name}
|
||||
</Link>
|
||||
{p.user.profile?.city && (
|
||||
<span className="ml-2 text-[11px] text-neutral-500">
|
||||
📍 {p.user.profile.city}
|
||||
<span className="ml-2 inline-flex items-center gap-1 text-[11px] text-neutral-500">
|
||||
<MapPin size={12} strokeWidth={2} aria-hidden />
|
||||
{p.user.profile.city}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import Link from "next/link";
|
||||
import { redirect } from "next/navigation";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { CalendarDays, MapPin } from "lucide-react";
|
||||
import { authOptions } from "@/lib/auth";
|
||||
import { isAdminEmail } from "@/lib/admin";
|
||||
import { tripRepo } from "@/server/repositories/trip.repo";
|
||||
@@ -127,9 +128,22 @@ export default async function AdminTripsPage({ searchParams }: PageProps) {
|
||||
<h2 className="truncate text-base font-bold text-neutral-900 sm:text-lg">
|
||||
{t.title}
|
||||
</h2>
|
||||
<p className="mt-1 truncate text-xs text-neutral-500 sm:text-sm">
|
||||
📅 {formatTripCalendarDateRangeLong(t.date, t.endDate)}
|
||||
{" · "}📍 {t.location}
|
||||
<p className="mt-1 flex items-center gap-1 truncate text-xs text-neutral-500 sm:text-sm">
|
||||
<CalendarDays
|
||||
size={14}
|
||||
strokeWidth={1.75}
|
||||
aria-hidden
|
||||
className="shrink-0"
|
||||
/>
|
||||
{formatTripCalendarDateRangeLong(t.date, t.endDate)}
|
||||
<span aria-hidden>·</span>
|
||||
<MapPin
|
||||
size={14}
|
||||
strokeWidth={1.75}
|
||||
aria-hidden
|
||||
className="shrink-0"
|
||||
/>
|
||||
{t.location}
|
||||
</p>
|
||||
<p className="mt-1 text-xs text-neutral-500 sm:text-sm">
|
||||
Organizer:{" "}
|
||||
|
||||
@@ -2,6 +2,7 @@ import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import { notFound, redirect } from "next/navigation";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { ArrowLeft, ArrowUpRight, Ban, Check } from "lucide-react";
|
||||
import { authOptions } from "@/lib/auth";
|
||||
import { isAdminEmail } from "@/lib/admin";
|
||||
import { userRepo } from "@/server/repositories/user.repo";
|
||||
@@ -38,8 +39,12 @@ export default async function AdminUserDetailPage({ params }: PageProps) {
|
||||
return (
|
||||
<div className="mx-auto max-w-4xl px-4 py-8 sm:py-12">
|
||||
<div className="mb-4 text-xs text-neutral-500">
|
||||
<Link href="/admin/users" className="hover:text-primary-600">
|
||||
← Kembali ke list users
|
||||
<Link
|
||||
href="/admin/users"
|
||||
className="inline-flex items-center gap-1 hover:text-primary-600"
|
||||
>
|
||||
<ArrowLeft size={14} strokeWidth={2} aria-hidden />
|
||||
Kembali ke list users
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
@@ -75,8 +80,9 @@ export default async function AdminUserDetailPage({ params }: PageProps) {
|
||||
</span>
|
||||
)}
|
||||
{user.organizerVerification?.status === "APPROVED" && (
|
||||
<span className="rounded-full bg-emerald-100 px-2 py-0.5 text-[11px] font-bold uppercase tracking-wide text-emerald-800">
|
||||
✓ Verified Organizer
|
||||
<span className="inline-flex items-center gap-1 rounded-full bg-emerald-100 px-2 py-0.5 text-[11px] font-bold uppercase tracking-wide text-emerald-800">
|
||||
<Check size={12} strokeWidth={2.5} aria-hidden />
|
||||
Verified Organizer
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
@@ -122,8 +128,9 @@ export default async function AdminUserDetailPage({ params }: PageProps) {
|
||||
|
||||
{user.suspended && (
|
||||
<section className="mb-6 rounded-2xl border border-red-300 bg-red-50 p-4 sm:p-5">
|
||||
<h2 className="text-sm font-bold text-red-900">
|
||||
⛔ Akun ditangguhkan
|
||||
<h2 className="flex items-center gap-1.5 text-sm font-bold text-red-900">
|
||||
<Ban size={16} strokeWidth={2} aria-hidden />
|
||||
Akun ditangguhkan
|
||||
</h2>
|
||||
<p className="mt-1 text-xs text-red-900/80">
|
||||
{user.suspendedReason ?? "Tidak ada alasan tercatat."}
|
||||
@@ -244,9 +251,10 @@ export default async function AdminUserDetailPage({ params }: PageProps) {
|
||||
{" · "}
|
||||
<Link
|
||||
href={`/admin/verifications?tab=${user.organizerVerification.status}`}
|
||||
className="text-secondary-700 hover:text-secondary-900"
|
||||
className="inline-flex items-center gap-1 text-secondary-700 hover:text-secondary-900"
|
||||
>
|
||||
Buka di /admin/verifications →
|
||||
Buka di /admin/verifications
|
||||
<ArrowUpRight size={14} strokeWidth={2} aria-hidden />
|
||||
</Link>
|
||||
</p>
|
||||
{user.organizerVerification.rejectionReason && (
|
||||
|
||||
@@ -2,6 +2,7 @@ import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import { redirect } from "next/navigation";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { Check, ChartColumn } from "lucide-react";
|
||||
import { authOptions } from "@/lib/auth";
|
||||
import { isAdminEmail } from "@/lib/admin";
|
||||
import { userRepo } from "@/server/repositories/user.repo";
|
||||
@@ -56,9 +57,10 @@ export default async function AdminUsersPage({ searchParams }: PageProps) {
|
||||
</div>
|
||||
<Link
|
||||
href="/admin/users/stats"
|
||||
className="rounded-xl border border-neutral-200 bg-white px-3 py-1.5 text-xs font-semibold text-neutral-700 hover:bg-neutral-50"
|
||||
className="inline-flex items-center gap-1.5 rounded-xl border border-neutral-200 bg-white px-3 py-1.5 text-xs font-semibold text-neutral-700 hover:bg-neutral-50"
|
||||
>
|
||||
📊 Stats
|
||||
<ChartColumn size={16} strokeWidth={2} aria-hidden />
|
||||
Stats
|
||||
</Link>
|
||||
</header>
|
||||
|
||||
@@ -147,8 +149,9 @@ export default async function AdminUsersPage({ searchParams }: PageProps) {
|
||||
</span>
|
||||
)}
|
||||
{u.organizerVerification?.status === "APPROVED" && (
|
||||
<span className="rounded-full bg-emerald-100 px-2 py-0.5 text-[10px] font-bold uppercase tracking-wide text-emerald-800">
|
||||
✓ Organizer
|
||||
<span className="inline-flex items-center gap-1 rounded-full bg-emerald-100 px-2 py-0.5 text-[10px] font-bold uppercase tracking-wide text-emerald-800">
|
||||
<Check size={12} strokeWidth={2.5} aria-hidden />
|
||||
Organizer
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import Link from "next/link";
|
||||
import { redirect } from "next/navigation";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { ArrowLeft } from "lucide-react";
|
||||
import { authOptions } from "@/lib/auth";
|
||||
import { isAdminEmail } from "@/lib/admin";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
@@ -97,8 +98,12 @@ export default async function AdminUserStatsPage() {
|
||||
return (
|
||||
<div className="mx-auto max-w-5xl px-4 py-8 sm:py-12">
|
||||
<div className="mb-4 text-xs text-neutral-500">
|
||||
<Link href="/admin/users" className="hover:text-primary-600">
|
||||
← Kembali ke list users
|
||||
<Link
|
||||
href="/admin/users"
|
||||
className="inline-flex items-center gap-1 hover:text-primary-600"
|
||||
>
|
||||
<ArrowLeft size={14} strokeWidth={2} aria-hidden />
|
||||
Kembali ke list users
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user