Kp Index
diff --git a/PROJECTS/advanced/monitor-the-situation-dashboard/frontend/src/pages/panels/WikiPanel.module.scss b/PROJECTS/advanced/monitor-the-situation-dashboard/frontend/src/pages/panels/WikiPanel.module.scss
new file mode 100644
index 00000000..2cd0b22e
--- /dev/null
+++ b/PROJECTS/advanced/monitor-the-situation-dashboard/frontend/src/pages/panels/WikiPanel.module.scss
@@ -0,0 +1,61 @@
+// ©AngelaMos | 2026
+// WikiPanel.module.scss
+
+.list {
+ padding: 0 8px 4px;
+}
+
+.empty {
+ padding: var(--row-py) 0;
+ font-family: var(--font-mono);
+ font-size: var(--type-body);
+ color: var(--fg-3);
+}
+
+.row {
+ display: flex;
+ align-items: baseline;
+ justify-content: space-between;
+ gap: 8px;
+ padding: var(--row-py) 0;
+ border-bottom: 1px solid var(--fg-4);
+
+ &:last-child {
+ border-bottom: none;
+ }
+}
+
+.headline {
+ font-size: var(--type-body);
+ color: var(--fg-1);
+ line-height: 1.35;
+ flex: 1;
+ min-width: 0;
+ overflow: hidden;
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+
+ &[href] {
+ text-decoration: none;
+
+ &:hover {
+ color: var(--fg-1);
+ text-decoration: underline;
+ text-underline-offset: 2px;
+ }
+
+ &:focus-visible {
+ outline: 1px solid var(--fg-2);
+ outline-offset: 1px;
+ }
+ }
+}
+
+.ts {
+ font-family: var(--font-mono);
+ font-size: var(--type-label);
+ color: var(--fg-3);
+ flex-shrink: 0;
+ letter-spacing: var(--letter-spacing-label);
+}
diff --git a/PROJECTS/advanced/monitor-the-situation-dashboard/frontend/src/pages/panels/WikiPanel.tsx b/PROJECTS/advanced/monitor-the-situation-dashboard/frontend/src/pages/panels/WikiPanel.tsx
new file mode 100644
index 00000000..da1cdebe
--- /dev/null
+++ b/PROJECTS/advanced/monitor-the-situation-dashboard/frontend/src/pages/panels/WikiPanel.tsx
@@ -0,0 +1,72 @@
+// ©AngelaMos | 2026
+// WikiPanel.tsx
+
+import { useFreshness } from '@/stores/freshness'
+import { useTicker } from '@/stores/ticker'
+import { Panel } from './Panel'
+import styles from './WikiPanel.module.scss'
+
+const MAX_ITEMS = 6
+const STALE_AFTER_MS = 3_600_000
+const MS_PER_SECOND = 1_000
+const SECONDS_PER_MINUTE = 60
+const SECONDS_PER_HOUR = 3_600
+
+export function WikiPanel(): React.ReactElement {
+ const all = useTicker((s) => s.items)
+ const items = all
+ .filter((i) => i.source === 'Wikipedia')
+ .slice(-MAX_ITEMS)
+ .reverse()
+
+ const lastTickAt = useFreshness((s) => s.ts.wiki)
+ const isStale =
+ items.length === 0
+ ? undefined
+ : lastTickAt !== undefined && Date.now() - lastTickAt > STALE_AFTER_MS
+
+ return (
+
+
+ {items.length === 0 ? (
+
—
+ ) : (
+ items.map((item) => (
+
+ ))
+ )}
+
+
+ )
+}
+
+WikiPanel.displayName = 'WikiPanel'
+
+function fmtRel(ts: number): string {
+ const diff = (Date.now() - ts) / MS_PER_SECOND
+ if (diff < SECONDS_PER_MINUTE) return `${Math.floor(diff)}s`
+ if (diff < SECONDS_PER_HOUR) return `${Math.floor(diff / SECONDS_PER_MINUTE)}m`
+ return `${Math.floor(diff / SECONDS_PER_HOUR)}h`
+}
diff --git a/PROJECTS/advanced/monitor-the-situation-dashboard/frontend/src/pages/panels/shared/StaleIndicator.module.scss b/PROJECTS/advanced/monitor-the-situation-dashboard/frontend/src/pages/panels/shared/StaleIndicator.module.scss
index f2597471..e5fbeb3d 100644
--- a/PROJECTS/advanced/monitor-the-situation-dashboard/frontend/src/pages/panels/shared/StaleIndicator.module.scss
+++ b/PROJECTS/advanced/monitor-the-situation-dashboard/frontend/src/pages/panels/shared/StaleIndicator.module.scss
@@ -12,10 +12,10 @@
background: var(--ok);
}
-.stale {
+.batch {
background: var(--amber);
}
-.unknown {
- background: var(--fg-4);
+.stale {
+ background: var(--fg-3);
}
diff --git a/PROJECTS/advanced/monitor-the-situation-dashboard/frontend/src/pages/panels/shared/StaleIndicator.tsx b/PROJECTS/advanced/monitor-the-situation-dashboard/frontend/src/pages/panels/shared/StaleIndicator.tsx
index 7b981905..164866ba 100644
--- a/PROJECTS/advanced/monitor-the-situation-dashboard/frontend/src/pages/panels/shared/StaleIndicator.tsx
+++ b/PROJECTS/advanced/monitor-the-situation-dashboard/frontend/src/pages/panels/shared/StaleIndicator.tsx
@@ -6,18 +6,23 @@ import styles from './StaleIndicator.module.scss'
export interface StaleIndicatorProps {
stale?: boolean
lastTickAt?: number
+ batch?: boolean
}
export function StaleIndicator({
stale,
lastTickAt,
+ batch,
}: StaleIndicatorProps): React.ReactElement {
- const className =
- stale === undefined
- ? `${styles.dot} ${styles.unknown}`
- : stale
- ? `${styles.dot} ${styles.stale}`
- : `${styles.dot} ${styles.live}`
+ let state: 'live' | 'batch' | 'stale'
+ if (stale === undefined || stale === true) {
+ state = 'stale'
+ } else if (batch) {
+ state = 'batch'
+ } else {
+ state = 'live'
+ }
+ const className = `${styles.dot} ${styles[state]}`
const title = lastTickAt
? `Last update ${new Date(lastTickAt).toISOString().slice(11, 19)} UTC`
diff --git a/PROJECTS/advanced/monitor-the-situation-dashboard/frontend/src/pages/register/index.tsx b/PROJECTS/advanced/monitor-the-situation-dashboard/frontend/src/pages/register/index.tsx
index f0f003f7..ec7fd310 100644
--- a/PROJECTS/advanced/monitor-the-situation-dashboard/frontend/src/pages/register/index.tsx
+++ b/PROJECTS/advanced/monitor-the-situation-dashboard/frontend/src/pages/register/index.tsx
@@ -4,6 +4,7 @@
*/
import { useState } from 'react'
+import { FiArrowLeft } from 'react-icons/fi'
import { LuEye, LuEyeOff } from 'react-icons/lu'
import { Link, useNavigate } from 'react-router-dom'
import { toast } from 'sonner'
@@ -35,6 +36,7 @@ export function Component(): React.ReactElement {
const { registerEmail, setRegisterEmail, clearRegisterForm } =
useAuthFormStore()
+ const [name, setName] = useState('')
const [password, setPassword] = useState('')
const [confirmPassword, setConfirmPassword] = useState('')
const [showPassword, setShowPassword] = useState(false)
@@ -45,6 +47,7 @@ export function Component(): React.ReactElement {
const result = registerFormSchema.safeParse({
email: registerEmail,
+ name,
password,
confirmPassword,
})
@@ -56,11 +59,14 @@ export function Component(): React.ReactElement {
}
register.mutate(
- { email: result.data.email, password: result.data.password },
+ {
+ email: result.data.email,
+ name: result.data.name,
+ password: result.data.password,
+ },
{
onSuccess: () => {
clearRegisterForm()
- toast.success('Account created successfully')
navigate(ROUTES.LOGIN)
},
}
@@ -69,6 +75,15 @@ export function Component(): React.ReactElement {
return (
+
navigate(-1)}
+ aria-label="Go back"
+ >
+
+ Back
+
Sign up
@@ -76,6 +91,20 @@ export function Component(): React.ReactElement {