CMS · App button manager (MOFs)
Configures the mobile app’s navigation buttons: their type, label (per language), image, position, date window, visibility, access requirements and audience targeting — covering both the fixed standard menu and custom buttons, including groups of buttons.
| Area | CMS |
| Routes | cms.mofs · cms.mofs.index |
| Centre modules | cms |
| Staff levels | root · admin · manager · weboperator |
| Models | campaign · category · language · mof · mof-language |
| UI label | title.header.cms.mofs / menu.navbar.cms.mof |
| Coverage | 7 use cases · 3 happy · 11 failure |
How it works
The most configuration-dense screen in the app (750-line controller) because each of ~21 button types changes which fields are relevant. Two top-level kinds: custom buttons and menu_static (the app’s standard menu entries — home, search, loyalty, events, news, stores, profile, info, plan your visit, services, commerces map), and for menu_static the model deliberately drops the start/end date requirements (attrsToValidate.pop() twice). Type-conditional required fields: URL types need a url; stores/proposals need a categoryId; lotteries/booking_appointment/loyalty_qr_commerce need a campaignId; group needs mofsCount. A long list of canShow…/canEdit… getters decides field visibility per type and role. Uniqueness rules are enforced in the controller, not the model: at most one cashback button (cms.mofs.only-one-cashback-type) and one fidcard button (cms.mofs.only-one-fidcard-type), and a button already inside a group cannot be grouped again (cms.mofs.mof-already-grouped). Buttons can require dni or email_validated before opening, and can target or exclude a user type (selectedUserTypeId / selectedUserTypeNegativeId). Positions are managed against persistedPositions/availablePositions. Relationship loads are defensive (relationship.load().catch(() => null)), so a broken reference degrades rather than crashes.
Use cases
Why someone opens this (7)
- Adding a ‘Digital games’ button to the app home that opens a specific game.
- Pointing a button at a store or proposal category so it opens a filtered list.
- Gating a loyalty button so only users with a VAT number or a validated email can open it.
- Grouping several buttons under one entry to keep the home screen clean.
- Scheduling a seasonal button to appear and disappear on set dates.
- Reordering the standard menu for a particular centre.
- Showing a button only to a given user type, or hiding it from one.
Path map
flowchart LR F(["App button manager MOFs"]):::feat F --> H1["Custom URL button published"]:::ok F --> H2["Button group created"]:::ok F --> H3["Standard menu adjusted"]:::ok F -.-> X1["Core fields missing"]:::bad F -.-> X2["Start date after end date"]:::bad F -.-> X3["URL type without a URL"]:::bad F -.-> X4["Category type without a category"]:::bad F -.-> X5["Activity type without an activity"]:::bad F -.-> X6["Group without buttons"]:::bad F -.-> X7["Duplicate singleton type"]:::bad F -.-> X8["Button already in a group"]:::bad F -.-> X9["Save fails"]:::bad F -.-> X10["Delete fails"]:::bad F -.-> X11["Broken relationship"]:::bad classDef feat fill:#6a4a8f,stroke:#6a4a8f,color:#ffffff,font-weight:bold classDef ok fill:#e3f1ea,stroke:#0d6b42,color:#0d4a2e classDef bad fill:#f7e6e2,stroke:#9a2d1c,color:#6e1f13
Happy paths
Custom URL button published
- User creates a custom button, picks a URL type and enters the URL.
- Uploads the button image and sets the label for each language.
- Sets start and end dates and a free position.
- Optionally adds an access requirement and a user-type target.
savepassesvalidates()and the button appears in the app.
Button group created
- User creates a
groupbutton and selects the buttons to nest (groupableMofs).mofsCountis non-zero so validation passes.- The group is saved and the app shows one entry that expands.
Standard menu adjusted
- User switches the view type to
menu_staticand picks a sub-type.- Because the standard menu is permanent, the date requirements are dropped.
- Position and label are set and the standard entry is updated.
Failure paths
Core fields missing
Cause — Image, type, name, start date or end date blank on a custom button.
What happens —
error.mof.image.required,error.mof.type.required,error.mof.name.required,error.mof.start-date.required,error.mof.end-date.required.
Start date after end date
Cause —
validateDates(startDate, endDate)fails.What happens —
error.mof.start-date.date(‘The date must not be greater than the end date’).
URL type without a URL
Cause —
url,custom_url,external_url,formsortallytype with no URL.What happens —
error.mof.url.required.
Category type without a category
Cause —
storesorproposalstype with nocategoryId.What happens —
error.mof.category.required.
Activity type without an activity
Cause —
lotteries,booking_appointmentorloyalty_qr_commercewithoutcampaignId.What happens —
error.mof.campaign.required; the lottery-specific variant iserror.mof.lottery.required.
Group without buttons
Cause —
grouptype withmofsCount0.What happens —
error.mof.mofs.required(‘Grouping of buttons required’).
Duplicate singleton type
Cause — A second cashback or fidcard button.
What happens —
cms.mofs.only-one-cashback-type/cms.mofs.only-one-fidcard-type.
Button already in a group
Cause — Trying to add a button that another group already contains.
What happens —
cms.mofs.mof-already-grouped, backed byhasMofInAnotherGroup.
Save fails
Cause — API error.
What happens —
cms.mofs.save-error(‘An error occurred while trying to save the button, please contact the administrator’).
Delete fails
Cause — API error.
What happens —
cms.mofs.delete-error.
Broken relationship
Cause — A referenced campaign or category no longer exists.
What happens —
relationship.load().catch(() => null)and.catch(() => [])degrade silently — the button renders with an empty reference rather than reporting the broken link.
QA checklist
Tick these off while regression-testing this feature. The boxes are plain Markdown, so progress is visible in git diffs.
- ✅ Custom URL button published
- ✅ Button group created
- ✅ Standard menu adjusted
- ⛔ Core fields missing
- ⛔ Start date after end date
- ⛔ URL type without a URL
- ⛔ Category type without a category
- ⛔ Activity type without an activity
- ⛔ Group without buttons
- ⛔ Duplicate singleton type
- ⛔ Button already in a group
- ⛔ Save fails
- ⛔ Delete fails
- ⛔ Broken relationship
Implementation
app/controllers/cms/mofs/index.jsapp/routes/cms/mofs/index.jsapp/models/mof.jsapp/models/mof-language.jsapp/enums/mof-types.jsapp/enums/button-mof-types.jsapp/enums/button-sub-types.jsapp/enums/mof-requires.jsapp/enums/mofs-status.jsapp/abilities/mof.js
Translation keys (46)
Key English title.header.cms.mofsMOF menu.navbar.cms.mofMOF mof-type.customCustom mof-type.standardStandard mof-type.urlURL mof-type.custom-urlCustom url mof-type.external-urlExternal URL mof-type.proposalsProposals mof-type.storesStores mof-type.deliveryDelivery mof-type.tao-wifiTAO Wifi mof-type.digital-gamesDigital games mof-type.booking-appointmentBooking appointment mof-type.gift-card-adminAdmin gift card mof-type.proposal-rankingRanking mof-type.tallyInterest forms mof-type.formsCustom forms static-button-type.homehome static-button-type.searchsearch static-button-type.loyaltyloyalty static-button-type.eventsevents static-button-type.newsnews static-button-type.storesstores static-button-type.profileprofile static-button-type.informationinformation static-button-type.plan_your_visitplan your visit static-button-type.servicesservices static-button-type.commerces-mapStores map mof-requires.dniDNI mof-requires.email-validatedEmail validated error.mof.image.requiredImage is required error.mof.type.requiredType is required error.mof.name.requiredName is required error.mof.start-date.requiredStart date is required error.mof.start-date.dateThe date must not be greater than the end date error.mof.end-date.requiredEnd date is required error.mof.url.requiredURL is required error.mof.category.requiredCategory is required error.mof.campaign.requiredThe activity is required error.mof.lottery.requiredLottery is required error.mof.mofs.requiredGrouping of buttons required cms.mofs.save-errorAn error occurred while trying to save the button, please contact the administrator cms.mofs.delete-errorAn error occurred while trying to delete the button, please contact the administrator cms.mofs.mof-already-groupedOne of the selected MOFs already belongs to another group cms.mofs.only-one-cashback-typeThere is already an active customer wallet card-type button in this center. cms.mofs.only-one-fidcard-typeThere is already an active {name} button in this center.
Related
- Same area — Proposal list & publication workflow · Proposal editor (content, coupon, push, AI images) · Push notification list & publishing · Push notification editor (scheduling, geofencing, deep links) · Content categories · Multicentre proposals
- Shares data with — Store create & edit · Activity editor (loyalty mechanics, rules & targeting) · Activity campaigns (programme grouping) · Centre editor (modules, app config, integrations) · Customer create & edit · Customer CSV import · Customer receipts tab & ticket-stamp generation · Customer loyalty — raffle tickets
Generated from doc/feature-inventory.json · id cms-mofs · commit 276788ca6