WIP jordan

This commit is contained in:
Jordan Weingarten
2026-04-16 11:11:44 -05:00
parent ff2d7b18b5
commit 67482f6629
163 changed files with 50627 additions and 728 deletions

View File

@@ -0,0 +1,178 @@
<script setup lang="ts">
import { MAX_LOGO_FILE_BYTES } from '~/types/branding'
import type { BrokerageBrandingState } from '~/types/branding'
definePageMeta({ ssr: false })
usePageTitle('Organization · Settings')
const { isSuperAdmin } = useSuperAdmin()
const toast = useToast()
const { saved, defaultBrokerageBranding } = useBrokerageBranding()
const draft = ref<BrokerageBrandingState>(defaultBrokerageBranding())
const fileInputRef = ref<HTMLInputElement | null>(null)
function syncDraftFromSaved() {
const s = saved.value
draft.value = {
companyName: s.companyName,
logoDataUrl: s.logoDataUrl,
logoFileName: s.logoFileName,
reportPageHeader: s.reportPageHeader,
reportPageFooter: s.reportPageFooter
}
}
onMounted(() => syncDraftFromSaved())
watch(saved, () => syncDraftFromSaved(), { deep: true })
function pickFile() {
fileInputRef.value?.click()
}
function onLogoFile(e: Event) {
const input = e.target as HTMLInputElement
const file = input.files?.[0]
input.value = ''
if (!file) return
if (!file.type.startsWith('image/')) {
toast.add({ title: 'Choose an image file', color: 'error' })
return
}
if (file.size > MAX_LOGO_FILE_BYTES) {
toast.add({
title: 'Image too large',
description: `Use a file under ${Math.round(MAX_LOGO_FILE_BYTES / 1024)} KB.`,
color: 'error'
})
return
}
const reader = new FileReader()
reader.onload = () => {
draft.value.logoDataUrl = reader.result as string
draft.value.logoFileName = file.name
}
reader.onerror = () => {
toast.add({ title: 'Could not read file', color: 'error' })
}
reader.readAsDataURL(file)
}
function clearLogo() {
draft.value.logoDataUrl = null
draft.value.logoFileName = ''
}
function save() {
saved.value = { ...draft.value }
toast.add({
title: 'Organization saved',
description: 'Sidebar and home use this brokerage identity for all users.',
color: 'success'
})
}
function cancel() {
syncDraftFromSaved()
toast.add({ title: 'Changes discarded', color: 'neutral' })
}
</script>
<template>
<div class="mx-auto max-w-3xl space-y-8">
<div class="flex flex-wrap items-center gap-x-3 gap-y-2 text-sm">
<AppBackToHome />
<span class="text-[var(--text-muted)] opacity-50" aria-hidden="true">|</span>
<NuxtLink to="/settings" class="font-medium text-[var(--text-muted)] transition hover:text-[var(--text-primary)]">
All settings
</NuxtLink>
</div>
<UAlert
v-if="!isSuperAdmin"
color="warning"
variant="soft"
title="Tenant administrators only"
description="Company name, logo, and report headers are managed here for the whole organization. Ask a superadmin to update them or to grant you access."
/>
<template v-else>
<div>
<h1 class="mt-1 text-2xl font-semibold tracking-tight text-[var(--text-primary)]">Organization</h1>
<p class="mt-2 max-w-2xl text-[14px] leading-relaxed text-[var(--text-muted)]">
Brokerage legal name and logo in the app chrome; report header and footer for PDFs and exports. This applies to
the whole tenant not personal account settings.
</p>
</div>
<UCard class="overflow-hidden border-[var(--sidebar-border)] bg-[var(--surface)] ring-1 ring-black/5">
<div class="border-b border-[var(--sidebar-border)] bg-[var(--page-bg)] px-5 py-3 text-sm text-[var(--text-primary)]">
General / General
</div>
<div class="space-y-6 p-5">
<UFormField label="Company legal name" class="w-full">
<UInput
v-model="draft.companyName"
placeholder="Brokerage legal name as registered"
class="w-full"
/>
</UFormField>
<div class="flex flex-col gap-4 sm:flex-row sm:items-start">
<div class="min-w-0 flex-1 space-y-2">
<p class="text-sm font-medium text-[var(--text-primary)]">Company logo (sidebar & home)</p>
<input ref="fileInputRef" type="file" accept="image/*" class="sr-only" @change="onLogoFile" />
<div class="flex flex-wrap items-center gap-2">
<UButton color="primary" size="sm" @click="pickFile">Browse</UButton>
<UButton v-if="draft.logoDataUrl" color="neutral" variant="ghost" size="sm" @click="clearLogo">
Remove logo
</UButton>
</div>
<p v-if="draft.logoFileName" class="text-xs text-[var(--text-muted)]">Current file: {{ draft.logoFileName }}</p>
<p class="text-xs text-[var(--text-muted)]">Transparent PNGs work best with the sidebar chrome treatment.</p>
</div>
<div
class="app-logo-chrome flex h-28 min-w-[200px] shrink-0 items-center justify-center rounded-lg border border-dashed border-[var(--sidebar-border)] px-4 py-3"
>
<img
v-if="draft.logoDataUrl"
:src="draft.logoDataUrl"
:alt="draft.companyName || 'Brokerage logo'"
class="max-h-24 max-w-full object-contain"
/>
<span v-else class="text-center text-xs text-[var(--text-muted)]">Logo preview</span>
</div>
</div>
</div>
</UCard>
<UCard class="overflow-hidden border-[var(--sidebar-border)] bg-[var(--surface)] ring-1 ring-black/5">
<div class="border-b border-[var(--sidebar-border)] bg-[var(--page-bg)] px-5 py-3 text-sm text-[var(--text-primary)]">
Reports / Reportes
</div>
<div class="space-y-4 p-5">
<UFormField label="Page header (PDFs)" class="w-full">
<UTextarea
v-model="draft.reportPageHeader"
:rows="4"
placeholder="Address, phone, RUC — top of generated reports."
class="w-full font-mono text-sm"
/>
</UFormField>
<UFormField label="Page footer (PDFs)" class="w-full">
<UTextarea
v-model="draft.reportPageFooter"
:rows="4"
placeholder="Legal disclaimer, validity, contact — footer for PDFs."
class="w-full font-mono text-sm"
/>
</UFormField>
</div>
</UCard>
<div class="flex flex-wrap gap-3">
<UButton color="neutral" variant="soft" @click="cancel">Cancel</UButton>
<UButton color="primary" icon="i-heroicons-check" @click="save">Save</UButton>
</div>
</template>
</div>
</template>