feat: portal redesign, pricing catalog, partner-staff invites
- portal: new admin/ and partner/ surfaces with full component library (AppLauncher, Avatar, Badge, Card, Modal, Tabs, etc.), composables, layouts, partner-routing middleware, and supporting server APIs - pricing: Price schema/module with operator CRUD, pricing.vue catalog UI, Subscription extended with cycle/currency/perSeatAmount/seats snapshots for stable MRR aggregation - partner staff: User.partnerId, invite-partner-user DTO and flow, /partners/:slug/users endpoints, InvitePartnerUserModal, shared dezky-partner-staff Authentik group - /me: partner-aware endpoint returning user + partner context so portal can route between end-user and partner-admin surfaces - tenant: seats field for portfolio displays and future MRR calculations - operator: pricing page, signed-out page, useMe/useToast composables, ToastStack
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
<script setup lang="ts">
|
||||
// Shown when a partner clicks "Enter customer" anywhere in the partner UI.
|
||||
// Forces the partner to acknowledge that every action they take inside the
|
||||
// customer org will be logged under their partner identity, and prompts for
|
||||
// an optional (but recommended) reason — captured into the customer audit log.
|
||||
|
||||
import type { CustomerOrg } from '~/data/customers'
|
||||
|
||||
const props = defineProps<{ customer: CustomerOrg | null }>()
|
||||
const emit = defineEmits<{ close: []; confirm: [reason: string] }>()
|
||||
|
||||
const reason = ref('Quarterly account review')
|
||||
|
||||
watch(
|
||||
() => props.customer?.id,
|
||||
() => {
|
||||
reason.value = 'Quarterly account review'
|
||||
},
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal
|
||||
:open="!!customer"
|
||||
eyebrow="Partner action"
|
||||
:title="customer ? `Enter ${customer.name} as partner` : 'Enter customer'"
|
||||
size="sm"
|
||||
@close="emit('close')"
|
||||
>
|
||||
<template v-if="customer">
|
||||
<div class="cust-card">
|
||||
<div class="swatch" :style="{ background: customer.brandColor }" />
|
||||
<div class="cust-meta">
|
||||
<div class="cust-name">{{ customer.name }}</div>
|
||||
<Mono dim>{{ customer.domain }} · {{ customer.planLabel }}</Mono>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="note">
|
||||
You'll see this customer's admin console exactly as their own admins do. Any change
|
||||
you make is logged as a <b>partner action</b>, visible in their audit log with your
|
||||
name attached.
|
||||
</p>
|
||||
|
||||
<label class="field">
|
||||
<Eyebrow>Reason for entering · recommended</Eyebrow>
|
||||
<textarea
|
||||
v-model="reason"
|
||||
placeholder="e.g. Investigating support ticket #841"
|
||||
rows="3"
|
||||
/>
|
||||
</label>
|
||||
</template>
|
||||
|
||||
<template #footer>
|
||||
<UiButton variant="ghost" @click="emit('close')">Cancel</UiButton>
|
||||
<UiButton variant="primary" @click="emit('confirm', reason)">
|
||||
<template #leading><UiIcon name="arrowRight" :size="14" /></template>
|
||||
Enter customer
|
||||
</UiButton>
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.cust-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
padding: 14px;
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.swatch {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 6px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.cust-meta { min-width: 0; }
|
||||
.cust-name {
|
||||
font-family: var(--font-display);
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
letter-spacing: -0.015em;
|
||||
}
|
||||
|
||||
.note {
|
||||
font-size: 13px;
|
||||
color: var(--text-dim);
|
||||
line-height: 1.6;
|
||||
margin: 0 0 16px 0;
|
||||
}
|
||||
|
||||
.field { display: flex; flex-direction: column; gap: 8px; }
|
||||
|
||||
textarea {
|
||||
width: 100%;
|
||||
padding: 10px 12px;
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
font-size: 13px;
|
||||
font-family: inherit;
|
||||
color: var(--text);
|
||||
resize: vertical;
|
||||
min-height: 60px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
textarea:focus { outline: none; border-color: var(--border-hi); }
|
||||
</style>
|
||||
Reference in New Issue
Block a user