Files
RedditBot/apps/dashboard/src/features/settings/settings.css
T
Jamie BruntonandClaude Opus 5 38d175e4a1 feat: push selected events to a phone over ntfy
Adds a Notifications tab to Settings so an operator can pick which events
reach their phone, published to a single self-hosted ntfy topic.

The notifier subscribes to the realtime event hub the dashboard already
consumes, so every notifiable event keeps exactly one publisher. Inbound
media had no event, so `media.received` is added and published from the
existing durable `gotImage` gate in the chat service — one notification
per conversation, never repeated when the room re-syncs. It feeds the
in-app notification bell as well.

- Best-effort delivery: a rejecting or unreachable ntfy server is logged
  and never propagated into the path that produced the event.
- Events buffered before the notifier attaches are skipped, so a restart
  cannot replay a backlog to somebody's phone.
- Settings are applied field by field onto the live config object, so a
  save takes effect without a restart.
- The access token is environment-owned (NTFY_AUTH_TOKEN) like the other
  secrets: stripped before the settings record is written, never returned
  by the API, which reports only `hasAuthToken`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 13:55:00 +01:00

76 lines
4.3 KiB
CSS

.settings-page { max-width: 72rem; }
.settings-navigation { display: grid; grid-template-columns: 1fr; gap: .5rem; margin-bottom: 1rem; }
.settings-navigation button { display: grid; gap: .15rem; padding: .8rem; color: var(--muted); text-align: left; background: var(--surface); border: 1px solid var(--border); border-radius: .75rem; }
.settings-navigation button strong { color: #e9f1ec; }
.settings-navigation button small { font-size: .72rem; }
.settings-navigation button.is-active { background: var(--accent-dark); border-color: #28593d; }
.settings-navigation button:focus-visible, .settings-field input:focus-visible, .settings-field select:focus-visible, .settings-field textarea:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.settings-loading { min-height: 24rem; }
.settings-section { display: grid; gap: 1rem; }
.settings-copy { color: var(--muted); }
.settings-grid { display: grid; gap: 1rem; }
.settings-field { display: grid; gap: .4rem; color: var(--muted); font-size: .8rem; font-weight: 750; }
.settings-field input, .settings-field select, .settings-field textarea { width: 100%; min-height: 2.7rem; padding: .55rem .7rem; color: #e9f1ec; background: var(--surface-raised); border: 1px solid var(--border); border-radius: .6rem; }
.settings-field textarea { resize: vertical; }
.settings-field [aria-invalid="true"] { border-color: var(--danger); }
.field-error, .save-error { color: var(--danger); }
.toggle-field { display: flex; align-items: flex-start; gap: .8rem; padding: .9rem; background: var(--surface-raised); border: 1px solid var(--border); border-radius: .75rem; }
.toggle-field input { width: 1.15rem; height: 1.15rem; margin-top: .15rem; accent-color: var(--accent); }
.toggle-field span { display: grid; gap: .2rem; }
.toggle-field small { color: var(--muted); }
.settings-events { display: grid; gap: .6rem; padding: 1rem; border: 1px solid var(--border); border-radius: .75rem; }
.settings-events legend { padding: 0 .4rem; color: var(--accent); font-weight: 800; }
.subreddit-heading { display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: space-between; gap: 1rem; padding-top: .5rem; }
.subreddit-heading h3 { margin: 0 0 .25rem; }
.subreddit-heading p { margin: 0; color: var(--muted); }
.subreddit-list { display: grid; gap: .75rem; }
.subreddit-row { display: grid; gap: .75rem; min-width: 0; padding: 1rem; border: 1px solid var(--border); border-radius: .75rem; }
.subreddit-row legend { padding: 0 .4rem; color: var(--accent); font-weight: 800; }
.remove-subreddit { align-self: end; }
.settings-save-bar { position: sticky; bottom: 1rem; display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 1rem; padding: .9rem 1rem; margin-top: 1rem; background: rgba(23, 35, 30, .97); border: 1px solid var(--border); border-radius: .8rem; box-shadow: 0 1rem 3rem rgba(0, 0, 0, .3); }
.settings-save-bar > div:first-child { display: grid; gap: .15rem; color: var(--muted); }
.save-success { color: var(--accent); }
.save-warning { color: var(--warning); }
.risk-list { display: grid; gap: .5rem; padding-left: 1.2rem; color: var(--warning); }
.access-actions { display: flex; flex-wrap: wrap; gap: .75rem; }
.access-actions a { display: inline-flex; align-items: center; text-decoration: none; }
.settings-notice { padding: .75rem; border-radius: .65rem; }
.settings-notice.success { color: var(--success-text); background: var(--success-bg); }
.settings-notice.warning { color: var(--warning-text); background: var(--warning-bg); }
.settings-notice.error { color: var(--danger-text); background: var(--danger-bg); }
@media (min-width: 42rem) {
.settings-navigation { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.settings-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.subreddit-row { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.scripted-field { grid-column: 1 / -1; }
}
@media (min-width: 70rem) {
.settings-navigation { grid-template-columns: repeat(5, minmax(0, 1fr)); }
}
.reddit-account-list {
display: grid;
gap: .65rem;
}
.reddit-account-row {
align-items: center;
border: 1px solid var(--border);
border-radius: .75rem;
display: flex;
gap: 1rem;
justify-content: space-between;
padding: .75rem;
}
.reddit-account-row.is-active {
border-color: var(--accent);
}
.reddit-account-row > div {
display: grid;
gap: .2rem;
}