Making the code easier to read and more consistent.
This commit is contained in:
@@ -1,50 +1,67 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { ae_loc, ae_api, slct } from '$lib/stores/ae_stores';
|
||||
import { goto } from '$app/navigation';
|
||||
import { Activity, Contact, ExternalLink, ListFilter, Mail, Phone, Plus, Search, ShieldCheck, User, X } from '@lucide/svelte';
|
||||
import { load_ae_obj_li__contact, create_ae_obj__contact } from '$lib/ae_core/ae_core__contact';
|
||||
import Contact_form from './ae_comp__contact_form.svelte';
|
||||
import { onMount } from 'svelte';
|
||||
import { ae_loc, ae_api, slct } from '$lib/stores/ae_stores';
|
||||
import { goto } from '$app/navigation';
|
||||
import {
|
||||
Activity,
|
||||
Contact,
|
||||
ExternalLink,
|
||||
ListFilter,
|
||||
Mail,
|
||||
Phone,
|
||||
Plus,
|
||||
Search,
|
||||
ShieldCheck,
|
||||
User,
|
||||
X
|
||||
} from '@lucide/svelte';
|
||||
import {
|
||||
load_ae_obj_li__contact,
|
||||
create_ae_obj__contact
|
||||
} from '$lib/ae_core/ae_core__contact';
|
||||
import Contact_form from './ae_comp__contact_form.svelte';
|
||||
|
||||
let contact_li: any[] = $state([]);
|
||||
let qry_str = $state('');
|
||||
let filtered_li: any[] = $derived(
|
||||
qry_str
|
||||
? contact_li.filter(c =>
|
||||
c.name?.toLowerCase().includes(qry_str.toLowerCase()) ||
|
||||
c.title?.toLowerCase().includes(qry_str.toLowerCase()) ||
|
||||
c.email?.toLowerCase().includes(qry_str.toLowerCase()) ||
|
||||
c.phone_office?.toLowerCase().includes(qry_str.toLowerCase())
|
||||
)
|
||||
: contact_li
|
||||
);
|
||||
let loading = $state(true);
|
||||
let show_add_form = $state(false);
|
||||
let contact_li: any[] = $state([]);
|
||||
let qry_str = $state('');
|
||||
let filtered_li: any[] = $derived(
|
||||
qry_str
|
||||
? contact_li.filter(
|
||||
(c) =>
|
||||
c.name?.toLowerCase().includes(qry_str.toLowerCase()) ||
|
||||
c.title?.toLowerCase().includes(qry_str.toLowerCase()) ||
|
||||
c.email?.toLowerCase().includes(qry_str.toLowerCase()) ||
|
||||
c.phone_office?.toLowerCase().includes(qry_str.toLowerCase())
|
||||
)
|
||||
: contact_li
|
||||
);
|
||||
let loading = $state(true);
|
||||
let show_add_form = $state(false);
|
||||
|
||||
async function load_contacts() {
|
||||
if (!$ae_loc.account_id) return;
|
||||
loading = true;
|
||||
contact_li = await load_ae_obj_li__contact({
|
||||
api_cfg: $ae_api,
|
||||
for_obj_id: $ae_loc.account_id,
|
||||
enabled: 'all',
|
||||
hidden: 'all',
|
||||
log_lvl: 1
|
||||
});
|
||||
loading = false;
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
if (!$ae_loc.manager_access) {
|
||||
goto('/core');
|
||||
return;
|
||||
}
|
||||
load_contacts();
|
||||
async function load_contacts() {
|
||||
if (!$ae_loc.account_id) return;
|
||||
loading = true;
|
||||
contact_li = await load_ae_obj_li__contact({
|
||||
api_cfg: $ae_api,
|
||||
for_obj_id: $ae_loc.account_id,
|
||||
enabled: 'all',
|
||||
hidden: 'all',
|
||||
log_lvl: 1
|
||||
});
|
||||
loading = false;
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
if (!$ae_loc.manager_access) {
|
||||
goto('/core');
|
||||
return;
|
||||
}
|
||||
load_contacts();
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="container mx-auto p-4 space-y-6">
|
||||
<header class="flex justify-between items-center bg-surface-50-900-token p-4 rounded-xl shadow-lg border border-surface-500/10">
|
||||
<header
|
||||
class="flex justify-between items-center bg-surface-50-900-token p-4 rounded-xl shadow-lg border border-surface-500/10">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="p-2 bg-primary-500/10 rounded-lg">
|
||||
<Phone size={24} class="text-primary-500" />
|
||||
@@ -53,8 +70,7 @@
|
||||
</div>
|
||||
<button
|
||||
class="btn btn-sm preset-filled-primary font-bold shadow-lg"
|
||||
onclick={() => show_add_form = !show_add_form}
|
||||
>
|
||||
onclick={() => (show_add_form = !show_add_form)}>
|
||||
{#if show_add_form}
|
||||
<X size={16} class="mr-2" /> Cancel
|
||||
{:else}
|
||||
@@ -73,29 +89,36 @@
|
||||
goto(`/core/contacts/${new_con.contact_id_random}`);
|
||||
}
|
||||
}}
|
||||
onCancel={() => show_add_form = false}
|
||||
/>
|
||||
onCancel={() => (show_add_form = false)} />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="card p-6 shadow-xl preset-tonal-surface border border-surface-500/10">
|
||||
<div
|
||||
class="card p-6 shadow-xl preset-tonal-surface border border-surface-500/10">
|
||||
<div class="max-w-2xl space-y-1">
|
||||
<span class="label block text-xs font-bold opacity-75 uppercase tracking-wider ml-1">Search Directory</span>
|
||||
<div class="flex bg-surface-200-700-token rounded-lg overflow-hidden border border-surface-500/20 shadow-inner group focus-within:ring-2 focus-within:ring-primary-500/50 transition-all">
|
||||
<div class="flex items-center justify-center px-4 bg-surface-300-600-token border-r border-surface-500/20">
|
||||
<span
|
||||
class="label block text-xs font-bold opacity-75 uppercase tracking-wider ml-1"
|
||||
>Search Directory</span>
|
||||
<div
|
||||
class="flex bg-surface-200-700-token rounded-lg overflow-hidden border border-surface-500/20 shadow-inner group focus-within:ring-2 focus-within:ring-primary-500/50 transition-all">
|
||||
<div
|
||||
class="flex items-center justify-center px-4 bg-surface-300-600-token border-r border-surface-500/20">
|
||||
<Search size={18} class="opacity-50" />
|
||||
</div>
|
||||
<input
|
||||
class="bg-transparent border-0 ring-0 focus:ring-0 p-3 grow placeholder:opacity-50"
|
||||
type="search"
|
||||
bind:value={qry_str}
|
||||
placeholder="Search by name, title, email, or phone..."
|
||||
/>
|
||||
<button class="preset-filled-primary font-bold px-10 py-3 hover:brightness-110 transition-all border-l border-surface-500/20 flex items-center justify-center min-w-[100px]" onclick={load_contacts} disabled={loading}>
|
||||
placeholder="Search by name, title, email, or phone..." />
|
||||
<button
|
||||
class="preset-filled-primary font-bold px-10 py-3 hover:brightness-110 transition-all border-l border-surface-500/20 flex items-center justify-center min-w-[100px]"
|
||||
onclick={load_contacts}
|
||||
disabled={loading}>
|
||||
{#if loading}
|
||||
<span class="animate-spin text-xl">⏳</span>
|
||||
{:else}
|
||||
<span class="whitespace-nowrap tracking-wide">Refresh</span>
|
||||
<span class="whitespace-nowrap tracking-wide"
|
||||
>Refresh</span>
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
@@ -104,53 +127,82 @@
|
||||
|
||||
{#if loading}
|
||||
<div class="card p-8 flex justify-center items-center h-64">
|
||||
<div class="placeholder animate-pulse w-full h-full rounded-2xl"></div>
|
||||
<div class="placeholder animate-pulse w-full h-full rounded-2xl">
|
||||
</div>
|
||||
</div>
|
||||
{:else if filtered_li.length === 0}
|
||||
<div class="card p-12 text-center preset-tonal-surface border-2 border-dashed border-surface-500/20 rounded-2xl">
|
||||
<div
|
||||
class="card p-12 text-center preset-tonal-surface border-2 border-dashed border-surface-500/20 rounded-2xl">
|
||||
<Contact size={48} class="mx-auto mb-4 opacity-20" />
|
||||
<h3 class="h3 font-bold opacity-50">No Contacts Found</h3>
|
||||
<p class="opacity-60 max-w-xs mx-auto mt-2">Business and support contacts will appear here.</p>
|
||||
<p class="opacity-60 max-w-xs mx-auto mt-2">
|
||||
Business and support contacts will appear here.
|
||||
</p>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="card p-6 preset-tonal-surface shadow-xl border border-surface-500/10">
|
||||
<h3 class="h4 font-bold border-b border-surface-500/30 pb-2 mb-6 flex items-center gap-2">
|
||||
<div
|
||||
class="card p-6 preset-tonal-surface shadow-xl border border-surface-500/10">
|
||||
<h3
|
||||
class="h4 font-bold border-b border-surface-500/30 pb-2 mb-6 flex items-center gap-2">
|
||||
<ListFilter size={18} class="text-secondary-500" />
|
||||
Directory Results
|
||||
<span class="badge preset-tonal-secondary ml-auto">{filtered_li.length} entries</span>
|
||||
<span class="badge preset-tonal-secondary ml-auto"
|
||||
>{filtered_li.length} entries</span>
|
||||
</h3>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{#each filtered_li as con (con.contact_id_random)}
|
||||
<div class="card p-5 space-y-4 preset-tonal-surface shadow-md border border-surface-500/10 hover:border-primary-500/30 transition-all group relative">
|
||||
<div
|
||||
class="card p-5 space-y-4 preset-tonal-surface shadow-md border border-surface-500/10 hover:border-primary-500/30 transition-all group relative">
|
||||
<div class="absolute top-4 right-4">
|
||||
<span class="badge {con.enable ? 'preset-filled-success' : 'preset-filled-error'} text-[8px] uppercase font-bold shadow-sm">
|
||||
<span
|
||||
class="badge {con.enable
|
||||
? 'preset-filled-success'
|
||||
: 'preset-filled-error'} text-[8px] uppercase font-bold shadow-sm">
|
||||
{con.enable ? 'Active' : 'Disabled'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<header class="flex items-center gap-3">
|
||||
<div class="avatar preset-filled-primary w-12 h-12 flex items-center justify-center rounded-full shadow-inner group-hover:scale-110 transition-transform">
|
||||
<div
|
||||
class="avatar preset-filled-primary w-12 h-12 flex items-center justify-center rounded-full shadow-inner group-hover:scale-110 transition-transform">
|
||||
<User size={24} />
|
||||
</div>
|
||||
<div class="pr-12">
|
||||
<p class="font-black tracking-tight truncate">{con.name || con.title || '--'}</p>
|
||||
<p class="text-[10px] uppercase font-bold opacity-50 truncate">{con.title || 'Support Contact'}</p>
|
||||
<p class="font-black tracking-tight truncate">
|
||||
{con.name || con.title || '--'}
|
||||
</p>
|
||||
<p
|
||||
class="text-[10px] uppercase font-bold opacity-50 truncate">
|
||||
{con.title || 'Support Contact'}
|
||||
</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="space-y-2 text-xs opacity-70">
|
||||
<div class="flex items-center gap-2 bg-black/5 p-2 rounded-lg">
|
||||
<Mail size={14} class="text-primary-500 shrink-0" />
|
||||
<span class="truncate">{con.email || 'No Email'}</span>
|
||||
<div
|
||||
class="flex items-center gap-2 bg-black/5 p-2 rounded-lg">
|
||||
<Mail
|
||||
size={14}
|
||||
class="text-primary-500 shrink-0" />
|
||||
<span class="truncate"
|
||||
>{con.email || 'No Email'}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 bg-black/5 p-2 rounded-lg">
|
||||
<Phone size={14} class="text-secondary-500 shrink-0" />
|
||||
<span class="truncate">{con.phone_office || con.phone_mobile || '--'}</span>
|
||||
<div
|
||||
class="flex items-center gap-2 bg-black/5 p-2 rounded-lg">
|
||||
<Phone
|
||||
size={14}
|
||||
class="text-secondary-500 shrink-0" />
|
||||
<span class="truncate"
|
||||
>{con.phone_office ||
|
||||
con.phone_mobile ||
|
||||
'--'}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a class="btn btn-sm preset-filled-primary font-bold w-full shadow-lg group-hover:brightness-110 transition-all" href="/core/contacts/{con.contact_id_random}">
|
||||
<a
|
||||
class="btn btn-sm preset-filled-primary font-bold w-full shadow-lg group-hover:brightness-110 transition-all"
|
||||
href="/core/contacts/{con.contact_id_random}">
|
||||
Manage Contact
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -1,56 +1,75 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { page } from '$app/stores';
|
||||
import {
|
||||
load_ae_obj_id__contact,
|
||||
update_ae_obj__contact,
|
||||
delete_ae_obj_id__contact
|
||||
} from '$lib/ae_core/ae_core__contact';
|
||||
import { editable_fields__contact } from '$lib/ae_core/ae_core__contact.editable_fields';
|
||||
import { ae_api, ae_loc } from '$lib/stores/ae_stores';
|
||||
import { goto } from '$app/navigation';
|
||||
import { Activity, ArrowLeft, Contact, Edit, Eye, Globe, Info, Link2, Linkedin, Mail, Phone, Save, ShieldCheck, Trash2, UserRound } from '@lucide/svelte';
|
||||
import Contact_form from '../ae_comp__contact_form.svelte';
|
||||
import { onMount } from 'svelte';
|
||||
import { page } from '$app/stores';
|
||||
import {
|
||||
load_ae_obj_id__contact,
|
||||
update_ae_obj__contact,
|
||||
delete_ae_obj_id__contact
|
||||
} from '$lib/ae_core/ae_core__contact';
|
||||
import { editable_fields__contact } from '$lib/ae_core/ae_core__contact.editable_fields';
|
||||
import { ae_api, ae_loc } from '$lib/stores/ae_stores';
|
||||
import { goto } from '$app/navigation';
|
||||
import {
|
||||
Activity,
|
||||
ArrowLeft,
|
||||
Contact,
|
||||
Edit,
|
||||
Eye,
|
||||
Globe,
|
||||
Info,
|
||||
Link2,
|
||||
Linkedin,
|
||||
Mail,
|
||||
Phone,
|
||||
Save,
|
||||
ShieldCheck,
|
||||
Trash2,
|
||||
UserRound
|
||||
} from '@lucide/svelte';
|
||||
import Contact_form from '../ae_comp__contact_form.svelte';
|
||||
|
||||
let contact_id = $derived($page.params.contact_id ?? '');
|
||||
let contact: any = $state(null);
|
||||
let loading = $state(true);
|
||||
let is_editing = $state(false);
|
||||
let contact_id = $derived($page.params.contact_id ?? '');
|
||||
let contact: any = $state(null);
|
||||
let loading = $state(true);
|
||||
let is_editing = $state(false);
|
||||
|
||||
async function load_data() {
|
||||
loading = true;
|
||||
contact = await load_ae_obj_id__contact({
|
||||
api_cfg: $ae_api,
|
||||
contact_id,
|
||||
log_lvl: 1
|
||||
});
|
||||
loading = false;
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
if (!$ae_loc.manager_access) {
|
||||
goto('/core');
|
||||
return;
|
||||
}
|
||||
load_data();
|
||||
async function load_data() {
|
||||
loading = true;
|
||||
contact = await load_ae_obj_id__contact({
|
||||
api_cfg: $ae_api,
|
||||
contact_id,
|
||||
log_lvl: 1
|
||||
});
|
||||
loading = false;
|
||||
}
|
||||
|
||||
async function handle_delete() {
|
||||
if (!confirm('Permanently delete this contact?')) return;
|
||||
await delete_ae_obj_id__contact({
|
||||
api_cfg: $ae_api,
|
||||
contact_id,
|
||||
method: 'delete',
|
||||
log_lvl: 1
|
||||
});
|
||||
goto('/core/contacts');
|
||||
onMount(() => {
|
||||
if (!$ae_loc.manager_access) {
|
||||
goto('/core');
|
||||
return;
|
||||
}
|
||||
load_data();
|
||||
});
|
||||
|
||||
async function handle_delete() {
|
||||
if (!confirm('Permanently delete this contact?')) return;
|
||||
await delete_ae_obj_id__contact({
|
||||
api_cfg: $ae_api,
|
||||
contact_id,
|
||||
method: 'delete',
|
||||
log_lvl: 1
|
||||
});
|
||||
goto('/core/contacts');
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="container mx-auto p-4 space-y-6">
|
||||
<header class="flex flex-wrap justify-between items-center bg-surface-50-900-token p-4 rounded-xl shadow-lg border border-surface-500/10 gap-4">
|
||||
<header
|
||||
class="flex flex-wrap justify-between items-center bg-surface-50-900-token p-4 rounded-xl shadow-lg border border-surface-500/10 gap-4">
|
||||
<div class="flex items-center gap-4">
|
||||
<a class="btn btn-sm preset-tonal-surface shadow-sm" href="/core/contacts">
|
||||
<a
|
||||
class="btn btn-sm preset-tonal-surface shadow-sm"
|
||||
href="/core/contacts">
|
||||
<ArrowLeft size={16} />
|
||||
</a>
|
||||
<div class="flex items-center gap-3">
|
||||
@@ -58,20 +77,31 @@
|
||||
<UserRound size={24} class="text-primary-500" />
|
||||
</div>
|
||||
<div>
|
||||
<h1 class="h2 font-black tracking-tight">{contact?.name || contact?.title || 'Loading...'}</h1>
|
||||
<p class="text-xs font-bold opacity-50 uppercase tracking-widest">Contact Detail</p>
|
||||
<h1 class="h2 font-black tracking-tight">
|
||||
{contact?.name || contact?.title || 'Loading...'}
|
||||
</h1>
|
||||
<p
|
||||
class="text-xs font-bold opacity-50 uppercase tracking-widest">
|
||||
Contact Detail
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<button class="btn btn-sm preset-tonal-secondary font-bold shadow-sm" onclick={() => is_editing = !is_editing} disabled={loading}>
|
||||
<button
|
||||
class="btn btn-sm preset-tonal-secondary font-bold shadow-sm"
|
||||
onclick={() => (is_editing = !is_editing)}
|
||||
disabled={loading}>
|
||||
{#if is_editing}
|
||||
<Eye size={16} class="mr-2" /> View Mode
|
||||
{:else}
|
||||
<Edit size={16} class="mr-2" /> Edit Mode
|
||||
{/if}
|
||||
</button>
|
||||
<button class="btn btn-sm preset-tonal-error font-bold shadow-sm" onclick={handle_delete} disabled={loading}>
|
||||
<button
|
||||
class="btn btn-sm preset-tonal-error font-bold shadow-sm"
|
||||
onclick={handle_delete}
|
||||
disabled={loading}>
|
||||
<Trash2 size={16} class="mr-2" /> Delete
|
||||
</button>
|
||||
</div>
|
||||
@@ -79,54 +109,74 @@
|
||||
|
||||
{#if loading}
|
||||
<div class="card p-8 flex justify-center items-center h-64">
|
||||
<div class="placeholder animate-pulse w-full h-full rounded-2xl"></div>
|
||||
<div class="placeholder animate-pulse w-full h-full rounded-2xl">
|
||||
</div>
|
||||
</div>
|
||||
{:else if contact}
|
||||
{#if is_editing}
|
||||
<div class="animate-fade-in">
|
||||
<Contact_form
|
||||
{contact}
|
||||
<Contact_form
|
||||
{contact}
|
||||
onSave={(updated) => {
|
||||
contact = updated;
|
||||
is_editing = false;
|
||||
}}
|
||||
onCancel={() => is_editing = false}
|
||||
/>
|
||||
onCancel={() => (is_editing = false)} />
|
||||
</div>
|
||||
{:else}
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6 animate-fade-in">
|
||||
<div class="lg:col-span-2 space-y-6">
|
||||
<div class="card p-6 shadow-xl preset-tonal-surface border border-surface-500/10 space-y-6">
|
||||
<h3 class="h4 font-bold border-b border-surface-500/30 pb-2 flex items-center gap-2">
|
||||
<div
|
||||
class="card p-6 shadow-xl preset-tonal-surface border border-surface-500/10 space-y-6">
|
||||
<h3
|
||||
class="h4 font-bold border-b border-surface-500/30 pb-2 flex items-center gap-2">
|
||||
<Contact size={20} class="text-primary-500" />
|
||||
Core Information
|
||||
</h3>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div class="space-y-1">
|
||||
<p class="text-[10px] opacity-60 uppercase font-black tracking-widest flex items-center gap-1">
|
||||
<p
|
||||
class="text-[10px] opacity-60 uppercase font-black tracking-widest flex items-center gap-1">
|
||||
<UserRound size={10} /> Full Name / Title
|
||||
</p>
|
||||
<p class="text-lg font-black tracking-tight leading-tight">{contact.name || contact.title || '--'}</p>
|
||||
<p
|
||||
class="text-lg font-black tracking-tight leading-tight">
|
||||
{contact.name || contact.title || '--'}
|
||||
</p>
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<p class="text-[10px] opacity-60 uppercase font-black tracking-widest flex items-center gap-1">
|
||||
<p
|
||||
class="text-[10px] opacity-60 uppercase font-black tracking-widest flex items-center gap-1">
|
||||
<Activity size={10} /> Tagline / Role
|
||||
</p>
|
||||
<p class="font-bold">{contact.tagline || '--'}</p>
|
||||
<p class="font-bold">
|
||||
{contact.tagline || '--'}
|
||||
</p>
|
||||
</div>
|
||||
<div class="space-y-1 bg-black/5 p-4 rounded-xl border border-surface-500/10">
|
||||
<p class="text-[10px] opacity-60 uppercase font-black tracking-widest flex items-center gap-1">
|
||||
<div
|
||||
class="space-y-1 bg-black/5 p-4 rounded-xl border border-surface-500/10">
|
||||
<p
|
||||
class="text-[10px] opacity-60 uppercase font-black tracking-widest flex items-center gap-1">
|
||||
<Mail size={10} /> Email Address
|
||||
</p>
|
||||
<p class="font-bold text-primary-500 break-all">{contact.email || '--'}</p>
|
||||
<p class="font-bold text-primary-500 break-all">
|
||||
{contact.email || '--'}
|
||||
</p>
|
||||
</div>
|
||||
<div class="space-y-1 bg-black/5 p-4 rounded-xl border border-surface-500/10">
|
||||
<p class="text-[10px] opacity-60 uppercase font-black tracking-widest flex items-center gap-1">
|
||||
<div
|
||||
class="space-y-1 bg-black/5 p-4 rounded-xl border border-surface-500/10">
|
||||
<p
|
||||
class="text-[10px] opacity-60 uppercase font-black tracking-widest flex items-center gap-1">
|
||||
<Globe size={10} /> Website
|
||||
</p>
|
||||
{#if contact.website_url}
|
||||
<a href={contact.website_url} target="_blank" rel="noopener noreferrer" class="font-bold text-secondary-500 hover:underline flex items-center gap-2 truncate">
|
||||
{contact.website_url} <Link2 size={12} />
|
||||
<a
|
||||
href={contact.website_url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="font-bold text-secondary-500 hover:underline flex items-center gap-2 truncate">
|
||||
{contact.website_url}
|
||||
<Link2 size={12} />
|
||||
</a>
|
||||
{:else}
|
||||
<p class="font-bold">--</p>
|
||||
@@ -135,36 +185,52 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card p-6 shadow-xl preset-tonal-surface border border-surface-500/10 space-y-6">
|
||||
<h3 class="h4 font-bold border-b border-surface-500/30 pb-2 flex items-center gap-2">
|
||||
<div
|
||||
class="card p-6 shadow-xl preset-tonal-surface border border-surface-500/10 space-y-6">
|
||||
<h3
|
||||
class="h4 font-bold border-b border-surface-500/30 pb-2 flex items-center gap-2">
|
||||
<Phone size={20} class="text-secondary-500" />
|
||||
Communication & Social
|
||||
</h3>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div class="space-y-1">
|
||||
<p class="text-[10px] opacity-60 uppercase font-black tracking-widest flex items-center gap-1">
|
||||
<p
|
||||
class="text-[10px] opacity-60 uppercase font-black tracking-widest flex items-center gap-1">
|
||||
<Phone size={10} /> Mobile Phone
|
||||
</p>
|
||||
<p class="font-mono font-bold">{contact.phone_mobile || contact.phone || '--'}</p>
|
||||
<p class="font-mono font-bold">
|
||||
{contact.phone_mobile ||
|
||||
contact.phone ||
|
||||
'--'}
|
||||
</p>
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<p class="text-[10px] opacity-60 uppercase font-black tracking-widest flex items-center gap-1">
|
||||
<p
|
||||
class="text-[10px] opacity-60 uppercase font-black tracking-widest flex items-center gap-1">
|
||||
<Phone size={10} /> Office Phone
|
||||
</p>
|
||||
<p class="font-mono font-bold">{contact.phone_office || '--'}</p>
|
||||
<p class="font-mono font-bold">
|
||||
{contact.phone_office || '--'}
|
||||
</p>
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<p class="text-[10px] opacity-60 uppercase font-black tracking-widest flex items-center gap-1">
|
||||
<p
|
||||
class="text-[10px] opacity-60 uppercase font-black tracking-widest flex items-center gap-1">
|
||||
<Linkedin size={10} /> LinkedIn
|
||||
</p>
|
||||
<p class="font-bold truncate">{contact.linkedin_url || '--'}</p>
|
||||
<p class="font-bold truncate">
|
||||
{contact.linkedin_url || '--'}
|
||||
</p>
|
||||
</div>
|
||||
<div class="md:col-span-2 space-y-2">
|
||||
<p class="text-[10px] opacity-60 uppercase font-black tracking-widest flex items-center gap-1">
|
||||
<p
|
||||
class="text-[10px] opacity-60 uppercase font-black tracking-widest flex items-center gap-1">
|
||||
<Info size={10} /> Internal Notes
|
||||
</p>
|
||||
<div class="p-4 bg-black/5 rounded-xl border border-dashed border-surface-500/20 italic opacity-80 min-h-[80px]">
|
||||
{contact.notes || 'No internal notes provided for this contact.'}
|
||||
<div
|
||||
class="p-4 bg-black/5 rounded-xl border border-dashed border-surface-500/20 italic opacity-80 min-h-[80px]">
|
||||
{contact.notes ||
|
||||
'No internal notes provided for this contact.'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -172,40 +238,77 @@
|
||||
</div>
|
||||
|
||||
<div class="space-y-6">
|
||||
<div class="card p-6 shadow-xl preset-tonal-surface border border-surface-500/10 space-y-6">
|
||||
<h3 class="h4 font-bold border-b border-surface-500/30 pb-2 flex items-center gap-2">
|
||||
<div
|
||||
class="card p-6 shadow-xl preset-tonal-surface border border-surface-500/10 space-y-6">
|
||||
<h3
|
||||
class="h4 font-bold border-b border-surface-500/30 pb-2 flex items-center gap-2">
|
||||
<ShieldCheck size={20} class="text-warning-500" />
|
||||
Status & Flags
|
||||
</h3>
|
||||
<div class="space-y-4">
|
||||
<div class="flex justify-between items-center p-3 bg-black/5 rounded-lg">
|
||||
<span class="text-sm font-bold opacity-75">Enabled</span>
|
||||
<span class="badge {contact.enable ? 'preset-filled-success' : 'preset-filled-error'} px-4 py-1 shadow-sm">
|
||||
<div
|
||||
class="flex justify-between items-center p-3 bg-black/5 rounded-lg">
|
||||
<span class="text-sm font-bold opacity-75"
|
||||
>Enabled</span>
|
||||
<span
|
||||
class="badge {contact.enable
|
||||
? 'preset-filled-success'
|
||||
: 'preset-filled-error'} px-4 py-1 shadow-sm">
|
||||
{contact.enable ? 'ACTIVE' : 'DISABLED'}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex justify-between items-center p-3 bg-black/5 rounded-lg">
|
||||
<span class="text-sm font-bold opacity-75">Hidden</span>
|
||||
<span class="badge {contact.hide ? 'preset-filled-warning' : 'preset-filled-surface'} px-4 py-1 shadow-sm">
|
||||
<div
|
||||
class="flex justify-between items-center p-3 bg-black/5 rounded-lg">
|
||||
<span class="text-sm font-bold opacity-75"
|
||||
>Hidden</span>
|
||||
<span
|
||||
class="badge {contact.hide
|
||||
? 'preset-filled-warning'
|
||||
: 'preset-filled-surface'} px-4 py-1 shadow-sm">
|
||||
{contact.hide ? 'YES' : 'NO'}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex justify-between items-center p-3 bg-black/5 rounded-lg">
|
||||
<span class="text-sm font-bold opacity-75">Priority</span>
|
||||
<span class="badge {contact.priority ? 'preset-filled-secondary' : 'preset-filled-surface'} px-4 py-1 shadow-sm">
|
||||
<div
|
||||
class="flex justify-between items-center p-3 bg-black/5 rounded-lg">
|
||||
<span class="text-sm font-bold opacity-75"
|
||||
>Priority</span>
|
||||
<span
|
||||
class="badge {contact.priority
|
||||
? 'preset-filled-secondary'
|
||||
: 'preset-filled-surface'} px-4 py-1 shadow-sm">
|
||||
{contact.priority ? 'YES' : 'NO'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card p-5 preset-tonal-surface shadow-inner border border-surface-500/10 space-y-3">
|
||||
<p class="text-[10px] uppercase font-black opacity-40 tracking-widest border-b border-surface-500/20 pb-1">System Audit</p>
|
||||
<div
|
||||
class="card p-5 preset-tonal-surface shadow-inner border border-surface-500/10 space-y-3">
|
||||
<p
|
||||
class="text-[10px] uppercase font-black opacity-40 tracking-widest border-b border-surface-500/20 pb-1">
|
||||
System Audit
|
||||
</p>
|
||||
<div class="space-y-2 text-[10px] font-mono opacity-60">
|
||||
<p class="flex justify-between"><span>ID:</span> <span class="text-primary-500 font-bold">{contact.contact_id_random}</span></p>
|
||||
<p class="flex justify-between"><span>Created:</span> <span>{new Date(contact.created_on).toLocaleString()}</span></p>
|
||||
<p class="flex justify-between">
|
||||
<span>ID:</span>
|
||||
<span class="text-primary-500 font-bold"
|
||||
>{contact.contact_id_random}</span>
|
||||
</p>
|
||||
<p class="flex justify-between">
|
||||
<span>Created:</span>
|
||||
<span
|
||||
>{new Date(
|
||||
contact.created_on
|
||||
).toLocaleString()}</span>
|
||||
</p>
|
||||
{#if contact.updated_on}
|
||||
<p class="flex justify-between"><span>Updated:</span> <span>{new Date(contact.updated_on).toLocaleString()}</span></p>
|
||||
<p class="flex justify-between">
|
||||
<span>Updated:</span>
|
||||
<span
|
||||
>{new Date(
|
||||
contact.updated_on
|
||||
).toLocaleString()}</span>
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,122 +1,144 @@
|
||||
<script lang="ts">
|
||||
/**
|
||||
* Contact Form Component
|
||||
* Standardized 2026-01-09 for Core UI Polish.
|
||||
* Uses unified ae_Contact type and Svelte 5 Runes.
|
||||
*/
|
||||
import { ae_api, ae_loc } from '$lib/stores/ae_stores';
|
||||
import { update_ae_obj__contact, create_ae_obj__contact } from '$lib/ae_core/ae_core__contact';
|
||||
import type { ae_Contact } from '$lib/types/ae_types';
|
||||
import { Facebook, Globe, Instagram, Linkedin, Mail, Phone, Save, UserPlus, X } from '@lucide/svelte';
|
||||
interface Props {
|
||||
contact?: ae_Contact | null;
|
||||
onSave?: (contact: ae_Contact) => void;
|
||||
onCancel?: () => void;
|
||||
}
|
||||
/**
|
||||
* Contact Form Component
|
||||
* Standardized 2026-01-09 for Core UI Polish.
|
||||
* Uses unified ae_Contact type and Svelte 5 Runes.
|
||||
*/
|
||||
import { ae_api, ae_loc } from '$lib/stores/ae_stores';
|
||||
import {
|
||||
update_ae_obj__contact,
|
||||
create_ae_obj__contact
|
||||
} from '$lib/ae_core/ae_core__contact';
|
||||
import type { ae_Contact } from '$lib/types/ae_types';
|
||||
import {
|
||||
Facebook,
|
||||
Globe,
|
||||
Instagram,
|
||||
Linkedin,
|
||||
Mail,
|
||||
Phone,
|
||||
Save,
|
||||
UserPlus,
|
||||
X
|
||||
} from '@lucide/svelte';
|
||||
interface Props {
|
||||
contact?: ae_Contact | null;
|
||||
onSave?: (contact: ae_Contact) => void;
|
||||
onCancel?: () => void;
|
||||
}
|
||||
|
||||
let { contact = null, onSave, onCancel }: Props = $props();
|
||||
let { contact = null, onSave, onCancel }: Props = $props();
|
||||
|
||||
// Form State (Runes)
|
||||
let formData = $state({
|
||||
title: '',
|
||||
tagline: '',
|
||||
email: '',
|
||||
phone_mobile: '',
|
||||
phone_office: '',
|
||||
website_url: '',
|
||||
facebook_url: '',
|
||||
instagram_url: '',
|
||||
linkedin_url: '',
|
||||
notes: '',
|
||||
enable: true,
|
||||
hide: false,
|
||||
priority: false
|
||||
});
|
||||
// Form State (Runes)
|
||||
let formData = $state({
|
||||
title: '',
|
||||
tagline: '',
|
||||
email: '',
|
||||
phone_mobile: '',
|
||||
phone_office: '',
|
||||
website_url: '',
|
||||
facebook_url: '',
|
||||
instagram_url: '',
|
||||
linkedin_url: '',
|
||||
notes: '',
|
||||
enable: true,
|
||||
hide: false,
|
||||
priority: false
|
||||
});
|
||||
|
||||
// Reset form when contact prop changes
|
||||
$effect(() => {
|
||||
formData.title = contact?.title ?? '';
|
||||
formData.tagline = contact?.tagline ?? '';
|
||||
formData.email = contact?.email ?? '';
|
||||
formData.phone_mobile = contact?.phone_mobile ?? '';
|
||||
formData.phone_office = contact?.phone_office ?? '';
|
||||
formData.website_url = contact?.website_url ?? '';
|
||||
formData.facebook_url = contact?.facebook_url ?? '';
|
||||
formData.instagram_url = contact?.instagram_url ?? '';
|
||||
formData.linkedin_url = contact?.linkedin_url ?? '';
|
||||
formData.notes = contact?.notes ?? '';
|
||||
formData.enable = contact?.enable ?? true;
|
||||
formData.hide = contact?.hide ?? false;
|
||||
formData.priority = contact?.priority ?? false;
|
||||
});
|
||||
// Reset form when contact prop changes
|
||||
$effect(() => {
|
||||
formData.title = contact?.title ?? '';
|
||||
formData.tagline = contact?.tagline ?? '';
|
||||
formData.email = contact?.email ?? '';
|
||||
formData.phone_mobile = contact?.phone_mobile ?? '';
|
||||
formData.phone_office = contact?.phone_office ?? '';
|
||||
formData.website_url = contact?.website_url ?? '';
|
||||
formData.facebook_url = contact?.facebook_url ?? '';
|
||||
formData.instagram_url = contact?.instagram_url ?? '';
|
||||
formData.linkedin_url = contact?.linkedin_url ?? '';
|
||||
formData.notes = contact?.notes ?? '';
|
||||
formData.enable = contact?.enable ?? true;
|
||||
formData.hide = contact?.hide ?? false;
|
||||
formData.priority = contact?.priority ?? false;
|
||||
});
|
||||
|
||||
let is_loading = $state(false);
|
||||
let error_msg = $state('');
|
||||
let is_loading = $state(false);
|
||||
let error_msg = $state('');
|
||||
|
||||
async function handleSubmit(event: Event) {
|
||||
event.preventDefault();
|
||||
is_loading = true;
|
||||
error_msg = '';
|
||||
async function handleSubmit(event: Event) {
|
||||
event.preventDefault();
|
||||
is_loading = true;
|
||||
error_msg = '';
|
||||
|
||||
// Surgical Payload
|
||||
const payload: any = { ...formData };
|
||||
for (const key in payload) {
|
||||
if (typeof payload[key] === 'string' && payload[key].trim() === '') {
|
||||
// title is likely required, but we'll trim it
|
||||
if (key === 'title') {
|
||||
payload[key] = payload[key].trim();
|
||||
} else {
|
||||
payload[key] = null;
|
||||
}
|
||||
// Surgical Payload
|
||||
const payload: any = { ...formData };
|
||||
for (const key in payload) {
|
||||
if (typeof payload[key] === 'string' && payload[key].trim() === '') {
|
||||
// title is likely required, but we'll trim it
|
||||
if (key === 'title') {
|
||||
payload[key] = payload[key].trim();
|
||||
} else {
|
||||
payload[key] = null;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
let result;
|
||||
if (contact?.contact_id_random) {
|
||||
// Update existing
|
||||
result = await update_ae_obj__contact({
|
||||
api_cfg: $ae_api,
|
||||
contact_id: contact.contact_id_random,
|
||||
data_kv: payload,
|
||||
log_lvl: 1
|
||||
});
|
||||
} else {
|
||||
// Create new
|
||||
result = await create_ae_obj__contact({
|
||||
api_cfg: $ae_api,
|
||||
account_id: $ae_loc.account_id,
|
||||
data_kv: payload,
|
||||
log_lvl: 1
|
||||
});
|
||||
}
|
||||
|
||||
if (result) {
|
||||
if (onSave) onSave(result);
|
||||
} else {
|
||||
error_msg = 'Failed to save contact record.';
|
||||
}
|
||||
} catch (err: any) {
|
||||
error_msg = err.message || 'An error occurred while saving.';
|
||||
} finally {
|
||||
is_loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
let result;
|
||||
if (contact?.contact_id_random) {
|
||||
// Update existing
|
||||
result = await update_ae_obj__contact({
|
||||
api_cfg: $ae_api,
|
||||
contact_id: contact.contact_id_random,
|
||||
data_kv: payload,
|
||||
log_lvl: 1
|
||||
});
|
||||
} else {
|
||||
// Create new
|
||||
result = await create_ae_obj__contact({
|
||||
api_cfg: $ae_api,
|
||||
account_id: $ae_loc.account_id,
|
||||
data_kv: payload,
|
||||
log_lvl: 1
|
||||
});
|
||||
}
|
||||
|
||||
if (result) {
|
||||
if (onSave) onSave(result);
|
||||
} else {
|
||||
error_msg = 'Failed to save contact record.';
|
||||
}
|
||||
} catch (err: any) {
|
||||
error_msg = err.message || 'An error occurred while saving.';
|
||||
} finally {
|
||||
is_loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<form onsubmit={handleSubmit} class="card p-6 space-y-6 shadow-xl preset-tonal-surface">
|
||||
<header class="flex justify-between items-center border-b border-surface-500/30 pb-4">
|
||||
<form
|
||||
onsubmit={handleSubmit}
|
||||
class="card p-6 space-y-6 shadow-xl preset-tonal-surface">
|
||||
<header
|
||||
class="flex justify-between items-center border-b border-surface-500/30 pb-4">
|
||||
<h3 class="h3 flex items-center gap-2">
|
||||
<UserPlus size={24} />
|
||||
{contact ? 'Edit Contact' : 'Create New Contact'}
|
||||
</h3>
|
||||
<div class="flex gap-2">
|
||||
{#if onCancel}
|
||||
<button type="button" class="btn btn-sm preset-tonal-surface" onclick={onCancel}>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm preset-tonal-surface"
|
||||
onclick={onCancel}>
|
||||
<X size={16} class="mr-1" /> Cancel
|
||||
</button>
|
||||
{/if}
|
||||
<button type="submit" class="btn btn-sm preset-filled-primary font-bold shadow-lg" disabled={is_loading}>
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-sm preset-filled-primary font-bold shadow-lg"
|
||||
disabled={is_loading}>
|
||||
{#if is_loading}
|
||||
<span class="animate-spin mr-2">⏳</span>
|
||||
{:else}
|
||||
@@ -138,82 +160,163 @@
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<!-- Identity Section -->
|
||||
<fieldset class="space-y-4">
|
||||
<legend class="text-sm font-bold uppercase tracking-widest opacity-60">Identity & Branding</legend>
|
||||
<legend
|
||||
class="text-sm font-bold uppercase tracking-widest opacity-60"
|
||||
>Identity & Branding</legend>
|
||||
|
||||
<div class="space-y-1">
|
||||
<label class="label text-xs font-bold opacity-75" for="contact-title">Title / Name</label>
|
||||
<input class="input preset-filled-surface rounded-lg placeholder-surface-400 p-2" id="contact-title" type="text" bind:value={formData.title} required placeholder="Business Office" />
|
||||
<label
|
||||
class="label text-xs font-bold opacity-75"
|
||||
for="contact-title">Title / Name</label>
|
||||
<input
|
||||
class="input preset-filled-surface rounded-lg placeholder-surface-400 p-2"
|
||||
id="contact-title"
|
||||
type="text"
|
||||
bind:value={formData.title}
|
||||
required
|
||||
placeholder="Business Office" />
|
||||
</div>
|
||||
|
||||
<div class="space-y-1">
|
||||
<label class="label text-xs font-bold opacity-75" for="contact-tagline">Tagline</label>
|
||||
<input class="input preset-filled-surface rounded-lg placeholder-surface-400 p-2" id="contact-tagline" type="text" bind:value={formData.tagline} placeholder="Primary contact for business inquiries" />
|
||||
<label
|
||||
class="label text-xs font-bold opacity-75"
|
||||
for="contact-tagline">Tagline</label>
|
||||
<input
|
||||
class="input preset-filled-surface rounded-lg placeholder-surface-400 p-2"
|
||||
id="contact-tagline"
|
||||
type="text"
|
||||
bind:value={formData.tagline}
|
||||
placeholder="Primary contact for business inquiries" />
|
||||
</div>
|
||||
|
||||
<div class="space-y-1">
|
||||
<label class="label text-xs font-bold opacity-75" for="contact-email">Email Address</label>
|
||||
<div class="input-group input-group-divider grid-cols-[auto_1fr] preset-filled-surface rounded-lg overflow-hidden">
|
||||
<label
|
||||
class="label text-xs font-bold opacity-75"
|
||||
for="contact-email">Email Address</label>
|
||||
<div
|
||||
class="input-group input-group-divider grid-cols-[auto_1fr] preset-filled-surface rounded-lg overflow-hidden">
|
||||
<div class="input-group-shim"><Mail size={16} /></div>
|
||||
<input class="bg-transparent border-0 ring-0 focus:ring-0 placeholder-surface-400 p-2" id="contact-email" type="email" bind:value={formData.email} placeholder="contact@example.com" />
|
||||
<input
|
||||
class="bg-transparent border-0 ring-0 focus:ring-0 placeholder-surface-400 p-2"
|
||||
id="contact-email"
|
||||
type="email"
|
||||
bind:value={formData.email}
|
||||
placeholder="contact@example.com" />
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<!-- Communication Section -->
|
||||
<fieldset class="space-y-4">
|
||||
<legend class="text-sm font-bold uppercase tracking-widest opacity-60">Phone & Web</legend>
|
||||
<legend
|
||||
class="text-sm font-bold uppercase tracking-widest opacity-60"
|
||||
>Phone & Web</legend>
|
||||
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<div class="space-y-1">
|
||||
<label class="label text-xs font-bold opacity-75" for="contact-phone-mobile">Mobile Phone</label>
|
||||
<div class="input-group input-group-divider grid-cols-[auto_1fr] preset-filled-surface rounded-lg overflow-hidden">
|
||||
<label
|
||||
class="label text-xs font-bold opacity-75"
|
||||
for="contact-phone-mobile">Mobile Phone</label>
|
||||
<div
|
||||
class="input-group input-group-divider grid-cols-[auto_1fr] preset-filled-surface rounded-lg overflow-hidden">
|
||||
<div class="input-group-shim"><Phone size={16} /></div>
|
||||
<input class="bg-transparent border-0 ring-0 focus:ring-0 placeholder-surface-400 p-2" id="contact-phone-mobile" type="tel" bind:value={formData.phone_mobile} placeholder="+1..." />
|
||||
<input
|
||||
class="bg-transparent border-0 ring-0 focus:ring-0 placeholder-surface-400 p-2"
|
||||
id="contact-phone-mobile"
|
||||
type="tel"
|
||||
bind:value={formData.phone_mobile}
|
||||
placeholder="+1..." />
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<label class="label text-xs font-bold opacity-75" for="contact-phone-office">Office Phone</label>
|
||||
<div class="input-group input-group-divider grid-cols-[auto_1fr] preset-filled-surface rounded-lg overflow-hidden">
|
||||
<label
|
||||
class="label text-xs font-bold opacity-75"
|
||||
for="contact-phone-office">Office Phone</label>
|
||||
<div
|
||||
class="input-group input-group-divider grid-cols-[auto_1fr] preset-filled-surface rounded-lg overflow-hidden">
|
||||
<div class="input-group-shim"><Phone size={16} /></div>
|
||||
<input class="bg-transparent border-0 ring-0 focus:ring-0 placeholder-surface-400 p-2" id="contact-phone-office" type="tel" bind:value={formData.phone_office} placeholder="+1..." />
|
||||
<input
|
||||
class="bg-transparent border-0 ring-0 focus:ring-0 placeholder-surface-400 p-2"
|
||||
id="contact-phone-office"
|
||||
type="tel"
|
||||
bind:value={formData.phone_office}
|
||||
placeholder="+1..." />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-1">
|
||||
<label class="label text-xs font-bold opacity-75" for="contact-website">Website URL</label>
|
||||
<div class="input-group input-group-divider grid-cols-[auto_1fr] preset-filled-surface rounded-lg overflow-hidden">
|
||||
<label
|
||||
class="label text-xs font-bold opacity-75"
|
||||
for="contact-website">Website URL</label>
|
||||
<div
|
||||
class="input-group input-group-divider grid-cols-[auto_1fr] preset-filled-surface rounded-lg overflow-hidden">
|
||||
<div class="input-group-shim"><Globe size={16} /></div>
|
||||
<input class="bg-transparent border-0 ring-0 focus:ring-0 placeholder-surface-400 p-2" id="contact-website" type="url" bind:value={formData.website_url} placeholder="https://..." />
|
||||
<input
|
||||
class="bg-transparent border-0 ring-0 focus:ring-0 placeholder-surface-400 p-2"
|
||||
id="contact-website"
|
||||
type="url"
|
||||
bind:value={formData.website_url}
|
||||
placeholder="https://..." />
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<!-- Social Media Section -->
|
||||
<fieldset class="space-y-4">
|
||||
<legend class="text-sm font-bold uppercase tracking-widest opacity-60">Social Media</legend>
|
||||
<legend
|
||||
class="text-sm font-bold uppercase tracking-widest opacity-60"
|
||||
>Social Media</legend>
|
||||
|
||||
<div class="space-y-1">
|
||||
<label class="label text-xs font-bold opacity-75" for="contact-linkedin">LinkedIn URL</label>
|
||||
<div class="input-group input-group-divider grid-cols-[auto_1fr] preset-filled-surface rounded-lg overflow-hidden">
|
||||
<label
|
||||
class="label text-xs font-bold opacity-75"
|
||||
for="contact-linkedin">LinkedIn URL</label>
|
||||
<div
|
||||
class="input-group input-group-divider grid-cols-[auto_1fr] preset-filled-surface rounded-lg overflow-hidden">
|
||||
<div class="input-group-shim"><Linkedin size={16} /></div>
|
||||
<input class="bg-transparent border-0 ring-0 focus:ring-0 placeholder-surface-400 p-2" id="contact-linkedin" type="url" bind:value={formData.linkedin_url} placeholder="https://linkedin.com/in/..." />
|
||||
<input
|
||||
class="bg-transparent border-0 ring-0 focus:ring-0 placeholder-surface-400 p-2"
|
||||
id="contact-linkedin"
|
||||
type="url"
|
||||
bind:value={formData.linkedin_url}
|
||||
placeholder="https://linkedin.com/in/..." />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<div class="space-y-1">
|
||||
<label class="label text-xs font-bold opacity-75" for="contact-facebook">Facebook URL</label>
|
||||
<div class="input-group input-group-divider grid-cols-[auto_1fr] preset-filled-surface rounded-lg overflow-hidden">
|
||||
<div class="input-group-shim"><Facebook size={16} /></div>
|
||||
<input class="bg-transparent border-0 ring-0 focus:ring-0 placeholder-surface-400 p-2" id="contact-facebook" type="url" bind:value={formData.facebook_url} placeholder="https://facebook.com/..." />
|
||||
<label
|
||||
class="label text-xs font-bold opacity-75"
|
||||
for="contact-facebook">Facebook URL</label>
|
||||
<div
|
||||
class="input-group input-group-divider grid-cols-[auto_1fr] preset-filled-surface rounded-lg overflow-hidden">
|
||||
<div class="input-group-shim">
|
||||
<Facebook size={16} />
|
||||
</div>
|
||||
<input
|
||||
class="bg-transparent border-0 ring-0 focus:ring-0 placeholder-surface-400 p-2"
|
||||
id="contact-facebook"
|
||||
type="url"
|
||||
bind:value={formData.facebook_url}
|
||||
placeholder="https://facebook.com/..." />
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<label class="label text-xs font-bold opacity-75" for="contact-instagram">Instagram URL</label>
|
||||
<div class="input-group input-group-divider grid-cols-[auto_1fr] preset-filled-surface rounded-lg overflow-hidden">
|
||||
<div class="input-group-shim"><Instagram size={16} /></div>
|
||||
<input class="bg-transparent border-0 ring-0 focus:ring-0 placeholder-surface-400 p-2" id="contact-instagram" type="url" bind:value={formData.instagram_url} placeholder="https://instagram.com/..." />
|
||||
<label
|
||||
class="label text-xs font-bold opacity-75"
|
||||
for="contact-instagram">Instagram URL</label>
|
||||
<div
|
||||
class="input-group input-group-divider grid-cols-[auto_1fr] preset-filled-surface rounded-lg overflow-hidden">
|
||||
<div class="input-group-shim">
|
||||
<Instagram size={16} />
|
||||
</div>
|
||||
<input
|
||||
class="bg-transparent border-0 ring-0 focus:ring-0 placeholder-surface-400 p-2"
|
||||
id="contact-instagram"
|
||||
type="url"
|
||||
bind:value={formData.instagram_url}
|
||||
placeholder="https://instagram.com/..." />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -221,32 +324,53 @@
|
||||
|
||||
<!-- Status Section -->
|
||||
<fieldset class="space-y-4">
|
||||
<legend class="text-sm font-bold uppercase tracking-widest opacity-60">Status</legend>
|
||||
<legend
|
||||
class="text-sm font-bold uppercase tracking-widest opacity-60"
|
||||
>Status</legend>
|
||||
|
||||
<div class="flex flex-wrap gap-4 pt-2">
|
||||
<label class="flex items-center space-x-2 cursor-pointer">
|
||||
<input class="checkbox" type="checkbox" bind:checked={formData.enable} />
|
||||
<input
|
||||
class="checkbox"
|
||||
type="checkbox"
|
||||
bind:checked={formData.enable} />
|
||||
<span class="text-sm font-medium">Enabled</span>
|
||||
</label>
|
||||
<label class="flex items-center space-x-2 cursor-pointer">
|
||||
<input class="checkbox" type="checkbox" bind:checked={formData.hide} />
|
||||
<input
|
||||
class="checkbox"
|
||||
type="checkbox"
|
||||
bind:checked={formData.hide} />
|
||||
<span class="text-sm font-medium">Hidden</span>
|
||||
</label>
|
||||
<label class="flex items-center space-x-2 cursor-pointer">
|
||||
<input class="checkbox" type="checkbox" bind:checked={formData.priority} />
|
||||
<input
|
||||
class="checkbox"
|
||||
type="checkbox"
|
||||
bind:checked={formData.priority} />
|
||||
<span class="text-sm font-medium">Priority</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="space-y-1">
|
||||
<label class="label text-xs font-bold opacity-75" for="contact-notes">Internal Notes</label>
|
||||
<textarea class="textarea preset-filled-surface rounded-lg placeholder-surface-400 p-2" id="contact-notes" rows="2" bind:value={formData.notes} placeholder="Additional details..."></textarea>
|
||||
<label
|
||||
class="label text-xs font-bold opacity-75"
|
||||
for="contact-notes">Internal Notes</label>
|
||||
<textarea
|
||||
class="textarea preset-filled-surface rounded-lg placeholder-surface-400 p-2"
|
||||
id="contact-notes"
|
||||
rows="2"
|
||||
bind:value={formData.notes}
|
||||
placeholder="Additional details..."></textarea>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<footer class="flex justify-end gap-2 border-t border-surface-500/30 pt-4">
|
||||
<button type="submit" class="btn preset-filled-primary font-bold shadow-lg w-full md:w-auto" disabled={is_loading}>
|
||||
<button
|
||||
type="submit"
|
||||
class="btn preset-filled-primary font-bold shadow-lg w-full md:w-auto"
|
||||
disabled={is_loading}>
|
||||
{#if is_loading}
|
||||
<span class="animate-spin mr-2">⏳</span>
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user