Files
optima/src/components/NewUnifiSiteModal.svelte
T
2026-02-22 19:12:13 -06:00

304 lines
6.9 KiB
Svelte

<script lang="ts">
import { unifi } from "$lib/optima-api/modules/unifi";
export let isOpen = false;
export let accessToken: string;
export let companyId: string;
export let onSuccess: () => void = () => {};
let siteName = "";
let isSubmitting = false;
let submitError = "";
function reset() {
siteName = "";
isSubmitting = false;
submitError = "";
}
function close() {
isOpen = false;
reset();
}
async function handleSubmit() {
if (!siteName.trim() || !accessToken) return;
isSubmitting = true;
submitError = "";
try {
// Create the site on the UniFi controller
const result = await unifi.createSite(accessToken, siteName.trim());
const newSiteId = result?.data?.id;
if (newSiteId && companyId) {
// Link it to this company
await unifi.linkSite(accessToken, newSiteId, companyId);
}
close();
onSuccess();
} catch (err) {
submitError =
err instanceof Error ? err.message : "Failed to create UniFi site";
console.error("Failed to create UniFi site:", err);
} finally {
isSubmitting = false;
}
}
</script>
{#if isOpen}
<!-- svelte-ignore a11y_no_static_element_interactions -->
<!-- svelte-ignore a11y_click_events_have_key_events -->
<div class="modal-overlay" on:click={close}>
<!-- svelte-ignore a11y_no_static_element_interactions -->
<!-- svelte-ignore a11y_click_events_have_key_events -->
<div class="modal-container" on:click|stopPropagation>
<div class="modal-header">
<h3 class="modal-title">
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
width="18"
height="18"
>
<path d="M12 2L2 7l10 5 10-5-10-5z" />
<path d="M2 17l10 5 10-5" />
<path d="M2 12l10 5 10-5" />
</svg>
New UniFi Site
</h3>
<button
class="modal-close"
on:click={close}
type="button"
aria-label="Close"
>
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
width="16"
height="16"
>
<path d="M18 6L6 18M6 6l12 12" />
</svg>
</button>
</div>
<div class="modal-body">
<div class="modal-field">
<label class="modal-label" for="unifi-site-name">Site Name</label>
<input
id="unifi-site-name"
class="modal-input"
type="text"
bind:value={siteName}
placeholder="e.g. Main Office"
disabled={isSubmitting}
/>
</div>
{#if submitError}
<div class="modal-error">
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
width="14"
height="14"
>
<circle cx="12" cy="12" r="10" /><line
x1="12"
y1="8"
x2="12"
y2="12"
/><line x1="12" y1="16" x2="12.01" y2="16" />
</svg>
{submitError}
</div>
{/if}
</div>
<div class="modal-footer">
<button
class="modal-btn modal-btn-cancel"
on:click={close}
type="button"
disabled={isSubmitting}
>
Cancel
</button>
<button
class="modal-btn modal-btn-primary"
on:click={handleSubmit}
type="button"
disabled={isSubmitting || !siteName.trim()}
>
{#if isSubmitting}
Creating…
{:else}
Create Site
{/if}
</button>
</div>
</div>
</div>
{/if}
<style>
.modal-overlay {
position: fixed;
inset: 0;
z-index: 1000;
display: flex;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(4px);
}
.modal-container {
width: 420px;
max-width: 90vw;
background: var(--bg-surface);
border: 1px solid var(--border-subtle);
border-radius: 12px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
overflow: hidden;
}
.modal-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 20px;
border-bottom: 1px solid var(--border-subtle);
}
.modal-title {
display: flex;
align-items: center;
gap: 8px;
margin: 0;
font-size: 15px;
font-weight: 600;
color: var(--text-primary);
}
.modal-close {
display: flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
border: none;
border-radius: 6px;
background: none;
color: var(--text-secondary);
cursor: pointer;
transition: all 0.15s;
}
.modal-close:hover {
background: var(--nav-hover-bg);
color: var(--text-primary);
}
.modal-body {
padding: 20px;
display: flex;
flex-direction: column;
gap: 16px;
}
.modal-field {
display: flex;
flex-direction: column;
gap: 6px;
}
.modal-label {
font-size: 12px;
font-weight: 600;
color: var(--text-secondary);
text-transform: uppercase;
letter-spacing: 0.03em;
}
.modal-input {
padding: 8px 12px;
font-size: 13px;
border: 1px solid var(--border-default);
border-radius: 6px;
background: var(--bg-base);
color: var(--text-primary);
outline: none;
transition: border-color 0.15s;
}
.modal-input:focus {
border-color: var(--input-focus-border);
box-shadow: 0 0 0 2px
color-mix(in srgb, var(--input-focus-border) 15%, transparent);
}
.modal-input:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.modal-error {
display: flex;
align-items: center;
gap: 6px;
padding: 8px 12px;
border-radius: 6px;
background: color-mix(in srgb, var(--status-error) 10%, transparent);
color: var(--status-error);
font-size: 12px;
font-weight: 500;
}
.modal-footer {
display: flex;
justify-content: flex-end;
gap: 8px;
padding: 16px 20px;
border-top: 1px solid var(--border-subtle);
}
.modal-btn {
padding: 7px 16px;
font-size: 12px;
font-weight: 500;
border-radius: 6px;
cursor: pointer;
transition: all 0.15s;
}
.modal-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.modal-btn-cancel {
background: var(--bg-surface);
color: var(--text-secondary);
border: 1px solid var(--border-default);
}
.modal-btn-cancel:hover:not(:disabled) {
background: var(--nav-hover-bg);
}
.modal-btn-primary {
background: var(--accent-color, #0066cc);
color: #fff;
border: none;
}
.modal-btn-primary:hover:not(:disabled) {
filter: brightness(1.12);
}
</style>