feat: add announcement
- fix: bug encryption not working on newer version Signed-off-by: pakintada@gmail.com <Pakin>
This commit is contained in:
parent
d4eb3be886
commit
270faf6b34
4 changed files with 183 additions and 2 deletions
174
src/lib/components/AnnouncementDialog.svelte
Normal file
174
src/lib/components/AnnouncementDialog.svelte
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
<script lang="ts">
|
||||
import { onMount, onDestroy } from 'svelte';
|
||||
import { GlobalEventBus } from '$lib/core/utils/eventBus';
|
||||
import { XIcon } from '@lucide/svelte/icons';
|
||||
|
||||
export interface AnnouncementPayload {
|
||||
title?: string;
|
||||
subtitle?: string;
|
||||
message: string;
|
||||
buttonText?: string;
|
||||
type?: 'info' | 'warning' | 'error' | 'success';
|
||||
}
|
||||
|
||||
let visible = $state(false);
|
||||
let payload = $state<AnnouncementPayload | null>(null);
|
||||
let animating = $state(false);
|
||||
|
||||
function show(p: AnnouncementPayload) {
|
||||
payload = p;
|
||||
visible = true;
|
||||
// Trigger enter animation on next frame
|
||||
requestAnimationFrame(() => {
|
||||
animating = true;
|
||||
});
|
||||
}
|
||||
|
||||
function dismiss() {
|
||||
animating = false;
|
||||
// Wait for exit animation
|
||||
setTimeout(() => {
|
||||
visible = false;
|
||||
payload = null;
|
||||
}, 200);
|
||||
}
|
||||
|
||||
function handleKeydown(e: KeyboardEvent) {
|
||||
if (e.key === 'Escape' && visible) {
|
||||
dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
let unsubscribe: (() => void) | undefined;
|
||||
|
||||
onMount(() => {
|
||||
unsubscribe = GlobalEventBus.on('announce', (data: AnnouncementPayload) => {
|
||||
show(data);
|
||||
});
|
||||
// if (window) window.addEventListener('keydown', handleKeydown);
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
unsubscribe?.();
|
||||
// if (window) window.removeEventListener('keydown', handleKeydown);
|
||||
});
|
||||
|
||||
const typeStyles: Record<string, { border: string; badge: string; icon: string }> = {
|
||||
info: {
|
||||
border: 'border-blue-500',
|
||||
badge: 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200',
|
||||
icon: ''
|
||||
},
|
||||
warning: {
|
||||
border: 'border-amber-500',
|
||||
badge: 'bg-amber-100 text-amber-800 dark:bg-amber-900 dark:text-amber-200',
|
||||
icon: ''
|
||||
},
|
||||
error: {
|
||||
border: 'border-red-500',
|
||||
badge: 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200',
|
||||
icon: ''
|
||||
},
|
||||
success: {
|
||||
border: 'border-green-500',
|
||||
badge: 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200',
|
||||
icon: ''
|
||||
}
|
||||
};
|
||||
|
||||
function currentStyles() {
|
||||
return typeStyles[payload?.type ?? 'info'] ?? typeStyles.info;
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if visible}
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events a11y_no_static_element_interactions -->
|
||||
<div
|
||||
class="fixed inset-0 z-[9999] flex items-center justify-center p-4 sm:p-6 md:p-8"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="announcement-title"
|
||||
onclick={(e) => {
|
||||
// Close on backdrop click
|
||||
if (e.target === e.currentTarget) dismiss();
|
||||
}}
|
||||
>
|
||||
<!-- Backdrop -->
|
||||
<div
|
||||
class="absolute inset-0 bg-black/60 backdrop-blur-lg transition-opacity duration-200"
|
||||
class:opacity-100={animating}
|
||||
class:opacity-0={!animating}
|
||||
></div>
|
||||
|
||||
<!-- Card -->
|
||||
<div
|
||||
class="relative w-full max-w-lg overflow-hidden rounded-2xl border bg-white shadow-2xl transition-all duration-200 dark:border-neutral-700 dark:bg-neutral-900"
|
||||
class:opacity-100={animating}
|
||||
class:opacity-0={!animating}
|
||||
class:scale-100={animating}
|
||||
class:scale-95={!animating}
|
||||
>
|
||||
<!-- Colored top border accent -->
|
||||
<div class="h-1.5 w-full {currentStyles().border}" />
|
||||
|
||||
<div class="p-6 sm:p-8">
|
||||
<!-- Close button -->
|
||||
<button
|
||||
onclick={dismiss}
|
||||
class="absolute top-4 right-4 rounded-full p-1.5 text-neutral-400 transition-colors hover:bg-neutral-100 hover:text-neutral-600 dark:hover:bg-neutral-800 dark:hover:text-neutral-300"
|
||||
aria-label="Close announcement"
|
||||
>
|
||||
<XIcon size={20} />
|
||||
</button>
|
||||
|
||||
<!-- Type badge -->
|
||||
{#if payload?.type}
|
||||
<span
|
||||
class="mb-4 inline-block rounded-full px-3 py-1 text-xs font-semibold tracking-wider uppercase {currentStyles()
|
||||
.badge}"
|
||||
>
|
||||
{payload.type}
|
||||
</span>
|
||||
{/if}
|
||||
|
||||
<!-- Title -->
|
||||
{#if payload?.title}
|
||||
<h2
|
||||
id="announcement-title"
|
||||
class="pr-8 text-2xl font-bold text-neutral-900 dark:text-white"
|
||||
>
|
||||
{payload.title}
|
||||
</h2>
|
||||
{/if}
|
||||
|
||||
<!-- Subtitle -->
|
||||
{#if payload?.subtitle}
|
||||
<p class="mt-1 text-sm font-medium text-neutral-500 dark:text-neutral-400">
|
||||
{payload.subtitle}
|
||||
</p>
|
||||
{/if}
|
||||
|
||||
<!-- Message body -->
|
||||
{#if payload?.message}
|
||||
<div
|
||||
class="mt-4 text-base leading-relaxed whitespace-pre-wrap text-neutral-700 dark:text-neutral-300"
|
||||
>
|
||||
{payload.message}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Acknowledge / action button -->
|
||||
{#if payload?.buttonText !== undefined}
|
||||
<div class="mt-6 flex justify-end gap-3">
|
||||
<button
|
||||
onclick={dismiss}
|
||||
class="inline-flex items-center justify-center rounded-lg bg-neutral-900 px-5 py-2.5 text-sm font-semibold text-white shadow-sm transition-colors hover:bg-neutral-800 focus-visible:ring-2 focus-visible:ring-neutral-400 focus-visible:outline-none dark:bg-white dark:text-neutral-900 dark:hover:bg-neutral-200"
|
||||
>
|
||||
{payload.buttonText || 'Acknowledge'}
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
@ -50,6 +50,7 @@ import { handleSheetResponseFromNoti } from './sheetNotiHandler';
|
|||
import { env } from '$env/dynamic/public';
|
||||
import * as semver from 'semver';
|
||||
import { WebCryptoHelper } from '../utils/crypto';
|
||||
import { GlobalEventBus } from '../utils/eventBus';
|
||||
|
||||
export const messages = writable<string[]>([]);
|
||||
|
||||
|
|
@ -481,6 +482,10 @@ const handlers: Record<string, (payload: any) => void> = {
|
|||
raw_stream_end_price: (p) => {
|
||||
// End for price stream
|
||||
handleRawStreamEnd('price', p);
|
||||
},
|
||||
announce: (p) => {
|
||||
// Server-pushed announcement (e.g., closing maintenance)
|
||||
GlobalEventBus.emit('announce', p);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -498,7 +503,7 @@ export async function handleIncomingMessages(raw: string, clientPrivateKey: Cryp
|
|||
|
||||
return;
|
||||
}
|
||||
if (semver.satisfies(APP_VERSION, '^0.0.2')) {
|
||||
if (semver.satisfies(APP_VERSION, '>=0.0.2')) {
|
||||
// secured message decryption
|
||||
let sharedKeyStore = get(sharedKey);
|
||||
if (sharedKeyStore) {
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ export async function sendMessage(
|
|||
|
||||
// console.log('send v2', APP_VERSION, semver.satisfies(APP_VERSION, '^0.0.2'));
|
||||
|
||||
if (semver.satisfies(APP_VERSION, '^0.0.2')) {
|
||||
if (semver.satisfies(APP_VERSION, '>=0.0.2')) {
|
||||
// console.log('sending secured');
|
||||
let sharedKeyRes = get(sharedKey);
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@
|
|||
} from '$lib/helpers/cookie';
|
||||
import { connectToWebsocket } from '$lib/core/stores/websocketStore';
|
||||
import { GlobalEventBus } from '$lib/core/utils/eventBus';
|
||||
import AnnouncementDialog from '$lib/components/AnnouncementDialog.svelte';
|
||||
import * as semver from 'semver';
|
||||
import { env } from '$env/dynamic/public';
|
||||
|
||||
|
|
@ -91,4 +92,5 @@
|
|||
|
||||
<ModeWatcher />
|
||||
<Toaster />
|
||||
<AnnouncementDialog />
|
||||
{@render children()}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue