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>
191 lines
7.7 KiB
HTML
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 %}
|