feat(mobile): extend mobile experience to Accounts role for payment actions

- Add ACCOUNTS to MOBILE_ROLES so the desktop-required wall no longer
  blocks them on small screens.
- MobileBottomNav is now role-aware: ACCOUNTS gets Payments + Profile tabs;
  MANAGER/SUPERUSER keep Approvals + Profile.  Role prop threaded from
  layout → MobileBottomNav.
- PaymentActions (both MGR_APPROVED and SENT_FOR_PAYMENT states) stacks
  vertically on small screens — input takes full width, button below it —
  then reverts to the horizontal inline layout at sm+ breakpoint.
- Payments page card bottom row (status badge + action) stacks on mobile
  (flex-col sm:flex-row) so the reference input isn't squashed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Hardik 2026-05-16 21:31:57 +05:30
parent cfb16600d7
commit f60f249c96
4 changed files with 25 additions and 13 deletions

View file

@ -7,8 +7,8 @@ import { MobileHeader } from "@/components/layout/mobile-header";
import { MobileBottomNav } from "@/components/layout/mobile-bottom-nav";
import { DesktopRequired } from "@/components/layout/desktop-required";
// Roles that have a useful mobile experience (approval queue + PO review)
const MOBILE_ROLES = ["MANAGER", "SUPERUSER"] as const;
// Roles that have a useful mobile experience
const MOBILE_ROLES = ["MANAGER", "SUPERUSER", "ACCOUNTS"] as const;
export default async function PortalLayout({
children,
@ -67,8 +67,8 @@ export default async function PortalLayout({
{children}
</main>
{/* Mobile bottom nav — managers/superusers only */}
{hasMobile && <MobileBottomNav />}
{/* Mobile bottom nav — roles with a mobile experience */}
{hasMobile && <MobileBottomNav role={session.user.role} />}
{/* Full-screen overlay for roles without a mobile experience */}
{!hasMobile && <DesktopRequired />}

View file

@ -74,7 +74,7 @@ export default async function PaymentsPage() {
</Link>
</div>
</div>
<div className="mt-4 border-t border-neutral-100 pt-4 flex items-center justify-between gap-4">
<div className="mt-4 border-t border-neutral-100 pt-4 flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3">
<span className={`text-xs font-medium rounded-full px-2.5 py-0.5 ${
po.status === "SENT_FOR_PAYMENT"
? "bg-primary-50 text-primary-700"

View file

@ -31,12 +31,12 @@ export function PaymentActions({ poId, poStatus }: { poId: string; poStatus: POS
if (poStatus === "MGR_APPROVED") {
return (
<div className="flex items-center gap-3">
<div className="flex flex-col sm:flex-row items-stretch sm:items-center gap-2">
{error && <span className="text-xs text-danger-700">{error}</span>}
<button
onClick={handleProcessPayment}
disabled={pending}
className="rounded-lg bg-primary-600 px-4 py-2 text-sm font-semibold text-white hover:bg-primary-700 disabled:opacity-60 transition-colors whitespace-nowrap"
className="w-full sm:w-auto rounded-lg bg-primary-600 px-4 py-2 text-sm font-semibold text-white hover:bg-primary-700 disabled:opacity-60 transition-colors"
>
{pending ? "Processing…" : "Start Payment Processing"}
</button>
@ -46,7 +46,7 @@ export function PaymentActions({ poId, poStatus }: { poId: string; poStatus: POS
if (poStatus === "SENT_FOR_PAYMENT") {
return (
<form onSubmit={handleMarkPaid} className="flex items-center gap-3">
<form onSubmit={handleMarkPaid} className="flex flex-col sm:flex-row items-stretch sm:items-center gap-2 w-full">
<input
type="text"
placeholder="Payment reference / transaction ID"
@ -58,7 +58,7 @@ export function PaymentActions({ poId, poStatus }: { poId: string; poStatus: POS
<button
type="submit"
disabled={pending}
className="rounded-lg bg-success px-4 py-2 text-sm font-semibold text-white hover:opacity-90 disabled:opacity-60 transition-opacity whitespace-nowrap"
className="w-full sm:w-auto rounded-lg bg-success px-4 py-2 text-sm font-semibold text-white hover:opacity-90 disabled:opacity-60 transition-opacity"
>
{pending ? "Confirming…" : "Confirm Payment Sent"}
</button>

View file

@ -2,20 +2,32 @@
import Link from "next/link";
import { usePathname } from "next/navigation";
import { CheckSquare, UserCircle } from "lucide-react";
import { CheckSquare, CreditCard, UserCircle } from "lucide-react";
import { cn } from "@/lib/utils";
import type { Role } from "@prisma/client";
const TABS = [
const MANAGER_TABS = [
{ href: "/approvals", label: "Approvals", icon: CheckSquare },
{ href: "/profile", label: "Profile", icon: UserCircle },
];
export function MobileBottomNav() {
const ACCOUNTS_TABS = [
{ href: "/payments", label: "Payments", icon: CreditCard },
{ href: "/profile", label: "Profile", icon: UserCircle },
];
function tabsForRole(role: Role) {
if (role === "ACCOUNTS") return ACCOUNTS_TABS;
return MANAGER_TABS; // MANAGER, SUPERUSER
}
export function MobileBottomNav({ role }: { role: Role }) {
const pathname = usePathname();
const tabs = tabsForRole(role);
return (
<nav className="md:hidden flex h-16 shrink-0 items-stretch border-t border-neutral-200 bg-white">
{TABS.map(({ href, label, icon: Icon }) => {
{tabs.map(({ href, label, icon: Icon }) => {
const active = pathname === href || pathname.startsWith(href + "/");
return (
<Link