Agency client portal
Show each client the boards your agency shared with them, with a picture of each.
An agency's client portal already has the invoices, the contracts and the messages. The work itself, the wireframes, the moodboard and the roadmap, lives on MockFlow boards shared with the client. This example brings them into the portal: each client connects their own MockFlow account, and sees the boards shared with them, with a picture of each and a link to open it.
Download the code (Node.js 18 or later, no packages)
What it does
Lists the boards the client can open but does not own, newest first: the ones the agency shared with them.
Shows a picture of each board, exported once and reused until the board changes.
Links each board to MockFlow, where the client can view and comment with their role.
How it works
The client connects through the OAuth flow with the scopes
user:read boards:read.List their boards with
GET /boards?sort=updatedAt&order=desc. Boards shared with them are included, androletells them apart from their own.Export each board as a PNG and wait for the job. Reviewers can export, as in the app.
Cache the picture with the board's
updatedAt, and export again only when that changes.
Each picture is published at an unguessable address that anyone with the link can open, so show it only to the client it belongs to.
Run the example
Register an app in the OAuth apps tab of the developer console with the redirect URI http://localhost:3000/oauth/callback and the scopes user:read and boards:read. Then connect as someone a board has been shared with:
cp .env.example .env # paste the client ID and secret into .env
node server.js # open http://localhost:3000
The key code
const shared = [];
for await (const b of mf.paginate('/boards?sort=updatedAt&order=desc')) {
if (b.role !== 'owner') shared.push(b);
}
async function preview(mf, board) {
const cached = previews.get(board.id);
if (cached && cached.updatedAt === board.updatedAt) return cached.imageUrl;
const job = await mf.post(`/boards/${board.id}/export`, { target: 'board', scale: 1 });
const done = await mf.waitForJob(job);
previews.set(board.id, { updatedAt: board.updatedAt, imageUrl: done.result.imageUrl });
return done.result.imageUrl;
}
Adapt it
Freelancers: a project page per client with the latest designs.
Schools: a parent portal showing a class's shared boards.
Partners: a partner portal with the joint roadmap and launch plans.
What you need
An OAuth app with the scopes user:read and boards:read. Works for clients on every plan, including Basic.