Extract admin dashboard from ghabilee-frontend2 into a dedicated Next.js app for backoffice.ghabilee.ir (no SEO indexing / Clarity).
6.7 KiB
Consumer UI guidelines
Hard constraints for the end-user app under frontend/app/(consumer)/ and
shared consumer surfaces (components/consumer/*, PWA/auth overlays used
there, event/review/booking flows).
Agents must follow the always-on Cursor rules
consumer-modals, consumer-input, consumer-button, and consumer-mobile-first. This doc is the longer
companion.
1. Overlays: ConsumerModal only
Any modal / dialog in the consumer app must use
components/consumer/ConsumerModal.
// ❌ BAD — admin/shared Modal in consumer UI
import Modal from '@/components/modals/Modal'
// ✅ GOOD
import ConsumerModal from '@/components/consumer/ConsumerModal'
Do not use components/modals/Modal, HeroUI Drawer, or ad-hoc fixed
banners for consumer dialogs. Admin stays on components/modals/Modal
(see admin-ui-guidelines.md).
ConsumerModal is always a bottom sheet (phone and desktop). There is no
centered / top / auto placement.
3. Form fields: ConsumerInput only
Any text field, select, textarea, OTP, radio, checkbox, switch, or number
input in the consumer app must use components/consumer/ConsumerInput.
// ❌ BAD — admin/shared Input in consumer UI
import Input from '@/components/formElements/Input'
// ✅ GOOD
import ConsumerInput from '@/components/consumer/ConsumerInput'
Do not use components/formElements/Input in consumer surfaces.
Props: use size (sm | md | lg), radius
(full | control, 10px), and tone (muted | bordered). muted applies
#E2E2E280 fill; bordered is white surface + border. Select is always
muted. Do not pass HeroUI
size, radius, or variant.
Standalone mode: when the parent is not wrapped in react-hook-form, pass
value and onValueChange — ConsumerInput creates an internal form bridge.
Custom wrappers: pass inputWrapper to merge classes (auth full-radius
fields, host contact section).
OTP styling: pass otpClassNames={{ segment, segmentWrapper }} when the
default consumer OTP look is not enough (e.g. auth gate).
Temporary exception: birthDatePicker, combobox, and other admin-only
field kinds not yet in ConsumerInput v1 may keep formElements/Input for that
single field until support is added.
Implementation lives under components/consumer/input/ (ConsumerFieldShell,
consumerInputStyles.ts, per-kind controls). Admin panel and event-create wizard
stay on formElements/Input.
4. Actions: ConsumerButton only
Any button, submit control, or link-styled action in the consumer app must use
components/consumer/ConsumerButton.
// ❌ BAD — admin/shared Button in consumer UI
import Button from '@/components/formElements/Button'
// ✅ GOOD
import ConsumerButton from '@/components/consumer/ConsumerButton'
Do not use components/formElements/Button in consumer surfaces.
Props: use fill (orange | navy | gray | none), size
(xs | sm | md | lg | xl), radius (full | control), textColor,
fontSize, align, and icon props (iconStart / iconEnd / iconOnly). Do not
pass HeroUI size, radius, variant, or color.
Size tokens (button heights):
size |
height |
|---|---|
xs |
40px |
sm |
44px |
md |
48px |
lg |
52px (default text CTA) |
xl |
60px |
Default: lg for text buttons, sm for iconOnly.
همنام بودن ≠ هماندازه بودن: ConsumerButton size and ConsumerInput
size reuse labels (sm, md, lg) but map to different pixel
heights. Do not assume a field and a button with the same token name share
one height.
| token | ConsumerButton size |
ConsumerInput size |
|---|---|---|
xs |
40px | — |
sm |
44px | 32px |
md |
48px | 48px |
lg |
52px | 52px |
xl |
60px | — |
Four button roles (+ icon chrome):
| Role | API | Typical use |
|---|---|---|
| Conversion CTA | fill="orange" (default) |
login, book, continue |
| Dark primary | fill="navy" |
modal confirm, host CTAs |
| Inline / card pill | fill="gray" |
cancel booking, nav chips, follow |
| Text / ghost action | fill="none" + textColor |
change mobile, resend code, links |
| Destructive | fill="none" + textColor="text-fourth-900" |
delete, cancel reservation |
| Icon-only | iconOnly + size="sm" |
bookmark, share, edit on cards |
Dual cancel/primary rows: ConsumerActionButtons (modal footers,
ConsumerFormActionBar). Do not duplicate that layout.
No ad-hoc visual className: if Figma needs a look this component cannot
express via fill, size, radius, textColor, fontSize, or align, stop
and ask the product owner — do not patch with bg-*, text-*, rounded-*, etc.
Extend ConsumerButton only after explicit approval. Layout-only classes
(flex-1, mt-5, shrink-0, …) are fine.
Admin panel stays on formElements/Button.
5. Mobile excellence; desktop does not matter
The consumer app is a mobile product.
- Target phone widths (~360–430px) first and last.
- No horizontal scroll, clipped content, overlapping BottomNav, or ignored safe-area insets.
- Touch targets ≥ 44px; primary CTAs reachable with one thumb when practical.
- Fix mobile layout bugs before any other visual work.
Non-goal: desktop / wide-viewport polish. Do not invest in multi-column desktop layouts or large-screen aesthetics unless explicitly requested. A correct phone layout always wins over a polished desktop layout.
Design tokens and patterns: docs/frontend/consumer-design-system.md.
Copy catalog: consumer-texts.md.
6. Event cards
Use the single medium card components/consumer/EventCard everywhere.
- Fixed top: poster + date / time / category / location + title.
- Variable bottom: pass page-specific UI via
children(inlinedivs). - Optional helper:
EventCapacityBadgefor remaining-seat chips in discovery/host footers. - Loading state:
EventCardSkeleton/EventCardSkeletonList. - Dense lists:
CompactEventCardorConsumerEventThumbRowwhen the medium card is too tall.