- Add Loyalty and Billing SQL query presets to dev tools - Extract shared program-form.html partial and loyalty-program-form.js mixin - Refactor admin program-edit to use shared form partial - Add store loyalty API endpoints for program management Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
128 lines
4.9 KiB
JavaScript
128 lines
4.9 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) payload.logo_url = 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;
|
|
},
|
|
};
|
|
}
|