Docs
Remix
Works the same in React Router 7. The widget in one tag, the changelog from a loader, and verified customers from your session.
Create a project
Sign in, create a project, and note its slug.
Add the widget
npx shipnoted initIt edits
app/root.tsx. By hand, put the tag next to<Scripts />:app/root.tsx
export function Layout({ children }: { children: React.ReactNode }) { return ( <html lang="en"> <head><Meta /><Links /></head> <body> {children} <ScrollRestoration /> <script src="https://shipnoted.com/widget.js" data-project="your-slug" async /> <Scripts /> </body> </html> ) }
A changelog route#
Load the first page in the loader, so the page arrives rendered, then let the hook handle “older releases”.
npm i @shipnoted/reactapp/routes/changelog.tsx
import { useLoaderData } from "@remix-run/react"
import { createPublicClient } from "@shipnoted/react/core"
import { ShipnotedProvider, useChangelog } from "@shipnoted/react"
export async function loader() {
const client = createPublicClient({ project: "your-slug", baseUrl: "https://shipnoted.com" })
return { releases: (await client.releases({ limit: 10 })).releases }
}
function List({ initialData }) {
const { releases, hasMore, loadMore } = useChangelog({ initialData })
return (
<>
{releases.map((r) => <article key={r.id}><h2>{r.title}</h2><p>{r.summary}</p></article>)}
{hasMore && <button onClick={loadMore}>Older</button>}
</>
)
}
export default function Changelog() {
const { releases } = useLoaderData<typeof loader>()
return <ShipnotedProvider project="your-slug" baseUrl="https://shipnoted.com"><List initialData={releases} /></ShipnotedProvider>
}Verified customers#
Sign a token from your session in a resource route, with the identify secret from Install & connect:
app/routes/shipnoted-token.ts
import { SignJWT } from "jose"
import { getUser } from "~/session.server"
export async function loader({ request }: { request: Request }) {
const user = await getUser(request)
if (!user) return Response.json({ token: null })
const token = await new SignJWT({ email: user.email, name: user.name, plan: user.plan, mrr: user.mrr, company: user.company })
.setProtectedHeader({ alg: "HS256" })
.setSubject(user.id)
.setExpirationTime("1h")
.sign(new TextEncoder().encode(process.env.SHIPNOTED_IDENTIFY_SECRET!))
return Response.json({ token })
}// in a useEffect on the client
fetch("/shipnoted-token").then((r) => r.json()).then(({ token }) => token && window.Shipnoted?.identify(token))