Files
orion/app/modules/tenancy/templates/tenancy/merchant/profile.html
Samir Boulahtit 467b1510f4 fix: use apiClient instead of httponly cookie in merchant stores/profile pages
The merchant_token cookie is httponly, so JS cannot read it via
document.cookie. This caused getToken() to return null, redirecting
users to login, which then bounced back to dashboard.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 22:53:05 +01:00

191 lines
7.7 KiB
HTML

{# app/modules/tenancy/templates/tenancy/merchant/profile.html #}
{% extends "merchant/base.html" %}
{% block title %}Merchant Profile{% endblock %}
{% block content %}
<div x-data="merchantProfile()">
<!-- Page Header -->
<div class="mb-8">
<h2 class="text-2xl font-bold text-gray-900">Merchant Profile</h2>
<p class="mt-1 text-gray-500">Update your business information.</p>
</div>
<!-- Error -->
<div x-show="error" x-cloak class="mb-6 p-4 bg-red-50 border border-red-200 rounded-lg">
<p class="text-sm text-red-800" x-text="error"></p>
</div>
<!-- Success -->
<div x-show="successMessage" x-cloak class="mb-6 p-4 bg-green-50 border border-green-200 rounded-lg">
<p class="text-sm text-green-800" x-text="successMessage"></p>
</div>
<!-- Loading -->
<div x-show="loading" class="text-center py-12 text-gray-500">
<svg class="inline w-6 h-6 animate-spin mr-2" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path>
</svg>
Loading profile...
</div>
<!-- Profile Form -->
<div x-show="!loading" class="bg-white rounded-lg shadow-sm border border-gray-200">
<div class="px-6 py-4 border-b border-gray-200">
<h3 class="text-lg font-semibold text-gray-900">Business Information</h3>
</div>
<form @submit.prevent="saveProfile()" class="p-6 space-y-6">
<!-- Name -->
<div>
<label for="profile_name" class="block text-sm font-medium text-gray-700 mb-1">Business Name</label>
<input
id="profile_name"
type="text"
x-model="form.name"
required
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg text-gray-900 focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition-colors"
/>
</div>
<!-- Two-column row: Email and Phone -->
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2">
<div>
<label for="profile_email" class="block text-sm font-medium text-gray-700 mb-1">Contact Email</label>
<input
id="profile_email"
type="email"
x-model="form.contact_email"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg text-gray-900 focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition-colors"
/>
</div>
<div>
<label for="profile_phone" class="block text-sm font-medium text-gray-700 mb-1">Phone</label>
<input
id="profile_phone"
type="tel"
x-model="form.phone"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg text-gray-900 focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition-colors"
/>
</div>
</div>
<!-- Website -->
<div>
<label for="profile_website" class="block text-sm font-medium text-gray-700 mb-1">Website</label>
<input
id="profile_website"
type="url"
x-model="form.website"
placeholder="https://example.com"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg text-gray-900 placeholder-gray-400 focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition-colors"
/>
</div>
<!-- Business Address -->
<div>
<label for="profile_address" class="block text-sm font-medium text-gray-700 mb-1">Business Address</label>
<textarea
id="profile_address"
x-model="form.business_address"
rows="3"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg text-gray-900 focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition-colors"
></textarea>
</div>
<!-- Tax Number -->
<div>
<label for="profile_tax" class="block text-sm font-medium text-gray-700 mb-1">Tax Number (VAT ID)</label>
<input
id="profile_tax"
type="text"
x-model="form.tax_number"
placeholder="LU12345678"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg text-gray-900 placeholder-gray-400 focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition-colors"
/>
</div>
<!-- Save Button -->
<div class="flex justify-end pt-4 border-t border-gray-200">
<button
type="submit"
:disabled="saving"
class="inline-flex items-center px-5 py-2.5 text-sm font-semibold text-white bg-indigo-600 rounded-lg hover:bg-indigo-700 transition-colors disabled:opacity-50"
>
<span x-show="!saving">Save Changes</span>
<span x-show="saving" class="inline-flex items-center">
<svg class="w-4 h-4 animate-spin mr-2" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path>
</svg>
Saving...
</span>
</button>
</div>
</form>
</div>
</div>
{% endblock %}
{% block extra_scripts %}
<script>
function merchantProfile() {
return {
loading: true,
saving: false,
error: null,
successMessage: null,
form: {
name: '',
contact_email: '',
phone: '',
website: '',
business_address: '',
tax_number: ''
},
init() {
this.loadProfile();
},
async loadProfile() {
try {
const data = await apiClient.get('/merchants/account/profile');
this.form.name = data.name || '';
this.form.contact_email = data.contact_email || data.email || '';
this.form.phone = data.phone || '';
this.form.website = data.website || '';
this.form.business_address = data.business_address || data.address || '';
this.form.tax_number = data.tax_number || '';
} catch (err) {
console.error('Error loading profile:', err);
this.error = 'Failed to load profile. Please try again.';
} finally {
this.loading = false;
}
},
async saveProfile() {
this.saving = true;
this.error = null;
this.successMessage = null;
try {
await apiClient.put('/merchants/account/profile', this.form);
this.successMessage = 'Profile updated successfully.';
setTimeout(() => { this.successMessage = null; }, 3000);
} catch (err) {
this.error = err.message;
} finally {
this.saving = false;
}
}
};
}
</script>
{% endblock %}