Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/www/public/integrations/brex.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions apps/www/public/integrations/chargebee.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions apps/www/public/integrations/datadog.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions apps/www/public/integrations/front.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions apps/www/public/integrations/intercom.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions apps/www/public/integrations/linear.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions apps/www/public/integrations/maxio.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions apps/www/public/integrations/mercury.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions apps/www/public/integrations/mixpanel-amplitude.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions apps/www/public/integrations/modern-treasury.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions apps/www/public/integrations/netsuite.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions apps/www/public/integrations/pagerduty.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions apps/www/public/integrations/posthog.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions apps/www/public/integrations/quickbooks-online.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions apps/www/public/integrations/recurly.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions apps/www/public/integrations/sage-intacct.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions apps/www/public/integrations/segment.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions apps/www/public/integrations/sentry.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions apps/www/public/integrations/stripe-billing.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions apps/www/public/integrations/xero.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions apps/www/public/integrations/zendesk.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
167 changes: 167 additions & 0 deletions apps/www/src/app/(marketing)/integrations/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import fs from "fs";
import path from "path";
import { fileURLToPath } from "url";
import type { Metadata } from "next";
import Image from "next/image";
import Link from "next/link";
import { ArrowUpRight } from "lucide-react";
import { PageShell } from "@/components/site/PageShell";
import { PageEnter } from "@/components/site/PageEnter";
import { PageMast } from "@/components/v2/PageMast";
import { INTEGRATIONS, type Integration } from "@/lib/integrations";

const publicIntegrationsDir = path.join(
path.dirname(fileURLToPath(import.meta.url)),
"../../../../public/integrations",
);

const availableLogos = new Set(
fs.existsSync(publicIntegrationsDir)
? fs.readdirSync(publicIntegrationsDir).filter((fileName) => fileName.endsWith(".svg"))
: [],
);

const statusStyles: Record<Integration["status"], string> = {
live: "bg-emerald-100 text-emerald-900",
"coming-soon": "bg-amber-100 text-amber-900",
"on-request": "bg-slate-100 text-slate-900",
};

const statusLabel: Record<Integration["status"], string> = {
live: "Live",
"coming-soon": "Coming soon",
"on-request": "On request",
};

function getInitials(name: string) {
return name
.replace(/[^A-Za-z0-9]/g, "")
.slice(0, 2)
.toUpperCase();
}

function hasLogo(slug: string) {
return availableLogos.has(`${slug}.svg`);
}

const categoryGroups = INTEGRATIONS.reduce<Record<string, Integration[]>>((groups, integration) => {
(groups[integration.category] ??= []).push(integration);
return groups;
}, {});

const categories = Object.entries(categoryGroups);

export const metadata: Metadata = {
title: "Integrations | Useroutr",
description:
"Connect Useroutr to your accounting, treasury, billing, support, and dev tools.",
};

export default async function IntegrationsPage() {
return (
<PageShell>
<PageEnter>
<PageMast
eyebrow="Integrations"
title="Plugged into the rails you already use."
description="One integration to Useroutr, fifty integrations to the rest of your stack. Connect Useroutr to your accounting, treasury, billing, support, and dev tools. Most integrations are read-only sync; some support write-back for refunds, reconciliation, and payouts."
/>

<section className="border-t border-rule py-20 md:py-28">
<div className="container-x">
<div className="space-y-20">
{categories.map(([category, items]) => (
<div key={category} className="space-y-8">
<h2
className="text-[24px] leading-[1.08] tracking-[-0.03em] text-ink md:text-[32px]"
style={{ fontFamily: "var(--font-display)", fontWeight: 600 }}
>
{category}
</h2>
<div className="grid gap-5 sm:grid-cols-2 xl:grid-cols-3">
{items.map((integration) => (
<div
key={integration.id}
className="rounded-[28px] border border-rule bg-bg-card p-7"
>
<div className="flex items-start gap-4">
<div className="flex h-14 w-14 items-center justify-center rounded-3xl bg-bg">
{hasLogo(integration.logoSlug) ? (
<Image
src={`/integrations/${integration.logoSlug}.svg`}
alt={`${integration.name} logo`}
width={40}
height={40}
className="max-h-10 max-w-10"
/>
) : (
<div className="grid h-12 w-12 place-items-center rounded-2xl bg-slate-100 text-[11px] font-semibold uppercase tracking-[0.16em] text-ink-3">
{getInitials(integration.name)}
</div>
)}
</div>
<div className="min-w-0 flex-1">
<div className="flex flex-wrap items-center gap-3">
<h3
className="text-[18px] leading-[1.18] tracking-[-0.02em] text-ink"
style={{ fontFamily: "var(--font-display)", fontWeight: 600 }}
>
{integration.name}
</h3>
<span
className={`rounded-full px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.12em] ${statusStyles[integration.status]}`}
>
{statusLabel[integration.status]}
</span>
</div>
<p className="mt-4 text-[15px] leading-relaxed text-ink-3">
{integration.description || "More details coming soon."}
</p>
</div>
</div>
</div>
))}
</div>
</div>
))}

<div className="grid gap-5 lg:grid-cols-2">
<div className="rounded-[28px] border border-rule bg-bg-card p-8">
<p className="text-[18px] font-semibold text-ink">Don't see yours?</p>
<p className="mt-3 text-[15px] leading-relaxed text-ink-3">
Request an integration from the Useroutr team, and we’ll follow up with next steps.
</p>
<div className="mt-6">
<Link
href="mailto:integrations@useroutr.com"
className="inline-flex items-center gap-2 text-[15px] text-ink transition hover:text-ink-2"
>
Request an integration
<ArrowUpRight className="size-4" strokeWidth={1.6} />
</Link>
</div>
</div>

<div className="rounded-[28px] border border-rule bg-bg-card p-8">
<p className="text-[18px] font-semibold text-ink">Build your own</p>
<p className="mt-3 text-[15px] leading-relaxed text-ink-3">
Use Useroutr's API docs to connect your internal systems or build a custom integration.
</p>
<div className="mt-6">
<Link
href="/docs/api"
className="inline-flex items-center gap-2 text-[15px] text-ink transition hover:text-ink-2"
>
Read the API docs
<ArrowUpRight className="size-4" strokeWidth={1.6} />
</Link>
</div>
</div>
</div>
</div>
</div>
</section>
</PageEnter>
</PageShell>
);
}
6 changes: 6 additions & 0 deletions apps/www/src/app/sitemap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ export default function sitemap(): MetadataRoute.Sitemap {
changeFrequency: "monthly",
priority: 0.9,
},
{
url: `${baseUrl}/integrations`,
lastModified: now,
changeFrequency: "monthly",
priority: 0.8,
},
...useCases.map((slug) => ({
url: `${baseUrl}/use-cases/${slug}`,
lastModified: now,
Expand Down
Loading