Files
orion/app/modules/loyalty/static/shared/js/loyalty-program-form.js
Samir Boulahtit 29d942322d
Some checks failed
CI / ruff (push) Successful in 10s
CI / pytest (push) Failing after 49m23s
CI / validate (push) Successful in 26s
CI / dependency-scanning (push) Successful in 30s
CI / docs (push) Has been skipped
CI / deploy (push) Has been skipped
feat(loyalty): make logo URL mandatory on program edit forms
Logo URL is required by Google Wallet API for LoyaltyClass creation.
Added validation across all three program edit screens (admin, merchant, store)
with a helpful hint explaining the requirement.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 20:08:38 +01:00

131 lines
5.0 KiB
JavaScript

// app/modules/loyalty/static/shared/js/loyalty-program-form.js
// Shared mixin for loyalty program settings forms (admin, merchant, store).
/**
* Factory that returns the shared Alpine.js properties and methods
* for the loyalty program settings form.
*
* Each page spreads this into its own component and provides:
* - init(), loadData(), saveSettings(), deleteProgram()
* with the correct API paths and navigation.
*/
function createProgramFormMixin() {
return {
// ---- state ----
settings: {
loyalty_type: 'points',
stamps_target: 10,
stamps_reward_description: '',
stamps_reward_value_cents: null,
points_per_euro: 1,
welcome_bonus_points: 0,
minimum_redemption_points: 100,
minimum_purchase_cents: 0,
points_expiration_days: null,
points_rewards: [],
cooldown_minutes: 15,
max_daily_stamps: 5,
require_staff_pin: true,
card_name: '',
card_color: '#4F46E5',
card_secondary_color: '',
logo_url: '',
hero_image_url: '',
terms_text: '',
privacy_url: '',
is_active: true,
},
isNewProgram: false,
saving: false,
deleting: false,
showDeleteModal: false,
// ---- helpers ----
/**
* Populate settings from an API program response object.
*/
populateSettings(program) {
this.settings = {
loyalty_type: program.loyalty_type || 'points',
stamps_target: program.stamps_target || 10,
stamps_reward_description: program.stamps_reward_description || '',
stamps_reward_value_cents: program.stamps_reward_value_cents || null,
points_per_euro: program.points_per_euro || 1,
welcome_bonus_points: program.welcome_bonus_points || 0,
minimum_redemption_points: program.minimum_redemption_points || 100,
minimum_purchase_cents: program.minimum_purchase_cents || 0,
points_expiration_days: program.points_expiration_days || null,
points_rewards: (program.points_rewards || []).map(r => ({
id: r.id,
name: r.name,
points_required: r.points_required,
description: r.description || '',
is_active: r.is_active !== false,
})),
cooldown_minutes: program.cooldown_minutes ?? 15,
max_daily_stamps: program.max_daily_stamps || 5,
require_staff_pin: program.require_staff_pin !== false,
card_name: program.card_name || '',
card_color: program.card_color || '#4F46E5',
card_secondary_color: program.card_secondary_color || '',
logo_url: program.logo_url || '',
hero_image_url: program.hero_image_url || '',
terms_text: program.terms_text || '',
privacy_url: program.privacy_url || '',
is_active: program.is_active !== false,
};
},
/**
* Build a clean payload from settings for POST/PATCH/PUT.
* Ensures rewards have IDs and cleans empty optional fields.
*/
buildPayload() {
const payload = { ...this.settings };
// Ensure rewards have IDs
payload.points_rewards = (payload.points_rewards || []).map((r, i) => ({
...r,
id: r.id || `reward_${i + 1}`,
is_active: r.is_active !== false,
}));
// Clean empty optional fields
if (!payload.stamps_reward_value_cents) payload.stamps_reward_value_cents = null;
if (!payload.points_expiration_days) payload.points_expiration_days = null;
if (!payload.minimum_purchase_cents) payload.minimum_purchase_cents = null;
if (!payload.card_name) payload.card_name = null;
if (!payload.card_secondary_color) payload.card_secondary_color = null;
if (!payload.logo_url) {
this.error = 'Logo URL is required for wallet integration.';
return null;
}
if (!payload.hero_image_url) payload.hero_image_url = null;
if (!payload.terms_text) payload.terms_text = null;
if (!payload.privacy_url) payload.privacy_url = null;
return payload;
},
addReward() {
this.settings.points_rewards.push({
id: `reward_${Date.now()}`,
name: '',
points_required: 100,
description: '',
is_active: true,
});
},
removeReward(index) {
this.settings.points_rewards.splice(index, 1);
},
confirmDelete() {
this.showDeleteModal = true;
},
};
}