Imported from github.com/cyguin/announce
Find a file
2026-05-18 23:25:39 -07:00
src fix #1: [smoke-test] Test coverage review: 11 source files, 1 test files (9.1% 2026-05-18 23:25:39 -07:00
.gitignore gsd: pre-implementation lint/format cleanup 2026-05-16 12:50:51 -07:00
DEV_LOG.md gsd: pre-implementation lint/format cleanup 2026-05-16 12:50:51 -07:00
LICENSE chore: normalize cyguin package metadata 2026-04-21 09:26:39 -05:00
package-lock.json fix #1: [smoke-test] Test coverage review: 11 source files, 1 test files (9.1% 2026-05-18 23:25:39 -07:00
package.json fix #1: [smoke-test] Test coverage review: 11 source files, 1 test files (9.1% 2026-05-18 23:25:39 -07:00
README.md gsd: pre-implementation lint/format cleanup 2026-05-16 12:50:51 -07:00
tsconfig.json chore: initial publish 2026-04-05 08:07:57 -05:00
tsup.config.ts gsd: pre-implementation lint/format cleanup 2026-05-16 12:50:51 -07:00

@cyguin/announce

DB-backed, per-user dismissible announcement banner for Next.js apps.

Install

npm install @cyguin/announce

Setup

1. Create the API route

Create app/api/announce/[...cyguin]/route.ts in your Next.js app:

import { createAnnounceHandler } from "@cyguin/announce/next";
import Database from "better-sqlite3";
import { createSQLiteAdapter } from "@cyguin/announce/adapters/sqlite";

const db = new Database("announce.db");
db.pragma("journal_mode = WAL");
const adapter = createSQLiteAdapter(db);

const handler = createAnnounceHandler({
  adapter,
  options: {
    secret: process.env.ANNOUNCE_SECRET,
  },
});

export { handler as GET, handler as POST, handler as DELETE };

2. Run migrations

CREATE TABLE IF NOT EXISTS announcements (
  id TEXT PRIMARY KEY,
  title TEXT NOT NULL,
  body TEXT NOT NULL,
  active_from INTEGER,
  active_until INTEGER,
  created_at INTEGER NOT NULL
);

CREATE TABLE IF NOT EXISTS announcement_dismissals (
  user_id TEXT NOT NULL,
  announcement_id TEXT NOT NULL,
  dismissed_at INTEGER NOT NULL,
  PRIMARY KEY (user_id, announcement_id)
);

3. Set the admin API secret

ANNOUNCE_SECRET=change-me

Creating and deleting announcements requires Authorization: Bearer $ANNOUNCE_SECRET. Listing active announcements and user dismissals remain public widget actions.

4. Add the banner

import { AnnouncementBanner } from "@cyguin/announce/react";

export default function Layout({ children }: { children: React.ReactNode }) {
  return (
    <>
      <AnnouncementBanner
        apiBase="/api/announce"
        userId={currentUser.id}
        theme="dark"
      />
      {children}
    </>
  );
}

Postgres Setup

import { createAnnounceHandler } from "@cyguin/announce/next";
import postgres from "postgres";
import { createPostgresAdapter } from "@cyguin/announce/adapters/postgres";

const sql = postgres(process.env.DATABASE_URL!);
const adapter = createPostgresAdapter(sql);

const handler = createAnnounceHandler({
  adapter,
  options: {
    secret: process.env.ANNOUNCE_SECRET,
  },
});

export { handler as GET, handler as POST, handler as DELETE };

API Routes

Method Route Description
GET /api/announce/cyguin?userId=xxx List active announcements for user
POST /api/announce/cyguin Create announcement (requires Bearer token)
DELETE /api/announce/cyguin/:id Delete announcement (requires Bearer token)
POST /api/announce/cyguin/:id/dismiss?userId=xxx Dismiss for user

Theming

AnnouncementBanner defaults to the cyguin dark theme. Pass theme="light" to opt into the light theme.

Use --cyguin-* CSS custom properties on .cyguin-announce-banner:

.cyguin-announce-banner {
  --cyguin-bg: #ffffff;
  --cyguin-fg: #0a0a0a;
  --cyguin-accent: #f5a800;
  --cyguin-border: #e5e5e5;
  --cyguin-radius: 6px;
  --cyguin-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
}

Switch to light theme with the theme prop:

<AnnouncementBanner theme="light" ... />

Dark theme applies these overrides automatically:

--cyguin-bg: #0a0a0a;
--cyguin-bg-subtle: #1a1a1a;
--cyguin-border: #2a2a2a;
--cyguin-fg: #f5f5f5;
--cyguin-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);

Configuration

createAnnounceHandler({
  adapter,
  options: {
    defaultActiveDays: 7, // auto-expire after N days
    maxActive: 3, // max simultaneous active announcements
    secret: process.env.ANNOUNCE_SECRET,
  },
});

License

MIT