Files
dezky/apps/portal/components/partner/EnterCustomerConfirmModal.vue
T
Ronni Baslund 0bd4e5498e 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
2026-05-28 20:00:33 +02:00

117 lines
2.9 KiB
Vue

<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>