Skip to content

feat: buffer integration - #5

Merged
AdrianOrlow merged 2 commits into
mainfrom
buffer-integration
Sep 28, 2026
Merged

AdrianOrlow merged 2 commits into
mainfrom
buffer-integration

Conversation

@AdrianOrlow

Copy link
Copy Markdown
Member
image

Changes

What has been done in this PR?

  • Buffer publishing in the Marketing module – the graphic generator and publishing are now one view: a new "Publikacja" section sits under the format previews. It talks only to console-publisher-worker (https://console.gi.org.pl/api/buffer/*), through a new src/services/buffer/ client (Axios + Zod schemas mirroring the worker's contract).
  • Post composer (ExportLab/BufferPublisher/PostComposer/):
    • Channel picker with Font Awesome brand icons; one tap selects a channel.
    • One shared post (format + caption) by default; each channel can be given its own image/caption ("Dostosuj" / "Wspólna").
    • Validation per network: caption length limits (X 280, Bluesky 300, Threads/Mastodon 500, Instagram 2200, LinkedIn 3000, Facebook 5000), 9:16 blocked for Instagram (the worker only sends feed posts), and a warning that links aren't clickable on Instagram.
    • Timing: queue / now / scheduled (1 minute – 30 days, same limits as the worker).
    • Channels are published one by one; a failed channel can be retried alone. Retries reuse the uploaded image and the worker's idempotency key (requestId), so no duplicate posts are created.
    • 4:5 is the default format.
  • Login – a Cloudflare Access popup (/api/buffer/login → new /buffer-auth callback page). Session state lives in a Zustand store (useBufferSession), because the sidebar and the composer both need it and login is a multi-step popup flow. The session is checked once on app load in ConsoleShell. An unauthenticated request is redirected by Access to its login page, which the browser reports as a network error; this is treated as "logged out", not as an error.
  • Sidebar – the avatar/"Publiczny" footer is replaced by an account control: a "Zaloguj się" button, or the user's email plus an icon-only logout button (Access /cdn-cgi/access/logout). On mobile it sits next to the logo.
  • Navigation – ConsoleModule now supports optional subModules: the sidebar expands them under the active module and the breadcrumb shows them. Marketing doesn't use this after merging publishing into one view; the support is covered by tests using a test module.
  • Preview cards – the share button (and canShareFiles) is removed. Cards are now just the image, with the format label and a download button in the corners.
  • Stale previews fix – the composer keeps the last finished previews while the generator re-renders, so editing the graphic doesn't reset the post draft or show broken images.
  • Other – EditorPanel gets an optional aside slot; the dashboard card description is updated; vite.config.ts allows .gi.org.pl hosts for the dev server.
  • New dependency: @fortawesome/free-brands-svg-icons (channel icons) – needs Technical Leader approval per our standards.
  • Out of scope: Instagram Stories. The worker hardcodes type: "post" for Instagram and its publish schema is strict, so Stories need a worker change first.

How to test

Describe how to test the changes manually

  • Open /marketing while logged out: generating and downloading graphics works, and both the Publikacja section and the sidebar show "Zaloguj się".
  • Click "Zaloguj się": the Access login popup opens. After logging in, the sidebar shows your email + logout button, and the composer lists the channels allowed by the worker.
  • Select channels, write a caption, and check:
    • the per-network counters turn red over the limit (e.g. >280 characters with X selected);
    • a link in the caption shows the Instagram warning;
    • picking 9:16 with Instagram selected blocks publishing, with the reason shown on the Instagram row.
  • Use "Dostosuj" on one channel, change its format/caption, then "Wspólna" to go back to the shared post.
  • Publish to a test channel with its Buffer queue paused, once for each timing option (queue / now / scheduled). Confirm exactly one post per channel in Buffer, with the right PNG and caption.
  • Edit the graphic after writing a post: the draft stays and the thumbnails update when rendering finishes.
  • Log out from the sidebar: the session is cleared and "Zaloguj się" is shown again.
  • Check the page at mobile width (≈390px).
  • Note: calls from localhost to the worker are blocked by CORS, so on localhost you'll always see the logged-out state. To test the logged-in flow, run on a *.gi.org.pl host. The e2e tests stub the worker (e2e/bufferWorker.ts).

Checklist

Check if the code follows the standards

  • Follows front-end standards – the new dependency above still needs TL approval
  • No unused code / console.log / leftover comments – the only thing no module uses yet is the subModules navigation support (tested)
  • Resolved merge conflicts (if applicable)
  • Added / updated unit tests (if applicable) – 239 tests; coverage 99.0% statements / 97.5% branches / 99.2% functions / 99.7% lines; e2e happy paths added for publishing and logged-out download
  • Added / updated storybook stories (if applicable) – N/A: all new components are Marketing-only, none are shared

@AdrianOrlow AdrianOrlow self-assigned this Sep 28, 2026
@AdrianOrlow
AdrianOrlow merged commit 056489a into main Sep 28, 2026
4 checks passed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 53829ef452

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +58 to +65
} catch (cause) {
setResult(channelId, {
status: "error",
message:
cause instanceof Error
? cause.message
: "Nie udało się opublikować.",
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Return to login when the Buffer session expires

If the Cloudflare Access session expires after the initial app-load check, an upload or post request reaches this catch, but only the channel result is updated; the global session remains connected, so the composer continues showing retry controls and each retry fails without offering a login flow. Recheck or invalidate the Buffer session when a publishing request reports an authentication failure so the login UI becomes available again.

Useful? React with 👍 / 👎.

Comment on lines +62 to +65
<PublishSuccess
channels={selected}
mode={draft.mode}
dueAt={draft.dueAt}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve per-channel timing in the success summary

When one channel succeeds and another fails, the timing controls become editable while retries target only the failed channel. If the operator changes from scheduled publishing to “Teraz” before retrying, the first channel remains scheduled, but this final screen uses the current global mode and lists both channels as published now. Either freeze timing after the first success or summarize each channel's actual submitted mode and time.

Useful? React with 👍 / 👎.

Comment on lines +24 to +28
} else if (text.length > info.maxLength) {
issues.push({
code: "tooLong",
level: "error",
message: `Treść jest za długa: ${text.length} z ${info.maxLength} znaków.`,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Count X text with platform weighting before blocking

For X channels, raw JavaScript string length does not match the platform's 280-character calculation: URLs are counted at their fixed shortened length, while some scripts and composed emoji are weighted differently. A common caption with roughly 250 Latin characters plus a long tracking URL can therefore be rejected here even though it fits on X, while other inputs can pass locally and fail at Buffer. Use a service-specific weighted counter for X instead of text.length.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant