CMS · Proposal editor (content, coupon, push, AI images)
The main content-authoring form (800+ lines of controller): multi-language title/description, app/web/totem images with optional AI generation, audio attachment, audience (gender, age range, segments), channels, store or centre scope, date and event windows, category, coupon, an optional push notification sent with the proposal, polls, extra attributes and a shareable public URL.
| Area | CMS |
| Routes | cms.proposals.new · cms.proposals.edit |
| Centre modules | ai · cms · marketingActivityCampaigns · proposal_ranking · totem_channel |
| Staff levels | root · admin · manager · storeManager · commerce · crmmanager · weboperator · content_creator |
| Models | campaign · category · commerce · coupon · extra · language · proposal · proposal-extra · proposal-language · push-notification · segment |
| UI label | cms.proposal.general-data |
| Coverage | 7 use cases · 3 happy · 16 failure |
Known issues (4)
Missing translations
These keys are used on this screen but do not exist in
translations.json, so users see the raw key:
cms.proposals.new.image-generation.gif
How it works
Several notable sub-capabilities. (1) AI image generation: openImageGenerationModal → generateImage with options from enums/image-ai-options, results polled over Pusher, then downloadImage. (2) Audio: uploadAudioFile/deleteAudioFile/mountAudio with a 10 MB limit enforced by the error copy. (3) An embedded push notification validated by _isValidPushNotification() only when wantToSendPushNotification — and cross-validated so the push cannot start before the event starts. (4) Coupons: a coupon sub-record with its own validations collected into couponErrors. (5) Extra attributes: requiredProposalExtras are reset and re-checked on each submit, with the same untranslated "<label> is required" message as the store form. (6) copyUrlTemplate copies a public proposal URL built from the centre’s template. Submission is a chain — submit → validate proposal + coupon + push → patchProposal → saveExtras → afterSaved — and errors go through the same mapErrors + user-error/api-error classification as the campaign editor.
Use cases
Why someone opens this (7)
- Publishing a centre event with images, an event window and a push notification announcing it.
- A store manager creating a store-scoped promotion with a redeemable coupon.
- Generating a hero image with AI rather than briefing a designer.
- Attaching an audio description for accessibility.
- Running a poll attached to a proposal, with a max-votes cap.
- Targeting a proposal to a segment and an age/gender range.
- Sharing a proposal outside the app via its public URL.
Path map
flowchart LR F(["Proposal editor content, coupon, push, AI images"]):::feat F --> H1["Centre event with push notification"]:::ok F --> H2["AI image generated"]:::ok F --> H3["Store coupon promotion"]:::ok F -.-> X1["Mandatory content missing"]:::bad F -.-> X2["Images missing"]:::bad F -.-> X3["Store missing on a store proposal"]:::bad F -.-> X4["End date before start date"]:::bad F -.-> X5["Event dates invalid"]:::bad F -.-> X6["Push notification starts before the event"]:::bad F -.-> X7["Segment required but missing"]:::bad F -.-> X8["Poll without an activity"]:::bad F -.-> X9["Images embedded in the description"]:::bad F -.-> X10["Coupon incomplete"]:::bad F -.-> X11["Audio upload fails or is too large"]:::bad F -.-> X12["Required extra attribute empty"]:::bad F -.-> X13["API rejection"]:::bad F -.-> X14["AI image generation fails"]:::bad F -.-> X15["URL copy fails"]:::bad F -.-> X16["Raw translation key on screen"]:::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
Centre event with push notification
- User fills name, description, category, type
center, channels and the app image.- Marks it an event and sets event start/end dates within the proposal’s date range.
- Audience: gender, min/max age and optionally segments.
- Enables the push notification, whose start is not before the event start.
submitvalidates the proposal, the coupon (if enabled) and the push, saves the proposal, then the extras.- The proposal is created in draft and can be requested/published from the list.
AI image generated
- User opens the image-generation modal and picks an AI option.
generateImagerequests it; the result arrives over the Pusher channel.- User reviews it and downloads/attaches it as the proposal image.
Store coupon promotion
- Type is set to
commerceand the store is selected.- Coupon is enabled with text, a usage limit and (for centre-scoped proposals) its stores.
- Multi-exchange is enabled with a per-user exchange limit.
- Save succeeds and customers can take the coupon in the app.
Failure paths
Mandatory content missing
Cause — Name, description, category, type, start date, end date, min/max age, gender or channels blank.
What happens —
error.proposal.name.required,error.proposal.description.required,error.proposal.category.required,error.proposal.proposalType.required,error.proposal.startDate.required,error.proposal.end-date.required,error.proposal.minimumAge.required,error.proposal.maximumAge.required,error.proposal.gender.required,error.proposal.channel.required.
Images missing
Cause —
appImage(always) ortotemImage(totem channel) not set.What happens —
required+proposal.app-image/proposal.totem-image.
Store missing on a store proposal
Cause —
proposalType === 'commerce'without a commerce.What happens —
required+ commerce.
End date before start date
Cause — Date ordering violated.
What happens —
error.proposal.end-date.after.
Event dates invalid
Cause — Event marked but start/end missing, or event end before event start.
What happens —
error.proposal.eventStartDate.required,error.proposal.event-end-date.required,error.proposal.event-end-date.after.
Push notification starts before the event
Cause — Embedded push start earlier than the event start.
What happens —
error.proposal.event-start-date.after-push-notification-startdate.
Segment required but missing
Cause —
isSegmentRequiredtrue with no segments.What happens —
error.proposal.segment.required.
Poll without an activity
Cause —
pollenabled without a campaign.What happens —
error.proposal.campaign.required(‘Required Activity’).
Images embedded in the description
Cause — Rich-text description contains an
<img>.What happens —
error.proposal.description.image-not-suported.
Coupon incomplete
Cause — Coupon enabled without text, usage limit, stores (centre-scoped) or per-user exchange limit (multi-exchange).
What happens —
error.coupon.text.required,error.coupon.usageLimit.required,error.coupon.commerces.required,error.coupon.exchange-limit-by-user.required, collected intocouponErrors.
Audio upload fails or is too large
Cause — Upload error or file over 10 MB.
What happens —
cms.proposal.audio-upload-errornames the 10 MB limit explicitly.
Required extra attribute empty
Cause — A centre-defined required proposal extra has no value.
What happens —
extraErrorset to an untranslated"<label> is required"string.
API rejection
Cause — Server-side validation or failure.
What happens —
errorDetailsfrommapErrors, thenuser-error(4xx) orapi-error(5xx). Sameerrors[0].status.charAt()fragility as the campaign editor.
AI image generation fails
Cause — Generation service error.
What happens — Caught by
.catch((err) => …)in the generation chain; no dedicated translated message exists for this path.
URL copy fails
Cause — Clipboard API unavailable or blocked.
What happens — Only the success path has copy (
cms.proposal.copy-url-success); a blocked clipboard is silent.
Raw translation key on screen
Cause — The code references
cms.proposals.new.image-generation.gifbut the key does not exist in translations.json.What happens — ember-intl renders the missing key (or its missing-message placeholder) instead of text — visible to every user of this screen until the key is added.
QA checklist
Tick these off while regression-testing this feature. The boxes are plain Markdown, so progress is visible in git diffs.
- ✅ Centre event with push notification
- ✅ AI image generated
- ✅ Store coupon promotion
- ⛔ Mandatory content missing
- ⛔ Images missing
- ⛔ Store missing on a store proposal
- ⛔ End date before start date
- ⛔ Event dates invalid
- ⛔ Push notification starts before the event
- ⛔ Segment required but missing
- ⛔ Poll without an activity
- ⛔ Images embedded in the description
- ⛔ Coupon incomplete
- ⛔ Audio upload fails or is too large
- ⛔ Required extra attribute empty
- ⛔ API rejection
- ⛔ AI image generation fails
- ⛔ URL copy fails
- ⛔ Raw translation key on screen
Implementation
app/controllers/cms/proposals/new.jsapp/routes/cms/proposals/new.jsapp/models/proposal.jsapp/models/proposal-extra.jsapp/models/coupon.jsapp/enums/proposal-types.jsapp/enums/image-ai-options.jsapp/services/pusher.js
Translation keys (29)
Key English cms.proposal.general-dataGeneral data cms.proposal.extra-attributesExtra attributes cms.proposal.copy-url-successURL has been copied! cms.proposal.code-params.user-name.descriptionAdd the username cms.proposal.audio-upload-errorError uploading audio file. Please try again or check audio file is less than 10 MB error.proposal.name.requiredName required error.proposal.description.requiredDescription is required error.proposal.category.requiredCategory is required error.proposal.channel.requiredAt least one channel must be selected error.proposal.proposalType.requiredType is required error.proposal.startDate.requiredStart date is required error.proposal.end-date.requiredEnd date required error.proposal.end-date.afterEnd date can not be before start date error.proposal.eventStartDate.requiredEvent start date is required error.proposal.event-end-date.requiredEvent end date required error.proposal.event-end-date.afterEvent end date can not be before event start date error.proposal.gender.requiredGender is required error.proposal.minimumAge.requiredMinimum age is required error.proposal.maximumAge.requiredMaximum age is required error.proposal.segment.requiredSegment is required error.proposal.campaign.requiredRequired Activity error.proposal.description.image-not-suportedThe images in the text field of description are not supported error.proposal.event-start-date.after-push-notification-startdatePush notification start date can not be before event start date error.coupon.text.requiredText is required error.coupon.usageLimit.requiredCoupon limit is required error.coupon.commerces.requiredStore required error.coupon.exchange-limit-by-user.requiredExchange limit is required user-errorSomething is wrong. Please check the following errors: api-errorSomething is wrong and it’s not your fault. Please, contact support with the following information:
Related
- Same area — Proposal list & publication workflow · Push notification list & publishing · Push notification editor (scheduling, geofencing, deep links) · Content categories · App button manager (MOFs) · Multicentre proposals
- Shares data with — Store create & edit · Activity editor (loyalty mechanics, rules & targeting) · Activity campaigns (programme grouping) · Centre editor (modules, app config, integrations) · Extra attributes · Customer create & edit · Customer receipts tab & ticket-stamp generation · Receipt verification queue
- Behind the same module — Proposal list & publication workflow · Push notification editor (scheduling, geofencing, deep links) · AI natural-language customer filter · Segment builder with automatic tag actions · Activity campaigns (programme grouping) · CandelarIA smart assistant
Generated from doc/feature-inventory.json · id cms-proposal-editor · commit 276788ca6