Compare commits
15 Commits
1e2c9d9b74
...
83e271a323
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
83e271a323 | ||
|
|
ace90ad043 | ||
|
|
d139ed1bd0 | ||
|
|
3d988222a1 | ||
|
|
5433a906bb | ||
|
|
d89218be15 | ||
|
|
a8e9bd6694 | ||
|
|
6cd3b5f8f9 | ||
|
|
b33c1b16f6 | ||
|
|
d7a0857bed | ||
|
|
6939c058d8 | ||
|
|
b88a7de358 | ||
|
|
27f0bd21fb | ||
|
|
f111670f60 | ||
|
|
045efa71e1 |
@@ -547,24 +547,30 @@ ae_loc.idaa_loc = { novi_uuid: 'test-uuid-value', ... };
|
|||||||
|
|
||||||
## IDAA Novi Groups and Moderators
|
## IDAA Novi Groups and Moderators
|
||||||
|
|
||||||
### IDAA Couples Meeting = "e9e162f0-3d03-4241-9682-340135ec3fb8"
|
### "IDAA Association Admins Group" = "409e91dc-f5a3-486c-a964-71b7d19e6841"
|
||||||
|
|
||||||
"Gregory X Boehm" "00ee764c-7559-496b-9d18-40d3e9092c0c"
|
* Scott
|
||||||
"Kee B. PARK" "24ab3297-bfce-473c-9311-4b31e3a8974f"
|
* Michelle
|
||||||
"Laura Lander" "ac697456-61fe-4f7d-a8b8-d04866032320"
|
* Brie
|
||||||
"Nancy J Duff-Boehm" "5c7c09bc-4f23-432c-bfd9-87a66b548502"
|
|
||||||
"Owen Lander" "9671a2c4-ff95-48c2-bcde-5c6eba95cded"
|
### "IDAA Couples Meeting" = "e9e162f0-3d03-4241-9682-340135ec3fb8"
|
||||||
"Susan Park" "4a9f94c5-d766-4808-ab76-117c9e43903a"
|
|
||||||
|
* "Gregory X Boehm" "00ee764c-7559-496b-9d18-40d3e9092c0c"
|
||||||
|
* "Kee B. PARK" "24ab3297-bfce-473c-9311-4b31e3a8974f"
|
||||||
|
* "Laura Lander" "ac697456-61fe-4f7d-a8b8-d04866032320"
|
||||||
|
* "Nancy J Duff-Boehm" "5c7c09bc-4f23-432c-bfd9-87a66b548502"
|
||||||
|
* "Owen Lander" "9671a2c4-ff95-48c2-bcde-5c6eba95cded"
|
||||||
|
* "Susan Park" "4a9f94c5-d766-4808-ab76-117c9e43903a"
|
||||||
|
|
||||||
### "Student/Resident Meeting Moderators" "d76d2c00-962d-40f6-a2e8-ed9c85594d96"
|
### "Student/Resident Meeting Moderators" "d76d2c00-962d-40f6-a2e8-ed9c85594d96"
|
||||||
|
|
||||||
"Melissa Eve Valasky" "182d1db3-caa9-41bc-b04a-2facc6859aeb"
|
* "Melissa Eve Valasky" "182d1db3-caa9-41bc-b04a-2facc6859aeb"
|
||||||
"Steven L. Klein" "5724aad7-6d89-47e7-8943-966fd22911bd"
|
* "Steven L. Klein" "5724aad7-6d89-47e7-8943-966fd22911bd"
|
||||||
|
|
||||||
### "IDAA BIPOC Meeting" "873d3ad0-2605-4ccf-824c-638c16b2b9cf"
|
### "IDAA BIPOC Meeting" "873d3ad0-2605-4ccf-824c-638c16b2b9cf"
|
||||||
|
|
||||||
"Paula Lynn Bailey-Walton" "68383ba2-0989-4860-9ea6-073f9698df67"
|
* "Paula Lynn Bailey-Walton" "68383ba2-0989-4860-9ea6-073f9698df67"
|
||||||
"Tasha Hudson" "03d5408c-3c13-4c3a-a93f-49871f9050b1"
|
* "Tasha Hudson" "03d5408c-3c13-4c3a-a93f-49871f9050b1"
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -260,3 +260,10 @@ Guard in [ae_tab__manage.svelte](src/routes/events/[event_id]/(leads)/leads/exhi
|
|||||||
- Export endpoint: `GET /v3/action/event_exhibit/{id}/tracking_export` — requires `leads_api_access`
|
- Export endpoint: `GET /v3/action/event_exhibit/{id}/tracking_export` — requires `leads_api_access`
|
||||||
- Custom questions are stored per-exhibit in `leads_custom_questions_json` (not global)
|
- Custom questions are stored per-exhibit in `leads_custom_questions_json` (not global)
|
||||||
- The exhibitor landing page link format: `/events/[event_id]/leads/exhibit/[exhibit_exhibit_id]/`
|
- The exhibitor landing page link format: `/events/[event_id]/leads/exhibit/[exhibit_exhibit_id]/`
|
||||||
|
|
||||||
|
|
||||||
|
## Old Files for Reference
|
||||||
|
|
||||||
|
@backups/legacy/events_leads_v2/exhibit/[slug]/+page.svelte
|
||||||
|
@backups/legacy/events_leads_v2/exhibit/[slug]/leads_manage.svelte
|
||||||
|
@backups/legacy/events_leads_v2/exhibit/[slug]/leads_payment.svelte
|
||||||
@@ -32,29 +32,37 @@ frontend pass the location directly in the Launcher URL without the extra lookup
|
|||||||
### [Svelte] State reference warnings
|
### [Svelte] State reference warnings
|
||||||
- [x] **`svelte-check` fully clean — 0 errors, 0 warnings.** All 42 `state_referenced_locally` warnings fixed (2026-03-11). CSS `@apply`/`@reference` warnings in `ae_idaa_comp__event_obj_id_edit.svelte` also resolved — Tailwind utilities inlined, `<style>` block removed. (2026-03-16)
|
- [x] **`svelte-check` fully clean — 0 errors, 0 warnings.** All 42 `state_referenced_locally` warnings fixed (2026-03-11). CSS `@apply`/`@reference` warnings in `ae_idaa_comp__event_obj_id_edit.svelte` also resolved — Tailwind utilities inlined, `<style>` block removed. (2026-03-16)
|
||||||
|
|
||||||
|
### [TypeScript] svelte-check hidden errors — discovered 2026-03-27
|
||||||
|
**HOW WE FOUND THIS:** The `@lucide/svelte` 0.577.0 update (2026-03-10) dropped `class` from
|
||||||
|
`IconProps`. Fixing it required a `declare module '@lucide/svelte'` augmentation. That
|
||||||
|
augmentation was mistakenly placed in `app.d.ts`, which is a *script-context* declaration file
|
||||||
|
(no `export {}`). In that context, `declare module` is an **ambient replacement**, not a merge —
|
||||||
|
it wiped all icon exports from svelte-check's view, surfacing 1368 previously hidden errors.
|
||||||
|
Once moved to `src/lucide-augment.d.ts` (a proper module file with `export {}`), the masking
|
||||||
|
lifted and the real pre-existing errors became visible.
|
||||||
|
|
||||||
|
**Lesson:** A broken ambient declaration can silently hide unrelated errors. If svelte-check
|
||||||
|
suddenly jumps to 0 errors, verify it's not because a bad `.d.ts` replaced a package's types.
|
||||||
|
|
||||||
|
**Current state (2026-03-27):** 31 errors, 0 warnings — all `ModalProps.children`.
|
||||||
|
|
||||||
|
- [ ] **[flowbite-svelte] `ModalProps.children` — 31 errors across 26 files.** The flowbite-svelte
|
||||||
|
`Modal` component API changed; `children` is no longer a direct prop (now Svelte snippet-based).
|
||||||
|
Affected files span journals, pres_mgmt, events/settings, and IDAA archives.
|
||||||
|
Run `npx svelte-check 2>&1 | grep ModalProps` to get the current list.
|
||||||
|
Fix pattern: replace `children` prop binding with Svelte snippet syntax per flowbite-svelte docs.
|
||||||
|
|
||||||
|
- [ ] **[package.json] Remove orphaned ShadCN/bits-ui packages.** `shadcn-svelte` and `bits-ui`
|
||||||
|
remain in `package.json` but have no usages — `src/lib/components/ui/` was removed 2026-03-27
|
||||||
|
(trashed to `~/tmp/gemini_trash/shadcn_components_ui_2026-03-27`). Safe to remove both packages
|
||||||
|
from `dependencies` when convenient.
|
||||||
|
|
||||||
### [Badges] Remaining badge work before first live event
|
### [Badges] Remaining badge work before first live event
|
||||||
- **Badge print controls UX polish:** Scott has improvements in mind — TBD next session.
|
- **Badge print controls UX polish:** Scott has improvements in mind — TBD next session.
|
||||||
File: `ae_comp__badge_print_controls.svelte`.
|
File: `ae_comp__badge_print_controls.svelte`.
|
||||||
|
|
||||||
### [Badges] Zebra ZC10L Hardware Testing — ~week of 2026-03-16
|
### [Badges] Zebra ZC10L Hardware Testing
|
||||||
Scott is renting a Zebra ZC10L for one day to do real-world badge printing tests before
|
- [x] **Hardware test day complete.** Real-world badge printing tested on Zebra ZC10L. Driver installed, test data set up, print verified. (2026-03-27)
|
||||||
Axonius (mid-April). See `documentation/PROJECT__AE_Events_Zebra_Hardware_Test_Day.md`
|
|
||||||
for the full checklist and prep plan.
|
|
||||||
|
|
||||||
**Pre-test work (do before printer arrives):**
|
|
||||||
- [x] **Debug outlines are gated** — `print/+page.svelte` prints nothing; outlines live in
|
|
||||||
`static/ae-print-badge.css` behind `html.debug_outlines` class (toggled by the "Show debug
|
|
||||||
outlines" checkbox in the controls panel, trusted-only). Won't appear in print unless explicitly
|
|
||||||
enabled. No action needed. (verified 2026-03-18)
|
|
||||||
- [x] **Zebra ZC10L Linux driver** — installed CUPS driver; verified card prints. (2026-03-27)
|
|
||||||
- [x] **`style_href` wired** — `print/+page.svelte` already loads `style_href` via `<svelte:head>`
|
|
||||||
and it's in `properties_to_save`. (verified 2026-03-18)
|
|
||||||
- [x] **`duplex=0` hides badge back** — `duplex` is in `properties_to_save`; v2 badge render
|
|
||||||
gates `{#if show_badge_back}` on `duplex != null && !!duplex`. Set `duplex=0` on the template
|
|
||||||
to suppress the back section for single-sided PVC. (verified 2026-03-18)
|
|
||||||
- [x] **Set up test event + PVC template** in dev DB with `layout: badge_3.5x5.5_pvc`,
|
|
||||||
`duplex=0`, badge records with varied name lengths, HTML in fields, different badge_type_codes,
|
|
||||||
edge cases (very long name, HTML markup, no affiliations, all ticket/option codes). (2026-03-27)
|
|
||||||
|
|
||||||
### [Leads] Exhibitor Lead Scanning — IN PROGRESS (demo-ready prep)
|
### [Leads] Exhibitor Lead Scanning — IN PROGRESS (demo-ready prep)
|
||||||
Module is substantially built as a PWA (no Electron). Core flow works end-to-end.
|
Module is substantially built as a PWA (no Electron). Core flow works end-to-end.
|
||||||
@@ -65,7 +73,7 @@ Full audit: `src/routes/events/[event_id]/(leads)/` and `src/lib/ae_events/ae_ev
|
|||||||
- Exhibit search/landing (`/leads/`) — SWR, local + API search, sort
|
- Exhibit search/landing (`/leads/`) — SWR, local + API search, sort
|
||||||
- Exhibit detail page — 4-tab layout, sticky header with Add/List toggle, auto-refresh timer
|
- Exhibit detail page — 4-tab layout, sticky header with Add/List toggle, auto-refresh timer
|
||||||
- Tab 1 (Start): sign-in via shared passcode OR licensed user (email + passcode)
|
- Tab 1 (Start): sign-in via shared passcode OR licensed user (email + passcode)
|
||||||
- Tab 2 (Add): QR scan (rapid vs. qualify mode) + manual badge search; duplicate detection on both
|
- Tab 2 (Add): QR scan (confirm mode — replaced rapid/qualify) + manual badge search; duplicate/re-enable detection on both
|
||||||
- Tab 3 (List): SWR lead list, licensee filter (All / My Leads), sort options, export button
|
- Tab 3 (List): SWR lead list, licensee filter (All / My Leads), sort options, export button
|
||||||
- Tab 4 (Manage): admin tools, booth profile edit, passcode, license mgmt, custom questions config, app settings (refresh interval, clear IDB/localStorage, reload)
|
- Tab 4 (Manage): admin tools, booth profile edit, passcode, license mgmt, custom questions config, app settings (refresh interval, clear IDB/localStorage, reload)
|
||||||
- Lead detail page: view/edit custom question responses, exhibitor notes (TipTap), priority/enable flags
|
- Lead detail page: view/edit custom question responses, exhibitor notes (TipTap), priority/enable flags
|
||||||
@@ -81,8 +89,12 @@ Full audit: `src/routes/events/[event_id]/(leads)/` and `src/lib/ae_events/ae_ev
|
|||||||
Opt-in model: `allow_tracking` must be explicitly `true` on the badge. Also added `allow_tracking`
|
Opt-in model: `allow_tracking` must be explicitly `true` on the badge. Also added `allow_tracking`
|
||||||
and `agree_to_tc` to `ae_EventBadge` in `ae_types.ts`.
|
and `agree_to_tc` to `ae_EventBadge` in `ae_types.ts`.
|
||||||
**Demo note:** ensure test badges have `allow_tracking = true` or no one can be added.
|
**Demo note:** ensure test badges have `allow_tracking = true` or no one can be added.
|
||||||
- [ ] **Payment component** — `ae_comp__exhibit_payment.svelte` is a stub (Stripe placeholder only);
|
- [x] **Payment component** — `ae_comp__exhibit_payment.svelte` fully implemented (2026-03-27).
|
||||||
omit from demo or hide the payment tab via "Show Payment Tab" toggle in Manage settings
|
Reads Stripe config from `$ae_loc.site_cfg_json` (`stripe_publishable_key`, `stripe_btn_1/3/6/10_license`).
|
||||||
|
License tier selector (1/3/6/10 users) with `{#key}` remount pattern for Stripe web component.
|
||||||
|
3 states: paid confirmation (priority=true), admin setup hint / "contact organizer" (no Stripe config),
|
||||||
|
payment form. `client_reference_id=exhibit_id`. TypeScript declaration in `app.d.ts`.
|
||||||
|
Stripe keys verified visible in `$ae_loc.site_cfg_json` on dev/demo site. Keys need validity check in Stripe dashboard.
|
||||||
- [ ] **End-to-end smoke test** — sign in with shared passcode, scan/search a badge, add a lead,
|
- [ ] **End-to-end smoke test** — sign in with shared passcode, scan/search a badge, add a lead,
|
||||||
view detail, add notes/responses, export CSV; verify on mobile (Chrome/Safari PWA)
|
view detail, add notes/responses, export CSV; verify on mobile (Chrome/Safari PWA)
|
||||||
- [x] **Install prompt** — PWA install nudge implemented (2026-03-16). `pwa_install.svelte.ts`
|
- [x] **Install prompt** — PWA install nudge implemented (2026-03-16). `pwa_install.svelte.ts`
|
||||||
@@ -98,7 +110,7 @@ Full audit: `src/routes/events/[event_id]/(leads)/` and `src/lib/ae_events/ae_ev
|
|||||||
- [x] **Remote deploy script:** `aether_container_env/deploy.sh` — SSH-triggered from workstation via `npm run deploy:remote:test/prod`. Handles git pull (ff-only) + docker build + restart. Tested and working on test env. (2026-03-25)
|
- [x] **Remote deploy script:** `aether_container_env/deploy.sh` — SSH-triggered from workstation via `npm run deploy:remote:test/prod`. Handles git pull (ff-only) + docker build + restart. Tested and working on test env. (2026-03-25)
|
||||||
- [x] **`.env.default` cleanup:** Removed 16 dead variables, added missing `AE_NETWORK_NAME`/`CONTAINER_DOZZLE`/`AE_DOZZLE_PORT`, parameterized all container names (`CONTAINER_MARIADB`, `CONTAINER_PMA`, `CONTAINER_AE_OPS`) with `:-default` fallbacks in compose. ("Dozzle" = log viewer container.) (2026-03-26)
|
- [x] **`.env.default` cleanup:** Removed 16 dead variables, added missing `AE_NETWORK_NAME`/`CONTAINER_DOZZLE`/`AE_DOZZLE_PORT`, parameterized all container names (`CONTAINER_MARIADB`, `CONTAINER_PMA`, `CONTAINER_AE_OPS`) with `:-default` fallbacks in compose. ("Dozzle" = log viewer container.) (2026-03-26)
|
||||||
- [ ] **Prod deploy:** Run `npm run deploy:remote:prod` (off-peak). Prerequisites: both repos pushed to Bitbucket ✓; verify `.env.prod` exists in `/srv/apps/prod_aether_app_sveltekit/` on Linode before running.
|
- [ ] **Prod deploy:** Run `npm run deploy:remote:prod` (off-peak). Prerequisites: both repos pushed to Bitbucket ✓; verify `.env.prod` exists in `/srv/apps/prod_aether_app_sveltekit/` on Linode before running.
|
||||||
- [ ] **Bitbucket → API token migration:** Bitbucket is deprecating app passwords — creation disabled 2025-09-09, existing passwords expire 2026-06-09. Migrate git remotes on workstation + Linode to use API tokens before then. See [Bitbucket API tokens docs](https://support.atlassian.com/bitbucket-cloud/docs/api-tokens/).
|
- [x] **Bitbucket → SSH migration:** Switched all three repos (`aether_app_sveltekit`, `aether_container_env`, `aether_api_fastapi`) to SSH remotes (`git@bitbucket.org`) on workstation. App passwords deprecated — SSH unaffected. (2026-03-27)
|
||||||
- [ ] **Branch strategy cleanup:** All environments (test, prod, bak) currently pull from same branches. `deploy.sh` defaults are `ae_app_3x_llm` / `development` — acceptable for now but should establish proper branch separation (e.g. `main`/`master` for prod).
|
- [ ] **Branch strategy cleanup:** All environments (test, prod, bak) currently pull from same branches. `deploy.sh` defaults are `ae_app_3x_llm` / `development` — acceptable for now but should establish proper branch separation (e.g. `main`/`master` for prod).
|
||||||
- [ ] **Tier 2 deploy (Gitea webhook):** Push-triggered deploys via Gitea webhook → listener on Linode → `deploy.sh`. Deferred until Gitea usage is more established.
|
- [ ] **Tier 2 deploy (Gitea webhook):** Push-triggered deploys via Gitea webhook → listener on Linode → `deploy.sh`. Deferred until Gitea usage is more established.
|
||||||
|
|
||||||
|
|||||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "osit-aether-app-svelte",
|
"name": "osit-aether-app-svelte",
|
||||||
"version": "3.00.05",
|
"version": "3.00.07",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "osit-aether-app-svelte",
|
"name": "osit-aether-app-svelte",
|
||||||
"version": "3.00.05",
|
"version": "3.00.07",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/autocomplete": "^6.20.0",
|
"@codemirror/autocomplete": "^6.20.0",
|
||||||
"@codemirror/commands": "^6.10.0",
|
"@codemirror/commands": "^6.10.0",
|
||||||
|
|||||||
12
src/app.d.ts
vendored
12
src/app.d.ts
vendored
@@ -22,3 +22,15 @@ declare global {
|
|||||||
// eslint-disable-next-line no-var
|
// eslint-disable-next-line no-var
|
||||||
var native_app: any;
|
var native_app: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Stripe Buy Button web component — needed so Svelte templates accept the element without TS errors.
|
||||||
|
declare module 'svelte/elements' {
|
||||||
|
interface IntrinsicElements {
|
||||||
|
'stripe-buy-button': {
|
||||||
|
'buy-button-id': string;
|
||||||
|
'publishable-key': string;
|
||||||
|
'client-reference-id'?: string;
|
||||||
|
[attr: string]: any;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
[Dolphin]
|
|
||||||
Timestamp=2024,12,2,17,34,30.327
|
|
||||||
Version=4
|
|
||||||
ViewMode=1
|
|
||||||
|
|
||||||
[Settings]
|
|
||||||
HiddenFilesShown=true
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
{
|
|
||||||
"rules": {
|
|
||||||
"@typescript-eslint/no-unused-vars": [
|
|
||||||
"warn",
|
|
||||||
{
|
|
||||||
"argsIgnorePattern": "^_",
|
|
||||||
"varsIgnorePattern": "^\\$\\$(Props|Events|Slots|Generic)$"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
<script lang="ts" module>
|
|
||||||
import type { WithElementRef } from 'bits-ui';
|
|
||||||
import type {
|
|
||||||
HTMLAnchorAttributes,
|
|
||||||
HTMLButtonAttributes
|
|
||||||
} from 'svelte/elements';
|
|
||||||
import { type VariantProps, tv } from 'tailwind-variants';
|
|
||||||
|
|
||||||
export const buttonVariants = tv({
|
|
||||||
base: 'ring-offset-background focus-visible:ring-ring inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
|
|
||||||
variants: {
|
|
||||||
variant: {
|
|
||||||
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
|
|
||||||
destructive:
|
|
||||||
'bg-destructive text-destructive-foreground hover:bg-destructive/90',
|
|
||||||
outline:
|
|
||||||
'border-input bg-background hover:bg-accent hover:text-accent-foreground border',
|
|
||||||
secondary:
|
|
||||||
'bg-secondary text-secondary-foreground hover:bg-secondary/80',
|
|
||||||
ghost: 'hover:bg-accent hover:text-accent-foreground',
|
|
||||||
link: 'text-primary underline-offset-4 hover:underline'
|
|
||||||
},
|
|
||||||
size: {
|
|
||||||
default: 'h-10 px-4 py-2',
|
|
||||||
sm: 'h-9 rounded-md px-3',
|
|
||||||
lg: 'h-11 rounded-md px-8',
|
|
||||||
icon: 'h-10 w-10'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
defaultVariants: {
|
|
||||||
variant: 'default',
|
|
||||||
size: 'default'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export type ButtonVariant = VariantProps<typeof buttonVariants>['variant'];
|
|
||||||
export type ButtonSize = VariantProps<typeof buttonVariants>['size'];
|
|
||||||
|
|
||||||
export type ButtonProps = WithElementRef<HTMLButtonAttributes> &
|
|
||||||
WithElementRef<HTMLAnchorAttributes> & {
|
|
||||||
variant?: ButtonVariant;
|
|
||||||
size?: ButtonSize;
|
|
||||||
class?: any;
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { cn } from '$lib/utils/utils.js';
|
|
||||||
|
|
||||||
let {
|
|
||||||
class: className,
|
|
||||||
variant = 'default',
|
|
||||||
size = 'default',
|
|
||||||
ref = $bindable(null),
|
|
||||||
href = undefined,
|
|
||||||
type = 'button',
|
|
||||||
children,
|
|
||||||
...restProps
|
|
||||||
}: ButtonProps = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{#if href}
|
|
||||||
<a
|
|
||||||
bind:this={ref}
|
|
||||||
class={cn(buttonVariants({ variant, size, className }))}
|
|
||||||
{href}
|
|
||||||
{...restProps}>
|
|
||||||
{@render children?.()}
|
|
||||||
</a>
|
|
||||||
{:else}
|
|
||||||
<button
|
|
||||||
bind:this={ref}
|
|
||||||
class={cn(buttonVariants({ variant, size, className }))}
|
|
||||||
{type}
|
|
||||||
{...restProps}>
|
|
||||||
{@render children?.()}
|
|
||||||
</button>
|
|
||||||
{/if}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
import Root, { buttonVariants } from './button.svelte';
|
|
||||||
|
|
||||||
export {
|
|
||||||
Root,
|
|
||||||
|
|
||||||
//
|
|
||||||
Root as Button,
|
|
||||||
buttonVariants
|
|
||||||
};
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import Root, {
|
|
||||||
type ButtonProps,
|
|
||||||
type ButtonSize,
|
|
||||||
type ButtonVariant,
|
|
||||||
buttonVariants
|
|
||||||
} from './button.svelte';
|
|
||||||
|
|
||||||
export {
|
|
||||||
Root,
|
|
||||||
type ButtonProps as Props,
|
|
||||||
//
|
|
||||||
Root as Button,
|
|
||||||
buttonVariants,
|
|
||||||
type ButtonProps,
|
|
||||||
type ButtonSize,
|
|
||||||
type ButtonVariant
|
|
||||||
};
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import {
|
|
||||||
DropdownMenu as DropdownMenuPrimitive,
|
|
||||||
type WithoutChildrenOrChild
|
|
||||||
} from 'bits-ui';
|
|
||||||
import Check from 'lucide-svelte/icons/check';
|
|
||||||
import Minus from 'lucide-svelte/icons/minus';
|
|
||||||
import { cn } from '$lib/utils/utils.js';
|
|
||||||
import type { Snippet } from 'svelte';
|
|
||||||
|
|
||||||
let {
|
|
||||||
ref = $bindable(null),
|
|
||||||
checked = $bindable(false),
|
|
||||||
indeterminate = $bindable(false),
|
|
||||||
class: className,
|
|
||||||
children: childrenProp,
|
|
||||||
...restProps
|
|
||||||
}: WithoutChildrenOrChild<DropdownMenuPrimitive.CheckboxItemProps> & {
|
|
||||||
children?: Snippet;
|
|
||||||
} = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<DropdownMenuPrimitive.CheckboxItem
|
|
||||||
bind:ref
|
|
||||||
bind:checked
|
|
||||||
bind:indeterminate
|
|
||||||
class={cn(
|
|
||||||
'data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground relative flex cursor-default items-center rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50',
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...restProps}>
|
|
||||||
{#snippet children({ checked, indeterminate })}
|
|
||||||
<span class="absolute left-2 flex size-3.5 items-center justify-center">
|
|
||||||
{#if indeterminate}
|
|
||||||
<Minus class="size-4" />
|
|
||||||
{:else}
|
|
||||||
<Check class={cn('size-4', !checked && 'text-transparent')} />
|
|
||||||
{/if}
|
|
||||||
</span>
|
|
||||||
{@render childrenProp?.()}
|
|
||||||
{/snippet}
|
|
||||||
</DropdownMenuPrimitive.CheckboxItem>
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { cn } from '$lib/utils/utils.js';
|
|
||||||
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
|
|
||||||
|
|
||||||
let {
|
|
||||||
ref = $bindable(null),
|
|
||||||
sideOffset = 4,
|
|
||||||
portalProps,
|
|
||||||
class: className,
|
|
||||||
...restProps
|
|
||||||
}: DropdownMenuPrimitive.ContentProps & {
|
|
||||||
portalProps?: DropdownMenuPrimitive.PortalProps;
|
|
||||||
} = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<DropdownMenuPrimitive.Portal {...portalProps}>
|
|
||||||
<DropdownMenuPrimitive.Content
|
|
||||||
bind:ref
|
|
||||||
{sideOffset}
|
|
||||||
class={cn(
|
|
||||||
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-32 overflow-hidden rounded-md border p-1 shadow-md outline-hidden',
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...restProps} />
|
|
||||||
</DropdownMenuPrimitive.Portal>
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
|
|
||||||
import { cn } from '$lib/utils/utils.js';
|
|
||||||
|
|
||||||
let {
|
|
||||||
ref = $bindable(null),
|
|
||||||
class: className,
|
|
||||||
inset,
|
|
||||||
...restProps
|
|
||||||
}: DropdownMenuPrimitive.GroupHeadingProps & {
|
|
||||||
inset?: boolean;
|
|
||||||
} = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<DropdownMenuPrimitive.GroupHeading
|
|
||||||
bind:ref
|
|
||||||
class={cn('px-2 py-1.5 text-sm font-semibold', inset && 'pl-8', className)}
|
|
||||||
{...restProps} />
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { cn } from '$lib/utils/utils.js';
|
|
||||||
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
|
|
||||||
|
|
||||||
let {
|
|
||||||
ref = $bindable(null),
|
|
||||||
class: className,
|
|
||||||
inset,
|
|
||||||
...restProps
|
|
||||||
}: DropdownMenuPrimitive.ItemProps & {
|
|
||||||
inset?: boolean;
|
|
||||||
} = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<DropdownMenuPrimitive.Item
|
|
||||||
bind:ref
|
|
||||||
class={cn(
|
|
||||||
'data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden transition-colors select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
|
|
||||||
inset && 'pl-8',
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...restProps} />
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { cn } from '$lib/utils/utils.js';
|
|
||||||
import { type WithElementRef } from 'bits-ui';
|
|
||||||
import type { HTMLAttributes } from 'svelte/elements';
|
|
||||||
|
|
||||||
let {
|
|
||||||
ref = $bindable(null),
|
|
||||||
class: className,
|
|
||||||
inset,
|
|
||||||
children,
|
|
||||||
...restProps
|
|
||||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> & {
|
|
||||||
inset?: boolean;
|
|
||||||
} = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div
|
|
||||||
bind:this={ref}
|
|
||||||
class={cn('px-2 py-1.5 text-sm font-semibold', inset && 'pl-8', className)}
|
|
||||||
{...restProps}>
|
|
||||||
{@render children?.()}
|
|
||||||
</div>
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import {
|
|
||||||
DropdownMenu as DropdownMenuPrimitive,
|
|
||||||
type WithoutChild
|
|
||||||
} from 'bits-ui';
|
|
||||||
import Circle from 'lucide-svelte/icons/circle';
|
|
||||||
import { cn } from '$lib/utils/utils.js';
|
|
||||||
|
|
||||||
let {
|
|
||||||
ref = $bindable(null),
|
|
||||||
class: className,
|
|
||||||
children: childrenProp,
|
|
||||||
...restProps
|
|
||||||
}: WithoutChild<DropdownMenuPrimitive.RadioItemProps> = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<DropdownMenuPrimitive.RadioItem
|
|
||||||
bind:ref
|
|
||||||
class={cn(
|
|
||||||
'data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground relative flex cursor-default items-center rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50',
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...restProps}>
|
|
||||||
{#snippet children({ checked })}
|
|
||||||
<span class="absolute left-2 flex size-3.5 items-center justify-center">
|
|
||||||
{#if checked}
|
|
||||||
<Circle class="size-2 fill-current" />
|
|
||||||
{/if}
|
|
||||||
</span>
|
|
||||||
{@render childrenProp?.({ checked })}
|
|
||||||
{/snippet}
|
|
||||||
</DropdownMenuPrimitive.RadioItem>
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
|
|
||||||
import { cn } from '$lib/utils/utils.js';
|
|
||||||
|
|
||||||
let {
|
|
||||||
ref = $bindable(null),
|
|
||||||
class: className,
|
|
||||||
...restProps
|
|
||||||
}: DropdownMenuPrimitive.SeparatorProps = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<DropdownMenuPrimitive.Separator
|
|
||||||
bind:ref
|
|
||||||
class={cn('bg-muted -mx-1 my-1 h-px', className)}
|
|
||||||
{...restProps} />
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import type { HTMLAttributes } from 'svelte/elements';
|
|
||||||
import { type WithElementRef } from 'bits-ui';
|
|
||||||
import { cn } from '$lib/utils/utils.js';
|
|
||||||
|
|
||||||
let {
|
|
||||||
ref = $bindable(null),
|
|
||||||
class: className,
|
|
||||||
children,
|
|
||||||
...restProps
|
|
||||||
}: WithElementRef<HTMLAttributes<HTMLSpanElement>> = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<span
|
|
||||||
bind:this={ref}
|
|
||||||
class={cn('ml-auto text-xs tracking-widest opacity-60', className)}
|
|
||||||
{...restProps}>
|
|
||||||
{@render children?.()}
|
|
||||||
</span>
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
|
|
||||||
import { cn } from '$lib/utils/utils.js';
|
|
||||||
|
|
||||||
let {
|
|
||||||
ref = $bindable(null),
|
|
||||||
class: className,
|
|
||||||
...restProps
|
|
||||||
}: DropdownMenuPrimitive.SubContentProps = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<DropdownMenuPrimitive.SubContent
|
|
||||||
bind:ref
|
|
||||||
class={cn(
|
|
||||||
'bg-popover text-popover-foreground z-50 min-w-32 rounded-md border p-1 shadow-lg focus:outline-hidden',
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...restProps} />
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
|
|
||||||
import ChevronRight from 'lucide-svelte/icons/chevron-right';
|
|
||||||
import { cn } from '$lib/utils/utils.js';
|
|
||||||
|
|
||||||
let {
|
|
||||||
ref = $bindable(null),
|
|
||||||
class: className,
|
|
||||||
inset,
|
|
||||||
children,
|
|
||||||
...restProps
|
|
||||||
}: DropdownMenuPrimitive.SubTriggerProps & {
|
|
||||||
inset?: boolean;
|
|
||||||
} = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<DropdownMenuPrimitive.SubTrigger
|
|
||||||
bind:ref
|
|
||||||
class={cn(
|
|
||||||
'data-[highlighted]:bg-accent data-[state=open]:bg-accent flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
|
|
||||||
inset && 'pl-8',
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...restProps}>
|
|
||||||
{@render children?.()}
|
|
||||||
<ChevronRight class="ml-auto" />
|
|
||||||
</DropdownMenuPrimitive.SubTrigger>
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
|
|
||||||
import CheckboxItem from './dropdown-menu-checkbox-item.svelte';
|
|
||||||
import Content from './dropdown-menu-content.svelte';
|
|
||||||
import GroupHeading from './dropdown-menu-group-heading.svelte';
|
|
||||||
import Item from './dropdown-menu-item.svelte';
|
|
||||||
import Label from './dropdown-menu-label.svelte';
|
|
||||||
import RadioItem from './dropdown-menu-radio-item.svelte';
|
|
||||||
import Separator from './dropdown-menu-separator.svelte';
|
|
||||||
import Shortcut from './dropdown-menu-shortcut.svelte';
|
|
||||||
import SubContent from './dropdown-menu-sub-content.svelte';
|
|
||||||
import SubTrigger from './dropdown-menu-sub-trigger.svelte';
|
|
||||||
|
|
||||||
const Sub = DropdownMenuPrimitive.Sub;
|
|
||||||
const Root = DropdownMenuPrimitive.Root;
|
|
||||||
const Trigger = DropdownMenuPrimitive.Trigger;
|
|
||||||
const Group = DropdownMenuPrimitive.Group;
|
|
||||||
const RadioGroup = DropdownMenuPrimitive.RadioGroup;
|
|
||||||
|
|
||||||
export {
|
|
||||||
CheckboxItem,
|
|
||||||
Content,
|
|
||||||
Root as DropdownMenu,
|
|
||||||
CheckboxItem as DropdownMenuCheckboxItem,
|
|
||||||
Content as DropdownMenuContent,
|
|
||||||
Group as DropdownMenuGroup,
|
|
||||||
GroupHeading as DropdownMenuGroupHeading,
|
|
||||||
Item as DropdownMenuItem,
|
|
||||||
Label as DropdownMenuLabel,
|
|
||||||
RadioGroup as DropdownMenuRadioGroup,
|
|
||||||
RadioItem as DropdownMenuRadioItem,
|
|
||||||
Separator as DropdownMenuSeparator,
|
|
||||||
Shortcut as DropdownMenuShortcut,
|
|
||||||
Sub as DropdownMenuSub,
|
|
||||||
SubContent as DropdownMenuSubContent,
|
|
||||||
SubTrigger as DropdownMenuSubTrigger,
|
|
||||||
Trigger as DropdownMenuTrigger,
|
|
||||||
Group,
|
|
||||||
GroupHeading,
|
|
||||||
Item,
|
|
||||||
Label,
|
|
||||||
RadioGroup,
|
|
||||||
RadioItem,
|
|
||||||
Root,
|
|
||||||
Separator,
|
|
||||||
Shortcut,
|
|
||||||
Sub,
|
|
||||||
SubContent,
|
|
||||||
SubTrigger,
|
|
||||||
Trigger
|
|
||||||
};
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
import { DropdownMenu as DropdownMenuPrimitive } from 'bits-ui';
|
|
||||||
import CheckboxItem from './dropdown-menu-checkbox-item.svelte';
|
|
||||||
import Content from './dropdown-menu-content.svelte';
|
|
||||||
import GroupHeading from './dropdown-menu-group-heading.svelte';
|
|
||||||
import Item from './dropdown-menu-item.svelte';
|
|
||||||
import Label from './dropdown-menu-label.svelte';
|
|
||||||
import RadioItem from './dropdown-menu-radio-item.svelte';
|
|
||||||
import Separator from './dropdown-menu-separator.svelte';
|
|
||||||
import Shortcut from './dropdown-menu-shortcut.svelte';
|
|
||||||
import SubContent from './dropdown-menu-sub-content.svelte';
|
|
||||||
import SubTrigger from './dropdown-menu-sub-trigger.svelte';
|
|
||||||
|
|
||||||
const Sub = DropdownMenuPrimitive.Sub;
|
|
||||||
const Root = DropdownMenuPrimitive.Root;
|
|
||||||
const Trigger = DropdownMenuPrimitive.Trigger;
|
|
||||||
const Group = DropdownMenuPrimitive.Group;
|
|
||||||
const RadioGroup = DropdownMenuPrimitive.RadioGroup;
|
|
||||||
|
|
||||||
export {
|
|
||||||
CheckboxItem,
|
|
||||||
Content,
|
|
||||||
Root as DropdownMenu,
|
|
||||||
CheckboxItem as DropdownMenuCheckboxItem,
|
|
||||||
Content as DropdownMenuContent,
|
|
||||||
Group as DropdownMenuGroup,
|
|
||||||
GroupHeading as DropdownMenuGroupHeading,
|
|
||||||
Item as DropdownMenuItem,
|
|
||||||
Label as DropdownMenuLabel,
|
|
||||||
RadioGroup as DropdownMenuRadioGroup,
|
|
||||||
RadioItem as DropdownMenuRadioItem,
|
|
||||||
Separator as DropdownMenuSeparator,
|
|
||||||
Shortcut as DropdownMenuShortcut,
|
|
||||||
Sub as DropdownMenuSub,
|
|
||||||
SubContent as DropdownMenuSubContent,
|
|
||||||
SubTrigger as DropdownMenuSubTrigger,
|
|
||||||
Trigger as DropdownMenuTrigger,
|
|
||||||
Group,
|
|
||||||
GroupHeading,
|
|
||||||
Item,
|
|
||||||
Label,
|
|
||||||
RadioGroup,
|
|
||||||
RadioItem,
|
|
||||||
Root,
|
|
||||||
Separator,
|
|
||||||
Shortcut,
|
|
||||||
Sub,
|
|
||||||
SubContent,
|
|
||||||
SubTrigger,
|
|
||||||
Trigger
|
|
||||||
};
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
import Root from './input.svelte';
|
|
||||||
|
|
||||||
export {
|
|
||||||
Root,
|
|
||||||
//
|
|
||||||
Root as Input
|
|
||||||
};
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
import Root from './input.svelte';
|
|
||||||
|
|
||||||
export {
|
|
||||||
Root,
|
|
||||||
//
|
|
||||||
Root as Input
|
|
||||||
};
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
||||||
import type { WithElementRef } from 'bits-ui';
|
|
||||||
import { cn } from '$lib/utils/utils.js';
|
|
||||||
|
|
||||||
let {
|
|
||||||
ref = $bindable(null),
|
|
||||||
value = $bindable(),
|
|
||||||
class: className,
|
|
||||||
...restProps
|
|
||||||
}: WithElementRef<HTMLInputAttributes> = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<input
|
|
||||||
bind:this={ref}
|
|
||||||
class={cn(
|
|
||||||
'border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring flex h-10 w-full rounded-md border px-3 py-2 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-hidden disabled:cursor-not-allowed disabled:opacity-50',
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
bind:value
|
|
||||||
{...restProps} />
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import { Popover as PopoverPrimitive } from 'bits-ui';
|
|
||||||
import Content from './popover-content.svelte';
|
|
||||||
const Root = PopoverPrimitive.Root;
|
|
||||||
const Trigger = PopoverPrimitive.Trigger;
|
|
||||||
const Close = PopoverPrimitive.Close;
|
|
||||||
|
|
||||||
export {
|
|
||||||
Root,
|
|
||||||
Content,
|
|
||||||
Trigger,
|
|
||||||
Close,
|
|
||||||
//
|
|
||||||
Root as Popover,
|
|
||||||
Content as PopoverContent,
|
|
||||||
Trigger as PopoverTrigger,
|
|
||||||
Close as PopoverClose
|
|
||||||
};
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import { Popover as PopoverPrimitive } from 'bits-ui';
|
|
||||||
import Content from './popover-content.svelte';
|
|
||||||
const Root = PopoverPrimitive.Root;
|
|
||||||
const Trigger = PopoverPrimitive.Trigger;
|
|
||||||
const Close = PopoverPrimitive.Close;
|
|
||||||
|
|
||||||
export {
|
|
||||||
Root,
|
|
||||||
Content,
|
|
||||||
Trigger,
|
|
||||||
Close,
|
|
||||||
//
|
|
||||||
Root as Popover,
|
|
||||||
Content as PopoverContent,
|
|
||||||
Trigger as PopoverTrigger,
|
|
||||||
Close as PopoverClose
|
|
||||||
};
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { cn } from '$lib/utils/utils.js';
|
|
||||||
import { Popover as PopoverPrimitive } from 'bits-ui';
|
|
||||||
|
|
||||||
let {
|
|
||||||
ref = $bindable(null),
|
|
||||||
class: className,
|
|
||||||
sideOffset = 4,
|
|
||||||
align = 'center',
|
|
||||||
portalProps,
|
|
||||||
...restProps
|
|
||||||
}: PopoverPrimitive.ContentProps & {
|
|
||||||
portalProps?: PopoverPrimitive.PortalProps;
|
|
||||||
} = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<PopoverPrimitive.Portal {...portalProps}>
|
|
||||||
<PopoverPrimitive.Content
|
|
||||||
bind:ref
|
|
||||||
{sideOffset}
|
|
||||||
{align}
|
|
||||||
class={cn(
|
|
||||||
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 rounded-md border p-4 shadow-md outline-hidden',
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...restProps} />
|
|
||||||
</PopoverPrimitive.Portal>
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
import Root from './separator.svelte';
|
|
||||||
|
|
||||||
export {
|
|
||||||
Root,
|
|
||||||
//
|
|
||||||
Root as Separator
|
|
||||||
};
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
import Root from './separator.svelte';
|
|
||||||
|
|
||||||
export {
|
|
||||||
Root,
|
|
||||||
//
|
|
||||||
Root as Separator
|
|
||||||
};
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { Separator as SeparatorPrimitive } from 'bits-ui';
|
|
||||||
import { cn } from '$lib/utils/utils.js';
|
|
||||||
|
|
||||||
let {
|
|
||||||
ref = $bindable(null),
|
|
||||||
class: className,
|
|
||||||
orientation = 'horizontal',
|
|
||||||
...restProps
|
|
||||||
}: SeparatorPrimitive.RootProps = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<SeparatorPrimitive.Root
|
|
||||||
bind:ref
|
|
||||||
class={cn(
|
|
||||||
'bg-border shrink-0',
|
|
||||||
orientation === 'horizontal' ? 'h-px w-full' : 'min-h-full w-px',
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{orientation}
|
|
||||||
{...restProps} />
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import { Tooltip as TooltipPrimitive } from 'bits-ui';
|
|
||||||
import Content from './tooltip-content.svelte';
|
|
||||||
|
|
||||||
const Root = TooltipPrimitive.Root;
|
|
||||||
const Trigger = TooltipPrimitive.Trigger;
|
|
||||||
const Provider = TooltipPrimitive.Provider;
|
|
||||||
|
|
||||||
export {
|
|
||||||
Root,
|
|
||||||
Trigger,
|
|
||||||
Content,
|
|
||||||
Provider,
|
|
||||||
//
|
|
||||||
Root as Tooltip,
|
|
||||||
Content as TooltipContent,
|
|
||||||
Trigger as TooltipTrigger,
|
|
||||||
Provider as TooltipProvider
|
|
||||||
};
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import { Tooltip as TooltipPrimitive } from 'bits-ui';
|
|
||||||
import Content from './tooltip-content.svelte';
|
|
||||||
|
|
||||||
const Root = TooltipPrimitive.Root;
|
|
||||||
const Trigger = TooltipPrimitive.Trigger;
|
|
||||||
const Provider = TooltipPrimitive.Provider;
|
|
||||||
|
|
||||||
export {
|
|
||||||
Root,
|
|
||||||
Trigger,
|
|
||||||
Content,
|
|
||||||
Provider,
|
|
||||||
//
|
|
||||||
Root as Tooltip,
|
|
||||||
Content as TooltipContent,
|
|
||||||
Trigger as TooltipTrigger,
|
|
||||||
Provider as TooltipProvider
|
|
||||||
};
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { Tooltip as TooltipPrimitive } from 'bits-ui';
|
|
||||||
import { cn } from '$lib/utils/utils.js';
|
|
||||||
|
|
||||||
let {
|
|
||||||
ref = $bindable(null),
|
|
||||||
class: className,
|
|
||||||
sideOffset = 4,
|
|
||||||
...restProps
|
|
||||||
}: TooltipPrimitive.ContentProps = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<TooltipPrimitive.Content
|
|
||||||
bind:ref
|
|
||||||
{sideOffset}
|
|
||||||
class={cn(
|
|
||||||
'bg-popover text-popover-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 overflow-hidden rounded-md border px-3 py-1.5 text-sm shadow-md',
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...restProps} />
|
|
||||||
26
src/lucide-augment.d.ts
vendored
Normal file
26
src/lucide-augment.d.ts
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
// Module augmentation to restore `class` prop on @lucide/svelte IconProps.
|
||||||
|
//
|
||||||
|
// WHY this file exists and why it is separate from app.d.ts:
|
||||||
|
// @lucide/svelte ≥ 0.577.0 dropped `class` from IconProps (it now derives props purely from
|
||||||
|
// SVGAttributes<SVGSVGElement>, which TypeScript types without `class`). Every
|
||||||
|
// <SomeIcon class="..." /> in the codebase errors without this.
|
||||||
|
//
|
||||||
|
// WHY it cannot live in app.d.ts:
|
||||||
|
// app.d.ts is a script-context declaration file (no top-level import/export). A
|
||||||
|
// `declare module 'x' {}` in a script file is an AMBIENT MODULE DECLARATION that completely
|
||||||
|
// replaces the package's types — not an augmentation. That caused svelte-check to see
|
||||||
|
// @lucide/svelte as exporting only IconProps, making every icon import a "no exported member"
|
||||||
|
// error. Putting the declaration here with `export {}` makes this file a module, so
|
||||||
|
// `declare module` becomes a proper augmentation that merges with the package types.
|
||||||
|
//
|
||||||
|
// If a future lucide update re-adds `class` natively, this file becomes a harmless no-op.
|
||||||
|
// Root cause: @lucide/svelte 0.561.0 → 0.577.0 bump in chore commit 366c6629 (2026-03-10).
|
||||||
|
|
||||||
|
export {};
|
||||||
|
|
||||||
|
declare module '@lucide/svelte' {
|
||||||
|
interface IconProps {
|
||||||
|
class?: string;
|
||||||
|
'aria-hidden'?: string | boolean;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -411,7 +411,7 @@ $effect(() => {
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if browser && (!$ae_loc?.iframe || $ae_loc?.administrator_access || ($ae_loc?.trusted_access && $ae_loc.edit_mode))}
|
{#if browser && (!$ae_loc?.iframe || !$ae_loc?.sys_menu?.hide || $ae_loc?.administrator_access || ($ae_loc?.trusted_access && $ae_loc.edit_mode))}
|
||||||
<!-- print:hidden wrapper: sys/debug menus are fixed overlays — must not appear on printed pages -->
|
<!-- print:hidden wrapper: sys/debug menus are fixed overlays — must not appear on printed pages -->
|
||||||
<div class="print:hidden">
|
<div class="print:hidden">
|
||||||
<E_app_sys_bar
|
<E_app_sys_bar
|
||||||
|
|||||||
@@ -478,7 +478,7 @@ function toggle_manage_tab() {
|
|||||||
<Tab_add exhibit_id={page.params.exhibit_id ?? ''} />
|
<Tab_add exhibit_id={page.params.exhibit_id ?? ''} />
|
||||||
{:else if active_tab === 'payment'}
|
{:else if active_tab === 'payment'}
|
||||||
<div class="mx-auto w-full max-w-4xl">
|
<div class="mx-auto w-full max-w-4xl">
|
||||||
<Comp_exhibit_payment />
|
<Comp_exhibit_payment exhibit_id={page.params.exhibit_id ?? ''} />
|
||||||
</div>
|
</div>
|
||||||
{:else if active_tab === 'list'}
|
{:else if active_tab === 'list'}
|
||||||
<div class="flex w-full flex-col space-y-6">
|
<div class="flex w-full flex-col space-y-6">
|
||||||
|
|||||||
@@ -1,11 +1,183 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
/**
|
/**
|
||||||
* src/routes/events/[event_id]/(leads)/leads/exhibit/[exhibit_id]/ae_comp__exhibit_payment.svelte
|
* ae_comp__exhibit_payment.svelte
|
||||||
* Leads Payment Stub.
|
* Leads Payment — Stripe Buy Button integration.
|
||||||
|
*
|
||||||
|
* Stripe config is read from $ae_loc.site_cfg_json (set per-event by admin in Site Settings):
|
||||||
|
* stripe_publishable_key — Stripe publishable key (pk_live_... or pk_test_...)
|
||||||
|
* stripe_btn_1_license — Stripe Buy Button ID for the 1-user license tier
|
||||||
|
* stripe_btn_3_license — Buy Button ID for 3-user tier
|
||||||
|
* stripe_btn_6_license — Buy Button ID for 6-user tier
|
||||||
|
* stripe_btn_10_license — Buy Button ID for 10-user tier
|
||||||
|
*
|
||||||
|
* client_reference_id = exhibit_id — ties each Stripe payment back to this booth record.
|
||||||
|
* Payment status (priority flag) is read live from Dexie (IDB).
|
||||||
|
*
|
||||||
|
* WHY {#key btn_payment_id}: stripe-buy-button is a web component that ignores attribute
|
||||||
|
* changes after its initial mount. Keying on btn_payment_id forces Svelte to fully
|
||||||
|
* remount the element whenever the selected license tier changes.
|
||||||
*/
|
*/
|
||||||
|
import { liveQuery } from 'dexie';
|
||||||
|
import { db_events } from '$lib/ae_events/db_events';
|
||||||
|
import { ae_loc } from '$lib/stores/ae_stores';
|
||||||
|
import { AlertTriangle, CheckCircle, CreditCard } from '@lucide/svelte';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
exhibit_id: string;
|
||||||
|
}
|
||||||
|
let { exhibit_id }: Props = $props();
|
||||||
|
|
||||||
|
const lq__exhibit_obj = liveQuery(() => {
|
||||||
|
if (!exhibit_id) return undefined;
|
||||||
|
return db_events.exhibit.get(exhibit_id);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Stripe config from site_cfg_json — set per-event by admin in Site Settings.
|
||||||
|
const stripe_publishable_key = $derived(
|
||||||
|
($ae_loc.site_cfg_json?.stripe_publishable_key as string | undefined) ?? null
|
||||||
|
);
|
||||||
|
const stripe_btn_ids = $derived({
|
||||||
|
1: ($ae_loc.site_cfg_json?.stripe_btn_1_license as string | undefined) ?? null,
|
||||||
|
3: ($ae_loc.site_cfg_json?.stripe_btn_3_license as string | undefined) ?? null,
|
||||||
|
6: ($ae_loc.site_cfg_json?.stripe_btn_6_license as string | undefined) ?? null,
|
||||||
|
10: ($ae_loc.site_cfg_json?.stripe_btn_10_license as string | undefined) ?? null,
|
||||||
|
});
|
||||||
|
|
||||||
|
const license_tiers = [
|
||||||
|
{ qty: 1, label: '1 user license', price: '$120' },
|
||||||
|
{ qty: 3, label: 'Up to 3 user licenses', price: '$330' },
|
||||||
|
{ qty: 6, label: 'Up to 6 user licenses', price: '$660' },
|
||||||
|
{ qty: 10, label: 'Up to 10 user licenses', price: '$1,100' },
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
type LicenseQty = 1 | 3 | 6 | 10;
|
||||||
|
let selected_qty: LicenseQty = $state(1);
|
||||||
|
|
||||||
|
const btn_payment_id = $derived(stripe_btn_ids[selected_qty] ?? null);
|
||||||
|
const is_stripe_configured = $derived(!!stripe_publishable_key);
|
||||||
|
|
||||||
|
// Inject the Stripe Buy Button JS once per session (idempotent — skips if already present).
|
||||||
|
// document.head is outside Svelte's managed DOM tree so this is safe.
|
||||||
|
$effect(() => {
|
||||||
|
if (!is_stripe_configured) return;
|
||||||
|
if (document.querySelector('script[src*="stripe.com/v3/buy-button"]')) return;
|
||||||
|
|
||||||
|
const script = document.createElement('script');
|
||||||
|
script.src = 'https://js.stripe.com/v3/buy-button.js';
|
||||||
|
script.async = true;
|
||||||
|
document.head.appendChild(script);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="exhibit-payment card p-4">
|
<div class="exhibit-payment space-y-6">
|
||||||
<h3 class="h3">Payment & Licensing</h3>
|
{#if $lq__exhibit_obj?.priority}
|
||||||
<p>Placeholder for Stripe integration.</p>
|
<!-- Paid Confirmation — shown when admin has marked this booth as paid (priority = true) -->
|
||||||
|
<div class="card preset-tonal-success border-success-500/30 border p-6">
|
||||||
|
<div class="mb-3 flex items-center gap-3">
|
||||||
|
<CheckCircle size="1.5em" class="text-success-500" />
|
||||||
|
<h4 class="text-success-500 text-lg font-bold">Marked as Paid</h4>
|
||||||
|
</div>
|
||||||
|
<p class="text-sm">
|
||||||
|
Thank you for your payment. You have purchased
|
||||||
|
<strong>{$lq__exhibit_obj?.license_max ?? 0}</strong> user license(s)
|
||||||
|
for lead retrieval at this event.
|
||||||
|
</p>
|
||||||
|
{#if ($lq__exhibit_obj?.leads_device_sm_qty ?? 0) > 0}
|
||||||
|
<p class="mt-2 text-sm">
|
||||||
|
Rental device(s): <strong>{$lq__exhibit_obj?.leads_device_sm_qty}</strong>.
|
||||||
|
Pick them up at onsite registration.
|
||||||
|
</p>
|
||||||
|
{:else}
|
||||||
|
<p class="mt-2 text-sm opacity-60">
|
||||||
|
No rental devices. Use your own device(s) with this service.
|
||||||
|
</p>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{:else if !is_stripe_configured}
|
||||||
|
<!-- Stripe not configured — show a setup hint only to admins -->
|
||||||
|
{#if $ae_loc.administrator_access}
|
||||||
|
<div
|
||||||
|
class="card preset-tonal-warning border-warning-500/30 flex items-start gap-3 border p-4">
|
||||||
|
<AlertTriangle size="1.2em" class="text-warning-500 mt-0.5 shrink-0" />
|
||||||
|
<div class="text-sm">
|
||||||
|
<p class="font-bold">Stripe not configured for this site.</p>
|
||||||
|
<p class="mt-1 opacity-70">
|
||||||
|
Add <code class="font-mono text-xs">stripe_publishable_key</code>,
|
||||||
|
<code class="font-mono text-xs">stripe_btn_1_license</code>,
|
||||||
|
<code class="font-mono text-xs">stripe_btn_3_license</code>,
|
||||||
|
<code class="font-mono text-xs">stripe_btn_6_license</code>, and
|
||||||
|
<code class="font-mono text-xs">stripe_btn_10_license</code>
|
||||||
|
to <strong>Site Config JSON</strong> to enable payment.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<p class="py-4 text-center text-sm opacity-50">
|
||||||
|
Online payment is not available at this time. Please contact the event organizer.
|
||||||
|
</p>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{:else}
|
||||||
|
<!-- Payment Form — Stripe configured, booth not yet marked as paid -->
|
||||||
|
<div class="space-y-6">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<CreditCard size="1.2em" class="text-success-500" />
|
||||||
|
<h4 class="text-lg font-bold">Purchase Licenses</h4>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card preset-tonal-surface border-surface-500/10 border p-4 text-sm">
|
||||||
|
<p>
|
||||||
|
Each person from your booth who will scan attendee badges needs their own user
|
||||||
|
license. You can use your own smartphone, tablet, or laptop — rental devices are
|
||||||
|
not required.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- License Tier Selector -->
|
||||||
|
<div class="space-y-1">
|
||||||
|
<label class="label">
|
||||||
|
<span class="text-xs font-black tracking-widest uppercase opacity-40"
|
||||||
|
>Select License Tier</span>
|
||||||
|
<select class="select mt-2 w-full" bind:value={selected_qty}>
|
||||||
|
{#each license_tiers as tier (tier.qty)}
|
||||||
|
<option value={tier.qty}>{tier.label} — {tier.price}</option>
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<p class="text-xs opacity-50">One license per team member who will scan badges.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Stripe Buy Button -->
|
||||||
|
<div class="flex flex-col items-center gap-3">
|
||||||
|
{#if stripe_publishable_key && btn_payment_id}
|
||||||
|
<!-- {#key} forces full remount when tier changes — stripe-buy-button ignores
|
||||||
|
attribute updates after initial render. See component comment above. -->
|
||||||
|
{#key btn_payment_id}
|
||||||
|
<stripe-buy-button
|
||||||
|
buy-button-id={btn_payment_id}
|
||||||
|
publishable-key={stripe_publishable_key}
|
||||||
|
client-reference-id={exhibit_id}>
|
||||||
|
</stripe-buy-button>
|
||||||
|
{/key}
|
||||||
|
<p class="text-center text-xs opacity-40">
|
||||||
|
Payment processed securely via Stripe. Verify quantities on the checkout
|
||||||
|
page.
|
||||||
|
</p>
|
||||||
|
<div class="card preset-tonal-warning w-full p-3 text-xs">
|
||||||
|
<strong>Note:</strong> Payment confirmation may take up to 2 business days
|
||||||
|
to reflect in your account status. Contact
|
||||||
|
<a
|
||||||
|
href="mailto:exhibits@oneskyit.com"
|
||||||
|
class="font-medium hover:underline">exhibits@oneskyit.com</a>
|
||||||
|
with questions.
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<p class="text-sm opacity-50">
|
||||||
|
No payment button is configured for this tier. Contact the event organizer.
|
||||||
|
</p>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -428,7 +428,7 @@ function handle_signout() {
|
|||||||
{#if show_billing}
|
{#if show_billing}
|
||||||
<div
|
<div
|
||||||
class="bg-surface-500/5 border-surface-500/10 animate-in fade-in slide-in-from-top-2 border-t p-4">
|
class="bg-surface-500/5 border-surface-500/10 animate-in fade-in slide-in-from-top-2 border-t p-4">
|
||||||
<Comp_exhibit_payment />
|
<Comp_exhibit_payment {exhibit_id} />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -477,23 +477,47 @@ async function fetch_novi_data() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const novi_idaa_group_guid_li =
|
// Trusted/admin users are always moderators. Check the UUID directly against the
|
||||||
$ae_loc.site_cfg_json?.novi_idaa_group_guid_li ?? [];
|
// known lists rather than $ae_loc.trusted_access — that flag is only upgraded, never
|
||||||
const moderatorIdSet = await get_novi_group_moderators(
|
// downgraded, so it sticks across Novi impersonation (which does a full iframe reload
|
||||||
novi_idaa_group_guid_li,
|
// with a different UUID but doesn't reset the inherited access level).
|
||||||
novi_api_root_url,
|
const admin_li: string[] = $idaa_loc.novi_admin_li ?? [];
|
||||||
novi_api_key
|
const trusted_li: string[] = $idaa_loc.novi_trusted_li ?? [];
|
||||||
);
|
const is_trusted_uuid = user_id
|
||||||
const normalizedUserId = String(user_id ?? '')
|
? admin_li.includes(user_id) || trusted_li.includes(user_id)
|
||||||
.toLowerCase()
|
: false;
|
||||||
.trim();
|
if (is_trusted_uuid) {
|
||||||
|
|
||||||
if (normalizedUserId && moderatorIdSet.has(normalizedUserId)) {
|
|
||||||
is_moderator = true;
|
is_moderator = true;
|
||||||
console.log(`Jitsi: User ${user_id} is a moderator.`);
|
console.log(`Jitsi: User ${user_id} is moderator via admin/trusted UUID list.`);
|
||||||
} else {
|
} else {
|
||||||
is_moderator = false; // Explicitly set to false if not in the set
|
// For regular authenticated members, check the specific meeting group.
|
||||||
console.log(`Jitsi: User ${user_id} is not a moderator.`);
|
// Prefer g_uuid from URL (per-meeting, more precise); fall back to the global
|
||||||
|
// novi_idaa_group_guid_li list for older Novi pages not yet passing g_uuid.
|
||||||
|
const group_uuid = url_params.g_uuid ?? null;
|
||||||
|
const group_guid_li = group_uuid
|
||||||
|
? [group_uuid]
|
||||||
|
: ($ae_loc.site_cfg_json?.novi_idaa_group_guid_li ?? []);
|
||||||
|
if (group_uuid) {
|
||||||
|
console.log(`Jitsi: Checking moderator via URL g_uuid: ${group_uuid}`);
|
||||||
|
} else {
|
||||||
|
console.log(`Jitsi: No g_uuid in URL — falling back to site config group list (${group_guid_li.length} groups).`);
|
||||||
|
}
|
||||||
|
const moderatorIdSet = await get_novi_group_moderators(
|
||||||
|
group_guid_li,
|
||||||
|
novi_api_root_url,
|
||||||
|
novi_api_key
|
||||||
|
);
|
||||||
|
const normalizedUserId = String(user_id ?? '')
|
||||||
|
.toLowerCase()
|
||||||
|
.trim();
|
||||||
|
|
||||||
|
if (normalizedUserId && moderatorIdSet.has(normalizedUserId)) {
|
||||||
|
is_moderator = true;
|
||||||
|
console.log(`Jitsi: User ${user_id} is a moderator.`);
|
||||||
|
} else {
|
||||||
|
is_moderator = false;
|
||||||
|
console.log(`Jitsi: User ${user_id} is not a moderator.`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.warn(
|
console.warn(
|
||||||
@@ -506,9 +530,31 @@ async function fetch_novi_data() {
|
|||||||
email_input = email ?? '';
|
email_input = email ?? '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dynamically loads the Jitsi external API script for a given domain and waits for it.
|
||||||
|
* Using <svelte:head> for this script is not reliable — it loads asynchronously and there
|
||||||
|
* is no lifecycle hook to await its completion, causing a race with onMount/init_jitsi.
|
||||||
|
* Loading it here lets us sequence it correctly: fetch_novi_data → load script → init_jitsi.
|
||||||
|
*/
|
||||||
|
function load_jitsi_script(jitsi_domain: string): Promise<void> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
// @ts-expect-error — JitsiMeetExternalAPI is a global injected by the Jitsi script
|
||||||
|
if (typeof JitsiMeetExternalAPI !== 'undefined') {
|
||||||
|
resolve(); // Already loaded (e.g. resync after first load)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const script = document.createElement('script');
|
||||||
|
script.src = `https://${jitsi_domain}/external_api.js`;
|
||||||
|
script.onload = () => resolve();
|
||||||
|
script.onerror = () => reject(new Error(`Failed to load Jitsi script from ${jitsi_domain}`));
|
||||||
|
document.head.appendChild(script);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function handle_novi_resync() {
|
async function handle_novi_resync() {
|
||||||
console.log('Jitsi: Manually re-syncing Novi Data...');
|
console.log('Jitsi: Manually re-syncing Novi Data...');
|
||||||
await fetch_novi_data();
|
await fetch_novi_data();
|
||||||
|
await load_jitsi_script(domain ?? 'jitsi.dgrzone.com');
|
||||||
await init_jitsi();
|
await init_jitsi();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -518,11 +564,24 @@ onMount(async () => {
|
|||||||
'Jitsi: onMount - fetching user data and initializing Jitsi...'
|
'Jitsi: onMount - fetching user data and initializing Jitsi...'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// $ae_loc.sys_menu.hide = true;
|
|
||||||
|
|
||||||
await fetch_novi_data();
|
await fetch_novi_data();
|
||||||
|
|
||||||
// --- All data fetched, now initialize Jitsi ---
|
if (!domain) {
|
||||||
|
console.error('Jitsi: domain not set after fetch_novi_data — cannot load Jitsi script.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await load_jitsi_script(domain);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Jitsi: Failed to load external API script:', err);
|
||||||
|
const container = document.getElementById(jitsi_container_id);
|
||||||
|
if (container) container.innerHTML = '<h1>Jitsi API script failed to load. Please refresh the page.</h1>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- All data fetched and script ready, now initialize Jitsi ---
|
||||||
await init_jitsi();
|
await init_jitsi();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -814,9 +873,6 @@ async function init_jitsi() {
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
|
||||||
<script src="https://jitsi.dgrzone.com/external_api.js"></script>
|
|
||||||
</svelte:head>
|
|
||||||
|
|
||||||
{#if show_jitsi_container}
|
{#if show_jitsi_container}
|
||||||
<div id={jitsi_container_id} class="jitsi-container"></div>
|
<div id={jitsi_container_id} class="jitsi-container"></div>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ export function load({ url }) {
|
|||||||
return {
|
return {
|
||||||
params: {
|
params: {
|
||||||
uuid: url.searchParams.get('uuid'),
|
uuid: url.searchParams.get('uuid'),
|
||||||
|
g_uuid: url.searchParams.get('g_uuid'),
|
||||||
email: url.searchParams.get('email'),
|
email: url.searchParams.get('email'),
|
||||||
full_name: url.searchParams.get('full_name'),
|
full_name: url.searchParams.get('full_name'),
|
||||||
moderator: url.searchParams.get('moderator'),
|
moderator: url.searchParams.get('moderator'),
|
||||||
|
|||||||
@@ -26,28 +26,61 @@
|
|||||||
</p>
|
</p>
|
||||||
<!-- IDAA and Novi specific JavaScript to get current Novi user info and load Jitsi iframe -->
|
<!-- IDAA and Novi specific JavaScript to get current Novi user info and load Jitsi iframe -->
|
||||||
<script>
|
<script>
|
||||||
let novi_customer_uid = '<%=Novi.User.CustomerUniqueId%>'; // NOTE: The Novi UUID for the current current user/customer
|
|
||||||
|
// WARNING: Do not change this.
|
||||||
|
// NOTE: The Novi UUID for the current current user/customer
|
||||||
|
let novi_customer_uid = '<%=Novi.User.CustomerUniqueId%>';
|
||||||
console.log(`Novi's Current User's ID: ${novi_customer_uid}`);
|
console.log(`Novi's Current User's ID: ${novi_customer_uid}`);
|
||||||
|
|
||||||
let novi_group_uid = 'check-Novi-Group-UID';
|
|
||||||
// let novi_category_id = ''; // Not in use yet or at all?
|
/* *** ** * CHANGES START HERE * ** *** */
|
||||||
|
|
||||||
|
|
||||||
|
// NOTE: Change the novi_group_uid value to use one of the desired Novi group UID for this meetings moderators.
|
||||||
|
// NOTE: IDAA staff are always moderators for any Jitsi meeting.
|
||||||
|
let novi_group_uid =
|
||||||
|
'check-Novi-Group-UID';
|
||||||
|
// Example Novi group UIDs:
|
||||||
|
// * "IDAA Couples Meeting" uses "e9e162f0-3d03-4241-9682-340135ec3fb8"
|
||||||
|
// * "Student/Resident Meeting Moderators" uses "d76d2c00-962d-40f6-a2e8-ed9c85594d96"
|
||||||
|
// * "IDAA BIPOC Meeting" uses "873d3ad0-2605-4ccf-824c-638c16b2b9cf"
|
||||||
|
|
||||||
|
|
||||||
// NOTE: Change the room_name value to the desired Jitsi room name for the meeting.
|
// NOTE: Change the room_name value to the desired Jitsi room name for the meeting.
|
||||||
|
let room_name =
|
||||||
|
'IDAA-Example-Meeting';
|
||||||
// Example meeting room names:
|
// Example meeting room names:
|
||||||
// 'IDAA-Meeting' 'IDAA-Student-and-Resident-Meeting' 'IDAA-Couples-Meeting' 'IDAA-BIPOC-Meeting'
|
// * 'IDAA-Meeting'
|
||||||
let room_name = 'IDAA-Example-Meeting'; // // NOTE: Change this example meeting room name
|
// * 'IDAA-Student-and-Resident-Meeting'
|
||||||
|
// * 'IDAA-Couples-Meeting'
|
||||||
|
// * 'IDAA-BIPOC-Meeting'
|
||||||
|
// WARNING: Do not use spaces in the room name. Use dashes or underscores instead. The room name must be unique to avoid conflicts with other meetings.
|
||||||
|
|
||||||
|
|
||||||
|
// NOTE: Change the idaa_osit_ae_api_root_url value to use one of the example URLs below.
|
||||||
// WARNING:Do *not* use relative paths here. They must be direct to the site OSIT is hosting for IDAA. This value must point to the Svelte Jitsi page.
|
|
||||||
let idaa_osit_ae_api_root_url =
|
let idaa_osit_ae_api_root_url =
|
||||||
'https://dev-idaa.oneskyit.com/idaa/video_conferences'; // NOTE: DO NOT CHANGE THIS VALUE
|
'https://dev-idaa.oneskyit.com/idaa/video_conferences';
|
||||||
// Example URLs: 'https://sk-idaa.oneskyit.com/idaa/video_conferences' OR 'https://dev-idaa.oneskyit.com/idaa/video_conferences' OR 'http://idaa.localhost:5173/idaa/video_conferences
|
// Example URLs:
|
||||||
|
// * production 'https://sk-idaa.oneskyit.com/idaa/video_conferences'
|
||||||
|
// * production 'https://idaa.oneskyit.com/idaa/video_conferences'
|
||||||
|
// * testing 'https://test-idaa.oneskyit.com/idaa/video_conferences'
|
||||||
|
// * development 'https://dev-idaa.oneskyit.com/idaa/video_conferences'
|
||||||
|
// WARNING: Do *not* use relative paths here. They must be direct to the site OSIT is hosting for IDAA. This value must point to the Svelte Jitsi page.
|
||||||
|
|
||||||
// WARNING: Do *not* change this value. It is required for access control to the IDAA AE API.
|
|
||||||
let idaa_osit_ae_site_key = 'restricted-access'; // DO NOT CHANGE THIS VALUE
|
// NOTE: IGNORE: This is not currently used, but will be soon for an added layer of access control on the API.
|
||||||
|
let idaa_osit_ae_site_key =
|
||||||
|
'restricted-access';
|
||||||
// Example site keys: '8VTOJ0X5hvT6JdiTJsGEzQ' OR 'restricted-access' OR 'restricted'
|
// Example site keys: '8VTOJ0X5hvT6JdiTJsGEzQ' OR 'restricted-access' OR 'restricted'
|
||||||
|
|
||||||
|
|
||||||
|
// NOTE: IGNORE: For now this is not used
|
||||||
|
// let novi_category_id = ''; // Not in use yet or at all
|
||||||
|
|
||||||
|
|
||||||
|
/* *** ** * CHANGES END HERE * ** *** */
|
||||||
|
|
||||||
|
|
||||||
let idaa_ae_params = new URLSearchParams(document.location.search);
|
let idaa_ae_params = new URLSearchParams(document.location.search);
|
||||||
let idaa_ae_iframe_element = document.getElementById(
|
let idaa_ae_iframe_element = document.getElementById(
|
||||||
'ae_idaa_jitsi_meeting_iframe'
|
'ae_idaa_jitsi_meeting_iframe'
|
||||||
|
|||||||
Reference in New Issue
Block a user