feat(auth): auto-create admin user profiles with highest tier and complimentary flag
- Add `is_complimentary` column to UserProfile model (migration 019) - Update `_ensure_user_profile` to accept `is_admin` param; admins get highest subscription tier, is_complimentary=True, onboarding skipped - Call `_ensure_user_profile` from all login paths (OAuth, local user, admin creds) - Add `is_complimentary` to UserProfileUpsert schema, response helpers, list_users, get_user, upsert_user_profile in admin API - Add complimentary toggle to admin users UI with gift badge in table - Write 18 new tests covering complimentary plan and admin auto-creation - Update SubscriptionTiers.md documentation Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
This commit is contained in:
@@ -140,6 +140,14 @@
|
||||
></i>
|
||||
<span x-text="user.subscription_tier || 'free'"></span>
|
||||
</span>
|
||||
<span
|
||||
x-show="user.is_complimentary"
|
||||
class="ml-1 inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs font-semibold bg-green-100 text-green-700"
|
||||
title="Complimentary — not billed"
|
||||
>
|
||||
<i class="fas fa-gift" aria-hidden="true"></i>
|
||||
<span class="sr-only">Complimentary plan</span>
|
||||
</span>
|
||||
</td>
|
||||
<!-- Upload limit -->
|
||||
<td class="px-4 py-3 text-sm text-center text-gray-700">
|
||||
@@ -375,6 +383,25 @@
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Complimentary plan toggle -->
|
||||
<div class="flex items-center gap-3">
|
||||
<label class="relative inline-flex items-center cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
x-model="form.is_complimentary"
|
||||
class="sr-only peer"
|
||||
id="modal-complimentary"
|
||||
role="switch"
|
||||
:aria-checked="form.is_complimentary"
|
||||
/>
|
||||
<div class="w-10 h-6 bg-gray-200 peer-focus:ring-2 peer-focus:ring-blue-400 rounded-full peer peer-checked:bg-green-500 after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:after:translate-x-4"></div>
|
||||
</label>
|
||||
<label for="modal-complimentary" class="text-sm font-medium text-gray-700">
|
||||
Complimentary plan
|
||||
<span class="text-xs text-gray-400 font-normal">(user keeps tier benefits but is never billed — set automatically for admin accounts)</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
@@ -472,6 +499,7 @@ function adminUsersApp() {
|
||||
subscription_tier: 'free',
|
||||
subscription_billing_cycle: 'monthly',
|
||||
subscription_period_start: null,
|
||||
is_complimentary: false,
|
||||
},
|
||||
|
||||
// Delete modal
|
||||
@@ -524,7 +552,7 @@ function adminUsersApp() {
|
||||
openCreateModal() {
|
||||
this.isCreate = true;
|
||||
this.modalTitle = 'Add User Profile';
|
||||
this.form = { user_id: '', display_name: '', daily_upload_limit: null, notes: '', is_blocked: false, subscription_tier: 'free', subscription_billing_cycle: 'monthly', subscription_period_start: null };
|
||||
this.form = { user_id: '', display_name: '', daily_upload_limit: null, notes: '', is_blocked: false, subscription_tier: 'free', subscription_billing_cycle: 'monthly', subscription_period_start: null, is_complimentary: false };
|
||||
this.modalOpen = true;
|
||||
},
|
||||
|
||||
@@ -541,6 +569,7 @@ function adminUsersApp() {
|
||||
subscription_tier: user.subscription_tier || 'free',
|
||||
subscription_billing_cycle: user.subscription_billing_cycle || 'monthly',
|
||||
subscription_period_start: user.subscription_period_start ? user.subscription_period_start.substring(0, 10) : null,
|
||||
is_complimentary: !!user.is_complimentary,
|
||||
};
|
||||
this.modalOpen = true;
|
||||
},
|
||||
@@ -555,6 +584,7 @@ function adminUsersApp() {
|
||||
notes: this.form.notes || null,
|
||||
is_blocked: !!this.form.is_blocked,
|
||||
subscription_tier: this.form.subscription_tier || 'free',
|
||||
is_complimentary: !!this.form.is_complimentary,
|
||||
};
|
||||
const uid = encodeURIComponent(this.form.user_id);
|
||||
const resp = await fetch(`/api/admin/users/${uid}`, {
|
||||
|
||||
Reference in New Issue
Block a user