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:
parent
cfb16600d7
commit
f60f249c96
4 changed files with 25 additions and 13 deletions
|
|
@ -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 />}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue