fix ui style

This commit is contained in:
2026-05-21 11:59:02 +07:00
parent 22e66ce493
commit f84d0e3726
51 changed files with 1035 additions and 347 deletions
@@ -2,6 +2,7 @@
import { useState } from "react";
import { useRouter } from "next/navigation";
import { CircleCheck } from "lucide-react";
import { adminCancelTripAction } from "@/features/trip/actions";
interface AdminCancelTripButtonProps {
@@ -42,7 +43,10 @@ export function AdminCancelTripButton({ tripId }: AdminCancelTripButtonProps) {
if (result) {
return (
<div className="rounded-xl border border-emerald-200 bg-emerald-50 p-4 text-sm text-emerald-900">
<p className="font-bold"> Trip berhasil dibatalkan.</p>
<p className="flex items-center gap-1.5 font-bold">
<CircleCheck size={18} strokeWidth={2} aria-hidden />
Trip berhasil dibatalkan.
</p>
<ul className="mt-2 space-y-0.5 text-xs">
<li> {result.refundCount} booking PAID refund auto-dibuat</li>
<li>
+24 -16
View File
@@ -2,6 +2,14 @@
import { useMemo, useState } from "react";
import { useRouter } from "next/navigation";
import {
ArrowLeft,
ArrowRight,
Check,
X,
CircleAlert,
Users,
} from "lucide-react";
import { DateRangeField, TimeField } from "@/components/shared/date-picker";
import { createTripAction } from "@/features/trip/actions";
import { ImageUrlInput } from "@/features/trip/components/image-url-input";
@@ -367,9 +375,10 @@ export function CreateTripForm({ isVerifiedOrganizer }: CreateTripFormProps) {
type="button"
onClick={goBack}
disabled={step === 1 || loading}
className="rounded-xl border border-neutral-200 bg-white px-4 py-2.5 text-sm font-semibold text-neutral-700 transition-colors hover:bg-neutral-50 disabled:cursor-not-allowed disabled:opacity-40"
className="inline-flex items-center gap-1 rounded-xl border border-neutral-200 bg-white px-4 py-2.5 text-sm font-semibold text-neutral-700 transition-colors hover:bg-neutral-50 disabled:cursor-not-allowed disabled:opacity-40"
>
Kembali
<ArrowLeft size={15} strokeWidth={2} aria-hidden />
Kembali
</button>
{isLastStep ? (
@@ -388,9 +397,10 @@ export function CreateTripForm({ isVerifiedOrganizer }: CreateTripFormProps) {
<button
type="button"
onClick={goNext}
className="rounded-xl bg-primary-600 px-5 py-2.5 text-sm font-bold text-white shadow-lg shadow-primary-600/20 transition-colors hover:bg-primary-700"
className="inline-flex items-center gap-1 rounded-xl bg-primary-600 px-5 py-2.5 text-sm font-bold text-white shadow-lg shadow-primary-600/20 transition-colors hover:bg-primary-700"
>
Lanjut
Lanjut
<ArrowRight size={15} strokeWidth={2} aria-hidden />
</button>
)}
</div>
@@ -442,7 +452,11 @@ function Stepper({
: "cursor-not-allowed"
}`}
>
{isCompleted ? "✓" : s.id}
{isCompleted ? (
<Check size={14} strokeWidth={3} aria-hidden />
) : (
s.id
)}
</button>
<span
className={`ml-2 hidden text-xs font-semibold sm:inline ${
@@ -942,7 +956,7 @@ function ItineraryBuilder({
aria-label="Hapus aktivitas"
className="self-end rounded-lg px-2 py-1.5 text-neutral-400 hover:bg-red-50 hover:text-red-500 sm:self-center"
>
<X size={16} strokeWidth={2} aria-hidden />
</button>
</div>
</li>
@@ -1032,14 +1046,7 @@ function StepSchedule({
</label>
<div className="relative">
<span className="absolute left-3 top-1/2 -translate-y-1/2 text-neutral-400">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
className="h-4 w-4"
>
<path d="M7 8a3 3 0 100-6 3 3 0 000 6zM14.5 9a2.5 2.5 0 100-5 2.5 2.5 0 000 5zM1.615 16.428a1.224 1.224 0 01-.569-1.175 6.002 6.002 0 0111.908 0c.058.467-.172.92-.57 1.174A9.953 9.953 0 017 18a9.953 9.953 0 01-5.385-1.572zM14.5 16h-.106c.07-.297.088-.611.048-.933a7.47 7.47 0 00-1.588-3.755 4.502 4.502 0 015.874 2.636.818.818 0 01-.36.98A7.465 7.465 0 0114.5 16z" />
</svg>
<Users size={16} strokeWidth={1.75} aria-hidden />
</span>
<input
id="maxParticipants"
@@ -1084,8 +1091,9 @@ function StepSchedule({
/>
</div>
{blockedByVerification && (
<p className="mt-2 text-xs font-medium text-amber-700">
Trip berbayar butuh verifikasi organizer terlebih dahulu.
<p className="mt-2 flex items-center gap-1.5 text-xs font-medium text-amber-700">
<CircleAlert size={14} strokeWidth={2} aria-hidden />
Trip berbayar butuh verifikasi organizer terlebih dahulu.
</p>
)}
</div>
+8 -2
View File
@@ -2,6 +2,7 @@
import Image from "next/image";
import { useState } from "react";
import { Mountain } from "lucide-react";
interface TripImage {
id: string;
@@ -14,8 +15,13 @@ export function ImageGallery({ images }: { images: TripImage[] }) {
if (images.length === 0) {
return (
<div className="flex h-44 items-center justify-center bg-linear-to-br from-primary-800 to-secondary-900 sm:h-56 lg:h-72">
<span className="text-5xl sm:text-6xl">🏔</span>
<div className="flex h-44 items-center justify-center bg-neutral-100 sm:h-56 lg:h-72">
<Mountain
size={56}
strokeWidth={1.5}
aria-hidden
className="text-neutral-300"
/>
</div>
);
}
+4 -2
View File
@@ -1,5 +1,6 @@
"use client";
import { Plus, X } from "lucide-react";
import { LIMITS } from "@/lib/limits";
interface ImageUrlInputProps {
@@ -59,7 +60,7 @@ export function ImageUrlInput({ value, onChange }: ImageUrlInputProps) {
aria-label={`Hapus foto ${i + 1}`}
className="flex h-10 w-10 shrink-0 items-center justify-center rounded-xl border border-neutral-200 text-neutral-400 hover:bg-red-50 hover:text-red-500"
>
<X size={16} strokeWidth={2} aria-hidden />
</button>
)}
</div>
@@ -71,7 +72,8 @@ export function ImageUrlInput({ value, onChange }: ImageUrlInputProps) {
onClick={addField}
className="mt-2 flex items-center gap-1 rounded-lg px-2 py-1 text-sm font-medium text-secondary-600 hover:bg-secondary-50"
>
+ Tambah foto
<Plus size={15} strokeWidth={2} aria-hidden />
Tambah foto
</button>
)}
<p className="mt-1.5 text-xs text-neutral-400">
@@ -143,7 +143,7 @@ export function JoinTripButton({
Kamu sudah{" "}
<span className="font-semibold">terkonfirmasi</span> sebagai peserta
trip ini
{isFree && <span> trip gratis, tidak ada pembayaran 🎉</span>}.
{isFree && <span> trip gratis, tidak ada pembayaran</span>}.
</div>
)}
{needsPayment && (
@@ -1,4 +1,5 @@
import Image from "next/image";
import { BadgeCheck, Star } from "lucide-react";
import type { OrganizerTrust } from "@/server/services/trust.service";
interface OrganizerTrustPanelProps {
@@ -13,7 +14,7 @@ export function OrganizerTrustPanel({
trust,
}: OrganizerTrustPanelProps) {
return (
<div className="rounded-xl border border-neutral-200 bg-linear-to-br from-white to-neutral-50 p-4 sm:p-5">
<div className="rounded-xl border border-neutral-200 bg-white p-4 sm:p-5">
<h2 className="mb-3 text-xs font-bold text-neutral-700 sm:text-sm">
Organizer & kepercayaan
</h2>
@@ -42,7 +43,8 @@ export function OrganizerTrustPanel({
className="inline-flex items-center gap-1 rounded-full bg-primary-100 px-2 py-0.5 text-[10px] font-bold uppercase tracking-wide text-primary-800 sm:text-xs"
title="Identitas organizer telah diverifikasi (KTP & rekening)"
>
Verified Organizer
<BadgeCheck size={12} strokeWidth={2} aria-hidden />
Verified Organizer
</span>
)}
{trust.isTripLeader && (
@@ -79,8 +81,20 @@ export function OrganizerTrustPanel({
<p className="text-[10px] font-medium text-neutral-500 sm:text-xs">
Rating organizer
</p>
<p className="text-lg font-bold text-amber-700">
{trust.avgRating != null ? `${trust.avgRating}` : "—"}
<p className="flex items-center gap-1 text-lg font-bold text-amber-700">
{trust.avgRating != null ? (
<>
{trust.avgRating}
<Star
size={15}
strokeWidth={2}
fill="currentColor"
aria-hidden
/>
</>
) : (
"—"
)}
</p>
{trust.reviewCount > 0 && (
<p className="text-[10px] text-neutral-400">
+33 -8
View File
@@ -1,5 +1,12 @@
import Image from "next/image";
import Link from "next/link";
import {
MapPin,
CalendarDays,
UserRound,
BadgeCheck,
Sparkles,
} from "lucide-react";
import { formatRupiah } from "@/lib/utils";
import { formatTripCalendarDateRangeLong } from "@/lib/trip-dates";
import { categoryMeta } from "@/lib/activity-category";
@@ -132,21 +139,38 @@ export function TripCard({
<div className="mt-3 space-y-1 text-sm text-neutral-600">
<div className="flex items-center gap-1.5">
<span className="text-xs text-secondary-500">📍</span> {location}
<MapPin
size={14}
strokeWidth={1.75}
aria-hidden
className="shrink-0 text-neutral-400"
/>
<span className="truncate">{location}</span>
</div>
<div className="flex items-center gap-1.5">
<span className="text-xs text-secondary-500">📅</span>{" "}
{formatTripCalendarDateRangeLong(date, endDate)}
<CalendarDays
size={14}
strokeWidth={1.75}
aria-hidden
className="shrink-0 text-neutral-400"
/>
<span>{formatTripCalendarDateRangeLong(date, endDate)}</span>
</div>
<div className="flex flex-wrap items-center gap-1.5">
<span className="text-xs text-secondary-500">👤</span>{" "}
<UserRound
size={14}
strokeWidth={1.75}
aria-hidden
className="shrink-0 text-neutral-400"
/>
<span className="truncate">{organizerName}</span>
{isVerifiedOrganizer && (
<span
className="inline-flex items-center gap-0.5 rounded-full bg-primary-100 px-1.5 py-0.5 text-[10px] font-bold uppercase tracking-wide text-primary-800"
className="inline-flex items-center gap-1 rounded-full bg-primary-100 px-1.5 py-0.5 text-[10px] font-bold uppercase tracking-wide text-primary-800"
title="Identitas organizer telah diverifikasi (KTP & rekening)"
>
Verified
<BadgeCheck size={11} strokeWidth={2} aria-hidden />
Verified
</span>
)}
{isSmallGroup && (
@@ -193,10 +217,11 @@ export function TripCard({
)}
{overlapCount > 0 && (
<span
className="rounded-full bg-secondary-50 px-2 py-0.5 text-[11px] font-semibold text-secondary-700"
className="inline-flex items-center gap-1 rounded-full bg-secondary-50 px-2 py-0.5 text-[11px] font-semibold text-secondary-700"
title="Peserta dengan minimal 1 minat sama dengan kamu"
>
{overlapCount} peserta sama minat
<Sparkles size={11} strokeWidth={2} aria-hidden />
{overlapCount} peserta sama minat
</span>
)}
</div>
+2 -12
View File
@@ -2,6 +2,7 @@
import { useRouter, useSearchParams } from "next/navigation";
import { useState } from "react";
import { Search } from "lucide-react";
import { DateRangeField } from "@/components/shared/date-picker";
import {
formatLocalCalendarYmd,
@@ -263,18 +264,7 @@ export function TripFilter() {
</label>
<div className="relative">
<span className="absolute left-3 top-1/2 -translate-y-1/2 text-neutral-400">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
className="h-4 w-4"
>
<path
fillRule="evenodd"
d="M9 3.5a5.5 5.5 0 100 11 5.5 5.5 0 000-11zM2 9a7 7 0 1112.452 4.391l3.328 3.329a.75.75 0 11-1.06 1.06l-3.329-3.328A7 7 0 012 9z"
clipRule="evenodd"
/>
</svg>
<Search size={16} strokeWidth={1.75} aria-hidden />
</span>
<input
type="text"