Rrlabs 2.0

This commit is contained in:
Flatlogic Bot 2026-05-08 01:48:35 +00:00
parent bcedba8574
commit 3373401682
20 changed files with 3020 additions and 750 deletions

View File

@ -1,88 +1,77 @@
import React from 'react'
import { mdiLogout, mdiClose } from '@mdi/js'
import BaseIcon from './BaseIcon'
import AsideMenuList from './AsideMenuList'
import { MenuAsideItem } from '../interfaces'
import { useAppSelector } from '../stores/hooks'
import React from 'react';
import { mdiClose } from '@mdi/js';
import Link from 'next/link';
import { useAppDispatch } from '../stores/hooks';
import { createAsyncThunk } from '@reduxjs/toolkit';
import axios from 'axios';
import BaseIcon from './BaseIcon';
import AsideMenuList from './AsideMenuList';
import RrlabsBrand from './RrlabsBrand';
import { MenuAsideItem } from '../interfaces';
import { useAppSelector } from '../stores/hooks';
type Props = {
menu: MenuAsideItem[]
className?: string
onAsideLgCloseClick: () => void
}
menu: MenuAsideItem[];
className?: string;
onAsideLgCloseClick: () => void;
};
type Organization = {
id: string;
name: string;
};
export default function AsideMenuLayer({ menu, className = '', ...props }: Props) {
const corners = useAppSelector((state) => state.style.corners);
const asideStyle = useAppSelector((state) => state.style.asideStyle)
const asideBrandStyle = useAppSelector((state) => state.style.asideBrandStyle)
const asideScrollbarsStyle = useAppSelector((state) => state.style.asideScrollbarsStyle)
const darkMode = useAppSelector((state) => state.style.darkMode)
const asideStyle = useAppSelector((state) => state.style.asideStyle);
const asideScrollbarsStyle = useAppSelector((state) => state.style.asideScrollbarsStyle);
const darkMode = useAppSelector((state) => state.style.darkMode);
const { currentUser } = useAppSelector((state) => state.auth);
const [organizations, setOrganizations] = React.useState<Organization[]>([]);
const handleAsideLgCloseClick = (e: React.MouseEvent) => {
e.preventDefault()
props.onAsideLgCloseClick()
}
const dispatch = useAppDispatch();
const { currentUser } = useAppSelector((state) => state.auth);
const organizationsId = currentUser?.organizations?.id;
const [organizations, setOrganizations] = React.useState(null);
const fetchOrganizations = createAsyncThunk('/org-for-auth', async () => {
try {
const response = await axios.get('/org-for-auth');
setOrganizations(response.data);
return response.data;
} catch (error) {
console.error(error.response);
throw error;
}
});
e.preventDefault();
props.onAsideLgCloseClick();
};
React.useEffect(() => {
dispatch(fetchOrganizations());
}, [dispatch]);
const loadOrganizations = async () => {
try {
const response = await axios.get('org-for-auth');
setOrganizations(Array.isArray(response.data) ? response.data : []);
} catch (error) {
console.error('Failed to load organizations for aside menu', error);
}
};
let organizationName = organizations?.find(item => item.id === organizationsId)?.name;
if(organizationName?.length > 25){
organizationName = organizationName?.substring(0, 25) + '...';
loadOrganizations();
}, []);
const organizationsId = currentUser?.organizations?.id;
let organizationName = organizations.find((item) => item.id === organizationsId)?.name;
if (organizationName && organizationName.length > 25) {
organizationName = `${organizationName.substring(0, 25)}...`;
}
return (
<aside
id='asideMenu'
className={`${className} zzz lg:py-2 lg:pl-2 w-60 fixed flex z-40 top-0 h-screen transition-position overflow-hidden`}
id="asideMenu"
className={`${className} zzz fixed top-0 z-40 flex h-screen w-60 overflow-hidden transition-position lg:pl-2 lg:py-2`}
>
<div
className={`flex-1 flex flex-col overflow-hidden dark:bg-dark-900 ${asideStyle} ${corners}`}
className={`${asideStyle} flex flex-1 flex-col overflow-hidden border border-white/10 bg-slate-950/85 shadow-[0_28px_90px_-40px_rgba(99,102,241,0.75)] backdrop-blur-2xl dark:bg-dark-900/90 ${corners}`}
>
<div
className={`flex flex-row h-14 items-center justify-between ${asideBrandStyle}`}
>
<div className="text-center flex-1 lg:text-left lg:pl-6 xl:text-center xl:pl-0">
<b className="font-black">Revenue Recovery Labs</b>
{organizationName && <p>{organizationName}</p>}
</div>
<button
className="hidden lg:inline-block xl:hidden p-3"
onClick={handleAsideLgCloseClick}
>
<div className="flex h-16 items-center justify-between border-b border-white/10 px-4">
<Link href="/dashboard" className="min-w-0 flex-1">
<RrlabsBrand compact showTagline />
{organizationName ? (
<p className="mt-1 truncate text-xs text-slate-400">{organizationName}</p>
) : null}
</Link>
<button className="hidden p-3 lg:inline-block xl:hidden" onClick={handleAsideLgCloseClick}>
<BaseIcon path={mdiClose} />
</button>
</div>
<div
className={`flex-1 overflow-y-auto overflow-x-hidden ${
className={`flex-1 overflow-x-hidden overflow-y-auto ${
darkMode ? 'aside-scrollbars-[slate]' : asideScrollbarsStyle
}`}
>
@ -90,5 +79,5 @@ export default function AsideMenuLayer({ menu, className = '', ...props }: Props
</div>
</div>
</aside>
)
);
}

View File

@ -1,35 +1,40 @@
import React, { ReactNode } from 'react'
import { containerMaxW } from '../config'
import Logo from './Logo'
import React, { ReactNode } from 'react';
import Link from 'next/link';
import { containerMaxW } from '../config';
import RrlabsBrand from './RrlabsBrand';
type Props = {
children?: ReactNode
}
children?: ReactNode;
};
export default function FooterBar({ children }: Props) {
const year = new Date().getFullYear()
const year = new Date().getFullYear();
return (
<footer className={`py-2 px-6 ${containerMaxW}`}>
<div className="block md:flex items-center justify-between">
<div className="text-center md:text-left mb-6 md:mb-0">
<b>
&copy;{year},{` `}
<a href="https://flatlogic.com/" rel="noreferrer" target="_blank">
Flatlogic
</a>
.
</b>
{` `}
{children}
<footer className={`border-t border-white/10 px-6 py-6 ${containerMaxW}`}>
<div className="flex flex-col gap-6 md:flex-row md:items-center md:justify-between">
<div>
<Link href="/" className="inline-flex">
<RrlabsBrand compact showTagline />
</Link>
<p className="mt-3 text-sm text-slate-400">
© {year} Revenue Recovery Labs. AI recovery intelligence for modern subscription teams.
</p>
{children ? <div className="mt-2 text-sm text-slate-500">{children}</div> : null}
</div>
<div className="flex item-center md:py-2 gap-4">
<a href="https://flatlogic.com/" rel="noreferrer" target="_blank">
<Logo className="w-auto h-8 md:h-6 mx-auto" />
</a>
</div>
<div className="flex flex-wrap gap-3 text-sm text-slate-400">
<Link href="/recovery-studio" className="transition-colors hover:text-white">
Recovery Studio
</Link>
<Link href="/pricing" className="transition-colors hover:text-white">
Pricing
</Link>
<Link href="/privacy-policy" className="transition-colors hover:text-white">
Privacy
</Link>
</div>
</div>
</footer>
)
);
}

View File

@ -1,28 +1,28 @@
import React, { ReactNode, useState, useEffect } from 'react'
import { mdiClose, mdiDotsVertical } from '@mdi/js'
import { containerMaxW } from '../config'
import BaseIcon from './BaseIcon'
import NavBarItemPlain from './NavBarItemPlain'
import NavBarMenuList from './NavBarMenuList'
import { MenuNavBarItem } from '../interfaces'
import { useAppSelector } from '../stores/hooks';
import React, { ReactNode, useEffect, useState } from 'react';
import { mdiClose, mdiDotsVertical } from '@mdi/js';
import { containerMaxW } from '../config';
import BaseIcon from './BaseIcon';
import NavBarItemPlain from './NavBarItemPlain';
import NavBarMenuList from './NavBarMenuList';
import { MenuNavBarItem } from '../interfaces';
import { useAppSelector } from '../stores/hooks';
type Props = {
menu: MenuNavBarItem[]
className: string
children: ReactNode
}
menu: MenuNavBarItem[];
className: string;
children: ReactNode;
};
export default function NavBar({ menu, className = '', children }: Props) {
const [isMenuNavBarActive, setIsMenuNavBarActive] = useState(false)
const [isMenuNavBarActive, setIsMenuNavBarActive] = useState(false);
const [isScrolled, setIsScrolled] = useState(false);
const bgColor = useAppSelector((state) => state.style.bgLayoutColor);
useEffect(() => {
const handleScroll = () => {
const scrolled = window.scrollY > 0;
setIsScrolled(scrolled);
setIsScrolled(window.scrollY > 0);
};
window.addEventListener('scroll', handleScroll);
return () => {
window.removeEventListener('scroll', handleScroll);
@ -30,28 +30,32 @@ export default function NavBar({ menu, className = '', children }: Props) {
}, []);
const handleMenuNavBarToggleClick = () => {
setIsMenuNavBarActive(!isMenuNavBarActive)
}
setIsMenuNavBarActive(!isMenuNavBarActive);
};
return (
<nav
className={`${className} top-0 inset-x-0 fixed ${bgColor} h-14 z-30 transition-position w-screen lg:w-auto dark:bg-dark-800`}
className={`${className} ${bgColor} fixed inset-x-0 top-0 z-30 h-14 w-screen border-b border-white/10 bg-slate-950/75 shadow-[0_18px_40px_-28px_rgba(2,6,23,0.9)] backdrop-blur-xl transition-position lg:w-auto dark:bg-dark-900/80`}
>
<div className={`flex lg:items-stretch ${containerMaxW} ${isScrolled && `border-b border-midnightBlueTheme-outsideCardColor dark:border-dark-700`}`}>
<div className="flex flex-1 items-stretch h-14">{children}</div>
<div className="flex-none items-stretch flex h-14 lg:hidden">
<div
className={`flex lg:items-stretch ${containerMaxW} ${
isScrolled ? 'border-b border-white/10 dark:border-dark-700' : ''
}`}
>
<div className="flex h-14 flex-1 items-stretch">{children}</div>
<div className="flex h-14 flex-none items-stretch lg:hidden">
<NavBarItemPlain onClick={handleMenuNavBarToggleClick}>
<BaseIcon path={isMenuNavBarActive ? mdiClose : mdiDotsVertical} size="24" />
</NavBarItemPlain>
</div>
<div
className={`${
className={`absolute left-0 top-14 w-screen border-b border-white/10 bg-slate-950/90 shadow-lg backdrop-blur-xl dark:bg-dark-900/90 ${
isMenuNavBarActive ? 'block' : 'hidden'
} flex items-center max-h-screen-menu overflow-y-auto lg:overflow-visible absolute w-screen top-14 left-0 ${bgColor} shadow-lg lg:w-auto lg:flex lg:static lg:shadow-none dark:bg-dark-800`}
} lg:static lg:flex lg:w-auto lg:items-center lg:border-b-0 lg:bg-transparent lg:shadow-none`}
>
<NavBarMenuList menu={menu} />
</div>
</div>
</nav>
)
);
}

View File

@ -1,6 +1,5 @@
import React, {useEffect, useRef} from 'react'
import React, { useEffect, useRef, useState } from 'react'
import Link from 'next/link'
import { useState } from 'react'
import { mdiChevronUp, mdiChevronDown } from '@mdi/js'
import BaseDivider from './BaseDivider'
import BaseIcon from './BaseIcon'

View File

@ -0,0 +1,49 @@
import React from 'react';
type Props = {
className?: string;
textClassName?: string;
compact?: boolean;
showTagline?: boolean;
};
export default function RrlabsBrand({
className = '',
textClassName = '',
compact = false,
showTagline = false,
}: Props) {
return (
<div className={`flex items-center ${compact ? 'gap-2' : 'gap-3'} ${className}`}>
<span
className={`relative inline-flex items-center justify-center overflow-hidden rounded-2xl border border-white/10 bg-gradient-to-br from-[#6366f1] via-[#7c3aed] to-[#06b6d4] ${
compact ? 'h-9 w-9' : 'h-11 w-11'
} shadow-[0_0_30px_rgba(99,102,241,0.35)]`}
>
<svg
viewBox="0 0 24 24"
fill="none"
aria-hidden="true"
className={`${compact ? 'h-4 w-4' : 'h-5 w-5'} text-white`}
>
<path
d="M13 2L4 13h6l-1 9 11-13h-6l1-7Z"
fill="currentColor"
/>
</svg>
</span>
<div className="min-w-0">
<div
className={`bg-gradient-to-r from-[#6366f1] to-[#06b6d4] bg-clip-text font-semibold tracking-tight text-transparent ${
compact ? 'text-lg' : 'text-xl'
} ${textClassName}`}
>
RRLabs
</div>
{showTagline && (
<p className="text-xs text-slate-400">Revenue Recovery Labs</p>
)}
</div>
</div>
);
}

View File

@ -0,0 +1,56 @@
import React from 'react';
import Head from 'next/head';
import PublicMarketingShell from './PublicMarketingShell';
import { getPageTitle } from '../../config';
import { LegalDocument } from '../../data/rrlabsContent';
type Props = {
document: LegalDocument;
};
export default function LegalPage({ document }: Props) {
return (
<>
<Head>
<title>{getPageTitle(document.title)}</title>
<meta name="description" content={document.description} />
</Head>
<PublicMarketingShell>
<section className="mx-auto max-w-4xl px-4 py-16 sm:px-6 lg:px-8 lg:py-20">
<div className="mb-8 rounded-[28px] border border-white/10 bg-white/5 p-8 shadow-[0_25px_80px_-30px_rgba(6,182,212,0.25)] backdrop-blur-xl">
<p className="mb-4 text-sm font-semibold uppercase tracking-[0.3em] text-[#06b6d4]">
Legal & compliance
</p>
<h1 className="mb-4 text-4xl font-semibold tracking-tight text-white sm:text-5xl">
{document.title}
</h1>
<p className="max-w-2xl text-base leading-7 text-slate-300 sm:text-lg">
{document.description}
</p>
<div className="mt-6 inline-flex rounded-full border border-white/10 bg-slate-950/70 px-4 py-2 text-sm text-slate-400">
Last updated: {document.updatedAt}
</div>
</div>
<div className="space-y-6">
{document.sections.map((section) => (
<article
key={section.heading}
className="rounded-[24px] border border-white/10 bg-slate-950/65 p-6 shadow-[0_20px_60px_-35px_rgba(99,102,241,0.55)] backdrop-blur-xl sm:p-8"
>
<h2 className="mb-4 text-xl font-semibold text-white sm:text-2xl">
{section.heading}
</h2>
<div className="space-y-4 text-sm leading-7 text-slate-300 sm:text-base">
{section.body.map((paragraph) => (
<p key={paragraph}>{paragraph}</p>
))}
</div>
</article>
))}
</div>
</section>
</PublicMarketingShell>
</>
);
}

View File

@ -0,0 +1,155 @@
import React from 'react';
import Link from 'next/link';
import { useRouter } from 'next/router';
import { mdiClose, mdiMenu } from '@mdi/js';
import BaseIcon from '../BaseIcon';
import RrlabsBrand from '../RrlabsBrand';
type Props = {
children: React.ReactNode;
};
const navLinks = [
{ href: '/', label: 'Home' },
{ href: '/pricing', label: 'Pricing' },
{ href: '/blog', label: 'Blog' },
{ href: '/terms-of-service', label: 'Terms' },
{ href: '/privacy-policy', label: 'Privacy' },
{ href: '/refund-policy', label: 'Refunds' },
];
export default function PublicMarketingShell({ children }: Props) {
const router = useRouter();
const [isMobileOpen, setIsMobileOpen] = React.useState(false);
const isActive = (href: string) => {
if (href === '/') {
return router.pathname === '/';
}
return router.pathname === href || router.asPath.startsWith(`${href}/`);
};
return (
<div className="min-h-screen overflow-hidden bg-[#020617] text-slate-100">
<div className="pointer-events-none fixed inset-0 overflow-hidden">
<div className="absolute left-0 top-0 h-72 w-72 rounded-full bg-[#6366f1]/20 blur-3xl" />
<div className="absolute bottom-0 right-0 h-72 w-72 rounded-full bg-[#06b6d4]/15 blur-3xl" />
<div className="absolute left-1/2 top-1/3 h-64 w-64 -translate-x-1/2 rounded-full bg-fuchsia-500/10 blur-3xl" />
</div>
<header className="sticky top-0 z-40 border-b border-white/10 bg-slate-950/75 backdrop-blur-xl">
<div className="mx-auto flex max-w-7xl items-center justify-between px-4 py-3 sm:px-6 lg:px-8">
<Link href="/" className="transition-transform duration-300 hover:scale-[1.01]">
<RrlabsBrand compact showTagline />
</Link>
<nav className="hidden items-center gap-1 md:flex">
{navLinks.map((link) => (
<Link
key={link.href}
href={link.href}
className={`rounded-full px-4 py-2 text-sm transition-all duration-300 hover:bg-white/5 hover:text-white ${
isActive(link.href)
? 'bg-white/10 text-white'
: 'text-slate-300'
}`}
>
{link.label}
</Link>
))}
</nav>
<div className="hidden items-center gap-3 md:flex">
<Link
href="/login"
className="rounded-full border border-white/10 px-4 py-2 text-sm text-slate-200 transition-all duration-300 hover:border-white/20 hover:bg-white/5"
>
Login
</Link>
<Link
href="/dashboard"
className="rounded-full bg-gradient-to-r from-[#6366f1] to-[#06b6d4] px-4 py-2 text-sm font-semibold text-white shadow-[0_10px_30px_rgba(99,102,241,0.35)] transition-transform duration-300 hover:scale-[1.02]"
>
Admin interface
</Link>
</div>
<button
type="button"
className="inline-flex h-11 w-11 items-center justify-center rounded-full border border-white/10 bg-white/5 text-slate-100 md:hidden"
onClick={() => setIsMobileOpen((current) => !current)}
aria-label={isMobileOpen ? 'Close navigation menu' : 'Open navigation menu'}
>
<BaseIcon path={isMobileOpen ? mdiClose : mdiMenu} size={22} />
</button>
</div>
{isMobileOpen && (
<div className="border-t border-white/10 bg-slate-950/90 px-4 py-4 md:hidden">
<div className="mx-auto flex max-w-7xl flex-col gap-2">
{navLinks.map((link) => (
<Link
key={link.href}
href={link.href}
className={`rounded-2xl px-4 py-3 text-sm ${
isActive(link.href)
? 'bg-white/10 text-white'
: 'text-slate-300 hover:bg-white/5'
}`}
onClick={() => setIsMobileOpen(false)}
>
{link.label}
</Link>
))}
<div className="mt-2 grid grid-cols-1 gap-2 sm:grid-cols-2">
<Link
href="/login"
className="rounded-2xl border border-white/10 px-4 py-3 text-center text-sm text-slate-200"
onClick={() => setIsMobileOpen(false)}
>
Login
</Link>
<Link
href="/dashboard"
className="rounded-2xl bg-gradient-to-r from-[#6366f1] to-[#06b6d4] px-4 py-3 text-center text-sm font-semibold text-white"
onClick={() => setIsMobileOpen(false)}
>
Admin interface
</Link>
</div>
</div>
</div>
)}
</header>
<main className="relative z-10">{children}</main>
<footer className="relative z-10 border-t border-white/10 bg-slate-950/80 backdrop-blur-xl">
<div className="mx-auto flex max-w-7xl flex-col gap-8 px-4 py-10 sm:px-6 lg:flex-row lg:items-center lg:justify-between lg:px-8">
<div>
<RrlabsBrand showTagline className="mb-3" />
<p className="max-w-xl text-sm text-slate-400">
Premium AI-assisted revenue recovery for subscription operators who need
clean messaging, clean webhook hygiene, and a cleaner path back to cash.
</p>
</div>
<div className="grid grid-cols-2 gap-3 text-sm text-slate-400 sm:grid-cols-3">
{navLinks.map((link) => (
<Link key={link.href} href={link.href} className="transition-colors hover:text-white">
{link.label}
</Link>
))}
<Link href="/login" className="transition-colors hover:text-white">
Login
</Link>
<Link href="/dashboard" className="transition-colors hover:text-white">
Dashboard
</Link>
</div>
</div>
</footer>
</div>
);
}

View File

@ -0,0 +1,369 @@
export const lemonCheckoutUrl =
'https://checkout.rrlabs.online/checkout/buy/462e334b-c2ba-49fe-ac43-157fdad0242c';
export const pricingTiers = [
{
name: 'Standard',
price: '$29',
cadence: '/mo',
tagline: 'For lean subscription teams getting revenue recovery live fast.',
highlight: 'Core recovery features',
ctaLabel: 'Start Standard',
features: [
'Failed-payment recovery workspace',
'Email, WhatsApp, and SMS message drafts',
'Recovery logs and trend visibility',
'Webhook endpoint and secret management',
],
},
{
name: 'Premium',
price: '$99',
cadence: '/mo',
tagline: 'For operators who want AI-driven message quality and faster iteration.',
highlight: 'AI-powered personalization + priority support',
featured: true,
ctaLabel: 'Start Premium',
features: [
'Everything in Standard',
'AI-generated recovery copy variations',
'Priority onboarding and support',
'Channel-level performance workflows',
],
},
{
name: 'Enterprise',
price: '$299',
cadence: '/mo',
tagline: 'For scaled teams coordinating gateways, messaging, and customer ops.',
highlight: 'Custom integrations + dedicated manager',
ctaLabel: 'Talk to Revenue Ops',
features: [
'Everything in Premium',
'Custom payment gateway mapping',
'Dedicated success manager',
'Enterprise rollout and approval support',
],
},
] as const;
export const marketingHighlights = [
{
title: 'Recovery intelligence, not just reminders',
description:
'Coordinate gateway events, messaging channels, and AI-crafted outreach inside one premium command center.',
},
{
title: 'Approval-friendly public surface',
description:
'Launch pricing, blog, and legal pages that make payment review teams comfortable from day one.',
},
{
title: 'Operator-first configuration flow',
description:
'Move from setup to message generation to recovery logging without bouncing between disconnected admin screens.',
},
] as const;
export const workflowSteps = [
{
step: 'Connect your payment stack',
description:
'Capture store IDs, merchant IDs, webhook secrets, and gateway endpoints for the channels you rely on.',
},
{
step: 'Generate AI recovery copy',
description:
'Draft reminder language that feels human, clear, and conversion-focused across email, WhatsApp, and SMS.',
},
{
step: 'Track recovery momentum',
description:
'Review logs, verification health, and trend snapshots so revenue ops can spot wins and issues quickly.',
},
] as const;
export const heroMetrics = [
{ label: 'Global gateway presets', value: '10' },
{ label: 'Supported recovery channels', value: '3' },
{ label: 'Operator-ready workflow', value: '1 click' },
] as const;
export const publicBlogCategories = [
'All',
'Revenue Recovery',
'Messaging Strategy',
'Payments Ops',
] as const;
export type PublicBlogPost = {
slug: string;
title: string;
excerpt: string;
category: (typeof publicBlogCategories)[number];
publishedAt: string;
readTime: string;
eyebrow: string;
seoDescription: string;
content: Array<{
heading: string;
paragraphs: string[];
}>;
};
export const publicBlogPosts: PublicBlogPost[] = [
{
slug: 'recovery-metrics-that-matter',
title: 'Recovery Metrics That Matter',
excerpt:
'Track recovery rate, time-to-recover, and channel performance before you start adding more automation.',
category: 'Revenue Recovery',
publishedAt: 'May 4, 2026',
readTime: '5 min read',
eyebrow: 'Analytics Playbook',
seoDescription:
'A concise guide to the revenue recovery KPIs that subscription operators should review every week.',
content: [
{
heading: 'Start with a believable scoreboard',
paragraphs: [
'Most teams jump to templates before they establish what success should look like. Begin with a tight weekly scoreboard that includes recoveries count, recovered amount, messages sent, and overall conversion rate.',
'Once that baseline is visible, you can compare channels, gateways, and customer segments without guessing where friction actually lives.',
],
},
{
heading: 'Look for lag, not just volume',
paragraphs: [
'A large number of reminders sent can look healthy while revenue is still slipping. Time-to-recover tells you whether your cadence is helping customers resolve payment issues quickly or simply creating noise.',
'The fastest programs usually pair clear messaging with clean payment update flows, so operators should watch both volume and delay together.',
],
},
{
heading: 'Use trends to guide the next experiment',
paragraphs: [
'When metrics move, connect the shift to a change you actually made: a new tone, a different channel, or a revised webhook secret rotation. That is what turns analytics into an operations loop instead of a passive dashboard.',
],
},
],
},
{
slug: 'dunning-that-feels-human',
title: 'Dunning That Feels Human',
excerpt:
'Recover more revenue with reminder language that sounds helpful and urgent without hurting customer trust.',
category: 'Messaging Strategy',
publishedAt: 'May 1, 2026',
readTime: '4 min read',
eyebrow: 'Copy Strategy',
seoDescription:
'How to write recovery messages that balance empathy, clarity, and commercial urgency.',
content: [
{
heading: 'Lead with context, not pressure',
paragraphs: [
'The best recovery messages acknowledge what happened, tell the customer what to do next, and respect their time. A terse demand can increase churn even when the balance is eventually recovered.',
'Simple phrases such as “your latest payment did not go through” and “update your billing details securely here” reduce confusion and create forward motion.',
],
},
{
heading: 'Match tone to relationship stage',
paragraphs: [
'New customers often need reassurance. Long-term customers usually respond better to direct reminders that preserve momentum. The message should adapt to lifecycle, tenure, and payment history.',
],
},
{
heading: 'Keep the path to payment obvious',
paragraphs: [
'A polished message still underperforms if the link is missing, the CTA is vague, or the channel arrives too late. Great recovery copy always points to one clear action and one trusted route back to payment.',
],
},
],
},
{
slug: 'webhook-verification-basics',
title: 'Webhook Verification Basics',
excerpt:
'Verify signatures, reject malformed payloads, and keep recovery flows dependable as gateway traffic scales.',
category: 'Payments Ops',
publishedAt: 'April 28, 2026',
readTime: '6 min read',
eyebrow: 'Platform Reliability',
seoDescription:
'Foundational guidance for securing payment gateway webhooks and keeping recovery automations trustworthy.',
content: [
{
heading: 'Treat every webhook as untrusted input',
paragraphs: [
'Webhook payloads can trigger customer-visible activity, so signature verification should happen before downstream processing. Store secrets securely, compare signatures using a stable algorithm, and fail closed when inputs are malformed.',
],
},
{
heading: 'Make retries observable',
paragraphs: [
'Verification failure without a clear event log forces operators to debug blindly. Record the event type, reference, verification status, and timestamp so teams can distinguish between malicious traffic and a configuration mistake.',
],
},
{
heading: 'Rotate secrets without panic',
paragraphs: [
'A healthy workflow makes secret rotation routine. Generate a new secret, update the gateway, monitor verification outcomes, and only retire the old value once traffic stabilizes.',
],
},
],
},
];
export type LegalDocument = {
title: string;
description: string;
updatedAt: string;
sections: Array<{
heading: string;
body: string[];
}>;
};
export const legalDocuments: Record<
'privacyPolicy' | 'termsOfService' | 'refundPolicy',
LegalDocument
> = {
privacyPolicy: {
title: 'Privacy Policy',
description:
'How Revenue Recovery Labs collects, uses, secures, and retains customer data across the service.',
updatedAt: 'May 4, 2026',
sections: [
{
heading: '1. Scope and purpose',
body: [
'Revenue Recovery Labs processes business and user information to provide subscription recovery workflows, analytics, and support. This policy explains what we collect, why we collect it, and how we protect it.',
],
},
{
heading: '2. Information we collect',
body: [
'We may collect account details, billing contact information, configuration data for gateways and messaging providers, support communications, and usage analytics needed to operate the service securely.',
'When customers connect payment gateways, SMTP providers, or WABA credentials, that information is used only to deliver configured service functionality and maintain operational integrity.',
],
},
{
heading: '3. How we use information',
body: [
'We use information to authenticate users, deliver product features, monitor webhook and messaging health, improve reliability, investigate fraud or abuse, and comply with legal obligations.',
],
},
{
heading: '4. Security and retention',
body: [
'We apply administrative, technical, and organizational safeguards designed to protect data from unauthorized access, misuse, and disclosure. We retain data for as long as necessary to provide the service, resolve disputes, and comply with law.',
],
},
{
heading: '5. Your choices and requests',
body: [
'You may request access, correction, deletion, or export of your personal data where applicable law provides those rights. We may need to verify the request before acting on it.',
],
},
{
heading: '6. Contact',
body: [
'Privacy requests and questions can be sent to support@rrlabs.online. We will review each inquiry in accordance with applicable law and our internal security procedures.',
],
},
],
},
termsOfService: {
title: 'Terms of Service',
description:
'The rules for accessing and using Revenue Recovery Labs, including billing, acceptable use, and service limitations.',
updatedAt: 'May 4, 2026',
sections: [
{
heading: '1. Agreement to terms',
body: [
'By accessing or using Revenue Recovery Labs, you agree to these Terms of Service. If you are using the service on behalf of an organization, you represent that you have authority to bind that organization.',
],
},
{
heading: '2. Service access',
body: [
'You are responsible for maintaining the security of your account credentials and for activities that occur under your account. We may suspend access to protect the service, other customers, or our systems.',
],
},
{
heading: '3. Acceptable use',
body: [
'You may not use the service to violate law, infringe rights, distribute malicious code, abuse payment or messaging providers, or send unlawful or deceptive communications.',
],
},
{
heading: '4. Billing and subscriptions',
body: [
'Paid plans renew according to the billing cadence presented at checkout unless cancelled before renewal. Taxes, gateway fees, and refunds are handled according to applicable law and our published refund policy.',
],
},
{
heading: '5. Customer data and integrations',
body: [
'You retain responsibility for the data and third-party credentials you connect. You must ensure that connected systems, webhook destinations, and outbound messages comply with your contractual and regulatory obligations.',
],
},
{
heading: '6. Disclaimers and limitation of liability',
body: [
'The service is provided on an “as is” and “as available” basis to the fullest extent permitted by law. To the maximum extent permitted by law, Revenue Recovery Labs will not be liable for indirect, incidental, special, consequential, or exemplary damages.',
],
},
{
heading: '7. Termination',
body: [
'We may suspend or terminate access for violations of these Terms, non-payment, or security concerns. You may stop using the service at any time subject to any minimum contractual commitments shown at purchase.',
],
},
],
},
refundPolicy: {
title: 'Refund Policy',
description:
'A clear summary of how refund requests are evaluated for subscription purchases made through Revenue Recovery Labs.',
updatedAt: 'May 4, 2026',
sections: [
{
heading: '1. General approach',
body: [
'Refund requests are reviewed in good faith and in accordance with applicable consumer protection law, the subscription terms displayed at checkout, and any enterprise order form that applies to your account.',
],
},
{
heading: '2. Eligibility considerations',
body: [
'When evaluating a request, we may consider the purchase date, plan type, account usage, renewal timing, duplicate charges, and whether the request is required by law or contract.',
],
},
{
heading: '3. Renewal charges',
body: [
'If you do not wish to renew, cancel before the next billing cycle begins. Renewal refund eligibility may be limited once a new billing period has started and the service remains available to the account.',
],
},
{
heading: '4. Non-refundable items',
body: [
'Professional services, custom implementation work, and third-party costs that have already been incurred are generally non-refundable unless required otherwise by law or a written agreement.',
],
},
{
heading: '5. How to request a refund',
body: [
'Send the account email, invoice reference, reason for the request, and any relevant screenshots to support@rrlabs.online. We may ask follow-up questions to verify the request and understand the issue.',
],
},
{
heading: '6. Processing time',
body: [
'Approved refunds are usually initiated within a reasonable period after approval. Final posting times depend on the payment gateway and the original payment method.',
],
},
],
},
};

View File

@ -1,5 +1,4 @@
import React, { ReactNode, useEffect } from 'react'
import { useState } from 'react'
import React, { ReactNode, useEffect, useState } from 'react'
import jwt from 'jsonwebtoken';
import { mdiForwardburger, mdiBackburger, mdiMenu } from '@mdi/js'
import menuAside from '../menuAside'
@ -11,6 +10,8 @@ import AsideMenu from '../components/AsideMenu'
import FooterBar from '../components/FooterBar'
import { useAppDispatch, useAppSelector } from '../stores/hooks'
import Search from '../components/Search';
import Link from 'next/link';
import RrlabsBrand from '../components/RrlabsBrand';
import { useRouter } from 'next/router'
import {findMe, logoutUser} from "../stores/authSlice";
@ -111,6 +112,9 @@ export default function LayoutAuthenticated({
>
<BaseIcon path={mdiMenu} size="24" />
</NavBarItemPlain>
<Link href="/dashboard" className="flex items-center px-3">
<RrlabsBrand compact />
</Link>
<NavBarItemPlain useMargin>
<Search />
</NavBarItemPlain>

View File

@ -8,6 +8,12 @@ const menuAside: MenuAsideItem[] = [
label: 'Dashboard',
},
{
href: '/recovery-studio',
icon: icon.mdiLightningBoltOutline ?? icon.mdiTable,
label: 'Recovery Studio',
},
{
href: '/users/users-list',
label: 'Users',

View File

@ -0,0 +1,119 @@
import React from 'react';
import type { ReactElement } from 'react';
import Head from 'next/head';
import Link from 'next/link';
import { useRouter } from 'next/router';
import LayoutGuest from '../../layouts/Guest';
import PublicMarketingShell from '../../components/marketing/PublicMarketingShell';
import { getPageTitle } from '../../config';
import { publicBlogPosts } from '../../data/rrlabsContent';
export default function BlogPostPage() {
const router = useRouter();
const slug = typeof router.query.slug === 'string' ? router.query.slug : '';
const post = publicBlogPosts.find((item) => item.slug === slug);
const relatedPosts = publicBlogPosts.filter((item) => item.slug !== slug).slice(0, 2);
if (!slug) {
return (
<PublicMarketingShell>
<section className="mx-auto max-w-4xl px-4 py-20 text-slate-300 sm:px-6 lg:px-8">
Loading article...
</section>
</PublicMarketingShell>
);
}
if (!post) {
return (
<>
<Head>
<title>{getPageTitle('Article not found')}</title>
</Head>
<PublicMarketingShell>
<section className="mx-auto max-w-4xl px-4 py-20 sm:px-6 lg:px-8">
<div className="rounded-[28px] border border-white/10 bg-slate-950/65 p-8 text-slate-300 backdrop-blur-xl">
<h1 className="text-3xl font-semibold text-white">Article not found</h1>
<p className="mt-4 leading-7">
The article you requested does not exist yet. Explore the rest of the blog instead.
</p>
<Link
href="/blog"
className="mt-6 inline-flex rounded-full bg-gradient-to-r from-[#6366f1] to-[#06b6d4] px-5 py-3 text-sm font-semibold text-white"
>
Back to blog
</Link>
</div>
</section>
</PublicMarketingShell>
</>
);
}
return (
<>
<Head>
<title>{getPageTitle(post.title)}</title>
<meta name="description" content={post.seoDescription} />
</Head>
<PublicMarketingShell>
<section className="mx-auto max-w-4xl px-4 py-16 sm:px-6 lg:px-8 lg:py-20">
<Link href="/blog" className="text-sm font-semibold text-[#7dd3fc] transition-colors hover:text-white">
Back to blog
</Link>
<div className="mt-6 rounded-[30px] border border-white/10 bg-white/5 p-8 shadow-[0_25px_80px_-35px_rgba(99,102,241,0.55)] backdrop-blur-xl sm:p-10">
<div className="flex flex-wrap items-center gap-3 text-xs uppercase tracking-[0.3em] text-slate-400">
<span>{post.eyebrow}</span>
<span></span>
<span>{post.category}</span>
<span></span>
<span>{post.readTime}</span>
</div>
<h1 className="mt-5 text-4xl font-semibold tracking-tight text-white sm:text-5xl">
{post.title}
</h1>
<p className="mt-6 text-lg leading-8 text-slate-300">{post.excerpt}</p>
<div className="mt-6 inline-flex rounded-full border border-white/10 bg-slate-950/70 px-4 py-2 text-sm text-slate-400">
Published {post.publishedAt}
</div>
</div>
<article className="prose prose-invert prose-slate mt-10 max-w-none rounded-[30px] border border-white/10 bg-slate-950/65 p-8 shadow-[0_25px_80px_-40px_rgba(6,182,212,0.45)] backdrop-blur-xl sm:p-10">
{post.content.map((section) => (
<section key={section.heading} className="mb-10 last:mb-0">
<h2>{section.heading}</h2>
{section.paragraphs.map((paragraph) => (
<p key={paragraph}>{paragraph}</p>
))}
</section>
))}
</article>
<div className="mt-12">
<h2 className="text-2xl font-semibold text-white">Related reading</h2>
<div className="mt-6 grid gap-6 md:grid-cols-2">
{relatedPosts.map((relatedPost) => (
<Link
key={relatedPost.slug}
href={`/blog/${relatedPost.slug}`}
className="rounded-[28px] border border-white/10 bg-white/5 p-6 backdrop-blur-xl transition-transform duration-300 hover:-translate-y-1 hover:scale-[1.01]"
>
<div className="text-xs uppercase tracking-[0.25em] text-slate-400">
{relatedPost.category}
</div>
<h3 className="mt-4 text-2xl font-semibold text-white">{relatedPost.title}</h3>
<p className="mt-4 text-sm leading-7 text-slate-300">{relatedPost.excerpt}</p>
</Link>
))}
</div>
</div>
</section>
</PublicMarketingShell>
</>
);
}
BlogPostPage.getLayout = function getLayout(page: ReactElement) {
return <LayoutGuest>{page}</LayoutGuest>;
};

View File

@ -0,0 +1,98 @@
import React from 'react';
import type { ReactElement } from 'react';
import Head from 'next/head';
import Link from 'next/link';
import LayoutGuest from '../../layouts/Guest';
import PublicMarketingShell from '../../components/marketing/PublicMarketingShell';
import { getPageTitle } from '../../config';
import { publicBlogCategories, publicBlogPosts } from '../../data/rrlabsContent';
export default function BlogIndexPage() {
const [activeCategory, setActiveCategory] = React.useState<(typeof publicBlogCategories)[number]>('All');
const visiblePosts = React.useMemo(() => {
if (activeCategory === 'All') {
return publicBlogPosts;
}
return publicBlogPosts.filter((post) => post.category === activeCategory);
}, [activeCategory]);
return (
<>
<Head>
<title>{getPageTitle('Blog')}</title>
<meta
name="description"
content="Browse Revenue Recovery Labs articles on revenue recovery, messaging, and payments operations."
/>
</Head>
<PublicMarketingShell>
<section className="mx-auto max-w-7xl px-4 py-16 sm:px-6 lg:px-8 lg:py-20">
<div className="max-w-3xl">
<p className="text-sm font-semibold uppercase tracking-[0.3em] text-[#06b6d4]">
Blog
</p>
<h1 className="mt-4 text-4xl font-semibold tracking-tight text-white sm:text-5xl">
Practical playbooks for recovery, messaging, and payments ops.
</h1>
<p className="mt-6 text-lg leading-8 text-slate-300">
Use the category filters to jump between commercial strategy, copywriting,
and the operational mechanics that keep recovery workflows healthy.
</p>
</div>
<div className="mt-10 flex flex-wrap gap-3">
{publicBlogCategories.map((category) => (
<button
key={category}
type="button"
onClick={() => setActiveCategory(category)}
className={`rounded-full px-4 py-2 text-sm transition-all duration-300 ${
activeCategory === category
? 'bg-gradient-to-r from-[#6366f1] to-[#06b6d4] text-white shadow-[0_12px_28px_rgba(99,102,241,0.35)]'
: 'border border-white/10 bg-white/5 text-slate-300 hover:bg-white/10'
}`}
>
{category}
</button>
))}
</div>
{visiblePosts.length ? (
<div className="mt-10 grid gap-6 lg:grid-cols-3">
{visiblePosts.map((post) => (
<Link
key={post.slug}
href={`/blog/${post.slug}`}
className="group flex h-full flex-col rounded-[30px] border border-white/10 bg-white/5 p-6 shadow-[0_25px_70px_-45px_rgba(99,102,241,0.8)] backdrop-blur-xl transition-transform duration-300 hover:-translate-y-1 hover:scale-[1.01]"
>
<div className="flex items-center justify-between gap-4 text-xs uppercase tracking-[0.25em] text-slate-400">
<span>{post.eyebrow}</span>
<span>{post.readTime}</span>
</div>
<h2 className="mt-5 text-2xl font-semibold text-white transition-colors group-hover:text-[#7dd3fc]">
{post.title}
</h2>
<p className="mt-4 flex-1 text-sm leading-7 text-slate-300">{post.excerpt}</p>
<div className="mt-6 flex items-center justify-between text-sm text-slate-400">
<span>{post.category}</span>
<span>{post.publishedAt}</span>
</div>
</Link>
))}
</div>
) : (
<div className="mt-10 rounded-[28px] border border-white/10 bg-slate-950/65 p-8 text-sm text-slate-300 backdrop-blur-xl">
No posts match this filter yet. Try another category.
</div>
)}
</section>
</PublicMarketingShell>
</>
);
}
BlogIndexPage.getLayout = function getLayout(page: ReactElement) {
return <LayoutGuest>{page}</LayoutGuest>;
};

View File

@ -1,166 +1,292 @@
import React, { useEffect, useState } from 'react';
import React from 'react';
import type { ReactElement } from 'react';
import Head from 'next/head';
import Link from 'next/link';
import BaseButton from '../components/BaseButton';
import CardBox from '../components/CardBox';
import SectionFullScreen from '../components/SectionFullScreen';
import LayoutGuest from '../layouts/Guest';
import BaseDivider from '../components/BaseDivider';
import BaseButtons from '../components/BaseButtons';
import PublicMarketingShell from '../components/marketing/PublicMarketingShell';
import { getPageTitle } from '../config';
import { useAppSelector } from '../stores/hooks';
import CardBoxComponentTitle from "../components/CardBoxComponentTitle";
import { getPexelsImage, getPexelsVideo } from '../helpers/pexels';
export default function Starter() {
const [illustrationImage, setIllustrationImage] = useState({
src: undefined,
photographer: undefined,
photographer_url: undefined,
})
const [illustrationVideo, setIllustrationVideo] = useState({video_files: []})
const [contentType, setContentType] = useState('video');
const [contentPosition, setContentPosition] = useState('left');
const textColor = useAppSelector((state) => state.style.linkColor);
const title = 'Revenue Recovery Labs'
// Fetch Pexels image/video
useEffect(() => {
async function fetchData() {
const image = await getPexelsImage();
const video = await getPexelsVideo();
setIllustrationImage(image);
setIllustrationVideo(video);
}
fetchData();
}, []);
const imageBlock = (image) => (
<div
className='hidden md:flex flex-col justify-end relative flex-grow-0 flex-shrink-0 w-1/3'
style={{
backgroundImage: `${
image
? `url(${image?.src?.original})`
: 'linear-gradient(rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0.5))'
}`,
backgroundSize: 'cover',
backgroundPosition: 'left center',
backgroundRepeat: 'no-repeat',
}}
>
<div className='flex justify-center w-full bg-blue-300/20'>
<a
className='text-[8px]'
href={image?.photographer_url}
target='_blank'
rel='noreferrer'
>
Photo by {image?.photographer} on Pexels
</a>
</div>
</div>
);
const videoBlock = (video) => {
if (video?.video_files?.length > 0) {
return (
<div className='hidden md:flex flex-col justify-end relative flex-grow-0 flex-shrink-0 w-1/3'>
<video
className='absolute top-0 left-0 w-full h-full object-cover'
autoPlay
loop
muted
>
<source src={video?.video_files[0]?.link} type='video/mp4'/>
Your browser does not support the video tag.
</video>
<div className='flex justify-center w-full bg-blue-300/20 z-10'>
<a
className='text-[8px]'
href={video?.user?.url}
target='_blank'
rel='noreferrer'
>
Video by {video.user.name} on Pexels
</a>
</div>
</div>)
}
};
import {
heroMetrics,
lemonCheckoutUrl,
marketingHighlights,
pricingTiers,
publicBlogPosts,
workflowSteps,
} from '../data/rrlabsContent';
export default function HomePage() {
return (
<div
style={
contentPosition === 'background'
? {
backgroundImage: `${
illustrationImage
? `url(${illustrationImage.src?.original})`
: 'linear-gradient(rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0.5))'
}`,
backgroundSize: 'cover',
backgroundPosition: 'left center',
backgroundRepeat: 'no-repeat',
}
: {}
}
>
<>
<Head>
<title>{getPageTitle('Starter Page')}</title>
<title>{getPageTitle('Revenue Recovery Labs')}</title>
<meta
name="description"
content="Revenue Recovery Labs is a premium AI SaaS for failed payment recovery, gateway operations, and message generation."
/>
</Head>
<PublicMarketingShell>
<section className="mx-auto max-w-7xl px-4 pb-12 pt-14 sm:px-6 lg:px-8 lg:pb-20 lg:pt-20">
<div className="grid gap-10 lg:grid-cols-[1.1fr_0.9fr] lg:items-center">
<div className="animate-fade-in">
<div className="mb-6 inline-flex rounded-full border border-[#6366f1]/30 bg-[#6366f1]/10 px-4 py-2 text-sm text-[#c7d2fe]">
Premium AI revenue recovery workspace
</div>
<h1 className="max-w-4xl text-5xl font-semibold tracking-tight text-white sm:text-6xl lg:text-7xl">
Recover failed payments with a{' '}
<span className="bg-gradient-to-r from-[#6366f1] to-[#06b6d4] bg-clip-text text-transparent">
glassy AI command center
</span>
.
</h1>
<p className="mt-6 max-w-2xl text-lg leading-8 text-slate-300 sm:text-xl">
Revenue Recovery Labs brings gateway setup, webhook hygiene, AI-written
outreach, and recovery analytics into one modern workflow for subscription
teams.
</p>
<div className="mt-8 flex flex-col gap-4 sm:flex-row">
<a
href={lemonCheckoutUrl}
target="_blank"
rel="noreferrer"
className="inline-flex items-center justify-center rounded-full bg-gradient-to-r from-[#6366f1] to-[#06b6d4] px-6 py-3 text-sm font-semibold text-white shadow-[0_16px_40px_rgba(99,102,241,0.35)] transition-transform duration-300 hover:scale-[1.02]"
>
Start with Lemon Squeezy
</a>
<Link
href="/dashboard"
className="inline-flex items-center justify-center rounded-full border border-white/10 bg-white/5 px-6 py-3 text-sm font-semibold text-white transition-all duration-300 hover:border-white/20 hover:bg-white/10"
>
Open admin interface
</Link>
<Link
href="/pricing"
className="inline-flex items-center justify-center rounded-full border border-white/10 px-6 py-3 text-sm font-semibold text-slate-200 transition-all duration-300 hover:border-white/20 hover:bg-white/5"
>
Explore pricing
</Link>
</div>
<SectionFullScreen bg='violet'>
<div
className={`flex ${
contentPosition === 'right' ? 'flex-row-reverse' : 'flex-row'
} min-h-screen w-full`}
>
{contentType === 'image' && contentPosition !== 'background'
? imageBlock(illustrationImage)
: null}
{contentType === 'video' && contentPosition !== 'background'
? videoBlock(illustrationVideo)
: null}
<div className='flex items-center justify-center flex-col space-y-4 w-full lg:w-full'>
<CardBox className='w-full md:w-3/5 lg:w-2/3'>
<CardBoxComponentTitle title="Welcome to your Revenue Recovery Labs app!"/>
<div className="space-y-3">
<p className='text-center '>This is a React.js/Node.js app generated by the <a className={`${textColor}`} href="https://flatlogic.com/generator">Flatlogic Web App Generator</a></p>
<p className='text-center '>For guides and documentation please check
your local README.md and the <a className={`${textColor}`} href="https://flatlogic.com/documentation">Flatlogic documentation</a></p>
<div className="mt-10 grid gap-3 sm:grid-cols-3">
{heroMetrics.map((metric) => (
<div
key={metric.label}
className="rounded-[24px] border border-white/10 bg-white/5 p-4 shadow-[0_20px_60px_-35px_rgba(6,182,212,0.55)] backdrop-blur-xl"
>
<div className="text-2xl font-semibold text-white">{metric.value}</div>
<div className="mt-2 text-sm text-slate-400">{metric.label}</div>
</div>
))}
</div>
</div>
<BaseButtons>
<BaseButton
href='/login'
label='Login'
color='info'
className='w-full'
/>
</BaseButtons>
</CardBox>
</div>
</div>
</SectionFullScreen>
<div className='bg-black text-white flex flex-col text-center justify-center md:flex-row'>
<p className='py-6 text-sm'>© 2026 <span>{title}</span>. All rights reserved</p>
<Link className='py-6 ml-4 text-sm' href='/privacy-policy/'>
Privacy Policy
</Link>
</div>
<div className="grid gap-4 animate-fade-in">
<div className="rounded-[28px] border border-white/10 bg-slate-950/70 p-6 shadow-[0_30px_80px_-35px_rgba(99,102,241,0.6)] backdrop-blur-xl transition-transform duration-300 hover:scale-[1.01]">
<div className="mb-4 flex items-center justify-between">
<span className="rounded-full border border-emerald-400/20 bg-emerald-400/10 px-3 py-1 text-xs uppercase tracking-[0.25em] text-emerald-300">
Recovery Trends
</span>
<span className="text-sm text-slate-400">Area insights</span>
</div>
<div className="space-y-4">
{[68, 82, 74, 98, 113, 129].map((value, index) => (
<div key={`${value}-${index}`} className="space-y-2">
<div className="flex items-center justify-between text-sm text-slate-400">
<span>Week {index + 1}</span>
<span>${value}k recovered</span>
</div>
<div className="h-2 overflow-hidden rounded-full bg-white/5">
<div
className="h-full rounded-full bg-gradient-to-r from-[#6366f1] to-[#06b6d4]"
style={{ width: `${Math.min(value, 130) / 1.3}%` }}
/>
</div>
</div>
))}
</div>
</div>
</div>
<div className="grid gap-4 sm:grid-cols-2">
<div className="rounded-[28px] border border-white/10 bg-white/5 p-5 backdrop-blur-xl transition-transform duration-300 hover:scale-[1.02]">
<div className="text-sm uppercase tracking-[0.25em] text-[#06b6d4]">
AI Message Lab
</div>
<p className="mt-3 text-sm leading-7 text-slate-300">
Draft empathetic payment reminders for email, WhatsApp, and SMS with
human review built in.
</p>
</div>
<div className="rounded-[28px] border border-white/10 bg-white/5 p-5 backdrop-blur-xl transition-transform duration-300 hover:scale-[1.02]">
<div className="text-sm uppercase tracking-[0.25em] text-[#6366f1]">
Gateway Control
</div>
<p className="mt-3 text-sm leading-7 text-slate-300">
Configure Stripe, Lemon Squeezy, Paddle, FastSpring, and custom
gateways from one operator-friendly surface.
</p>
</div>
</div>
</div>
</div>
</section>
<section className="mx-auto max-w-7xl px-4 py-10 sm:px-6 lg:px-8 lg:py-16">
<div className="grid gap-6 lg:grid-cols-3">
{marketingHighlights.map((highlight) => (
<div
key={highlight.title}
className="rounded-[28px] border border-white/10 bg-white/5 p-6 shadow-[0_20px_60px_-35px_rgba(99,102,241,0.55)] backdrop-blur-xl transition-transform duration-300 hover:-translate-y-1 hover:scale-[1.01]"
>
<h2 className="text-2xl font-semibold text-white">{highlight.title}</h2>
<p className="mt-4 text-sm leading-7 text-slate-300">{highlight.description}</p>
</div>
))}
</div>
</section>
<section className="mx-auto max-w-7xl px-4 py-10 sm:px-6 lg:px-8 lg:py-16">
<div className="mb-8 flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between">
<div>
<p className="text-sm font-semibold uppercase tracking-[0.3em] text-[#06b6d4]">
Thin-slice workflow
</p>
<h2 className="mt-3 text-3xl font-semibold tracking-tight text-white sm:text-4xl">
From setup to message generation to recovery visibility.
</h2>
</div>
<Link
href="/recovery-studio"
className="inline-flex items-center justify-center rounded-full border border-white/10 bg-white/5 px-5 py-3 text-sm font-semibold text-white transition-all duration-300 hover:border-white/20 hover:bg-white/10"
>
Preview Recovery Studio
</Link>
</div>
<div className="grid gap-6 lg:grid-cols-3">
{workflowSteps.map((item, index) => (
<div
key={item.step}
className="rounded-[28px] border border-white/10 bg-slate-950/65 p-6 backdrop-blur-xl"
>
<div className="mb-4 inline-flex h-10 w-10 items-center justify-center rounded-2xl bg-gradient-to-br from-[#6366f1] to-[#06b6d4] font-semibold text-white">
{index + 1}
</div>
<h3 className="text-xl font-semibold text-white">{item.step}</h3>
<p className="mt-4 text-sm leading-7 text-slate-300">{item.description}</p>
</div>
))}
</div>
</section>
<section className="mx-auto max-w-7xl px-4 py-10 sm:px-6 lg:px-8 lg:py-16">
<div className="mb-8 flex items-end justify-between gap-4">
<div>
<p className="text-sm font-semibold uppercase tracking-[0.3em] text-[#6366f1]">
Pricing
</p>
<h2 className="mt-3 text-3xl font-semibold tracking-tight text-white sm:text-4xl">
Choose the recovery velocity that fits your team.
</h2>
</div>
<Link href="/pricing" className="text-sm font-semibold text-[#7dd3fc] transition-colors hover:text-white">
Full pricing
</Link>
</div>
<div className="grid gap-6 lg:grid-cols-3">
{pricingTiers.map((tier) => (
<div
key={tier.name}
className={`rounded-[30px] border p-7 backdrop-blur-xl ${
tier.featured
? 'border-[#6366f1]/40 bg-gradient-to-b from-[#6366f1]/15 to-[#06b6d4]/10 shadow-[0_30px_80px_-35px_rgba(99,102,241,0.8)]'
: 'border-white/10 bg-white/5'
}`}
>
<div className="flex items-center justify-between">
<h3 className="text-xl font-semibold text-white">{tier.name}</h3>
<span className="rounded-full border border-white/10 bg-slate-950/70 px-3 py-1 text-xs uppercase tracking-[0.25em] text-slate-300">
{tier.highlight}
</span>
</div>
<div className="mt-5 flex items-end gap-1 text-white">
<span className="text-4xl font-semibold">{tier.price}</span>
<span className="mb-1 text-slate-400">{tier.cadence}</span>
</div>
<p className="mt-4 text-sm leading-7 text-slate-300">{tier.tagline}</p>
</div>
))}
</div>
</section>
<section className="mx-auto max-w-7xl px-4 py-10 sm:px-6 lg:px-8 lg:py-16">
<div className="mb-8 flex items-end justify-between gap-4">
<div>
<p className="text-sm font-semibold uppercase tracking-[0.3em] text-[#06b6d4]">
RRLabs journal
</p>
<h2 className="mt-3 text-3xl font-semibold tracking-tight text-white sm:text-4xl">
Public-facing insights for recovery, messaging, and payments ops.
</h2>
</div>
<Link href="/blog" className="text-sm font-semibold text-[#7dd3fc] transition-colors hover:text-white">
Visit blog
</Link>
</div>
<div className="grid gap-6 lg:grid-cols-3">
{publicBlogPosts.map((post) => (
<Link
key={post.slug}
href={`/blog/${post.slug}`}
className="rounded-[28px] border border-white/10 bg-white/5 p-6 backdrop-blur-xl transition-transform duration-300 hover:-translate-y-1 hover:scale-[1.01]"
>
<div className="text-xs font-semibold uppercase tracking-[0.3em] text-slate-400">
{post.eyebrow}
</div>
<h3 className="mt-4 text-2xl font-semibold text-white">{post.title}</h3>
<p className="mt-4 text-sm leading-7 text-slate-300">{post.excerpt}</p>
<div className="mt-6 flex items-center justify-between text-sm text-slate-400">
<span>{post.category}</span>
<span>{post.readTime}</span>
</div>
</Link>
))}
</div>
</section>
<section className="mx-auto max-w-7xl px-4 pb-20 pt-8 sm:px-6 lg:px-8">
<div className="rounded-[32px] border border-white/10 bg-gradient-to-r from-[#312e81]/60 via-slate-950/80 to-[#0f766e]/40 p-8 shadow-[0_35px_90px_-35px_rgba(99,102,241,0.85)] backdrop-blur-xl sm:p-10 lg:flex lg:items-center lg:justify-between">
<div className="max-w-2xl">
<p className="text-sm font-semibold uppercase tracking-[0.3em] text-[#93c5fd]">
Ready for the first revenue recovery win?
</p>
<h2 className="mt-4 text-3xl font-semibold text-white sm:text-4xl">
Launch an approval-ready public surface and a real operator workflow in one move.
</h2>
<p className="mt-4 text-base leading-7 text-slate-300">
Buy the plan, open the admin interface, configure your first gateway, and start generating recovery messages immediately.
</p>
</div>
<div className="mt-8 flex flex-col gap-3 sm:flex-row lg:mt-0">
<a
href={lemonCheckoutUrl}
target="_blank"
rel="noreferrer"
className="inline-flex items-center justify-center rounded-full bg-white px-6 py-3 text-sm font-semibold text-slate-950 transition-transform duration-300 hover:scale-[1.02]"
>
Buy now
</a>
<Link
href="/login"
className="inline-flex items-center justify-center rounded-full border border-white/15 px-6 py-3 text-sm font-semibold text-white transition-all duration-300 hover:bg-white/10"
>
Login
</Link>
</div>
</div>
</section>
</PublicMarketingShell>
</>
);
}
Starter.getLayout = function getLayout(page: ReactElement) {
HomePage.getLayout = function getLayout(page: ReactElement) {
return <LayoutGuest>{page}</LayoutGuest>;
};

View File

@ -0,0 +1,99 @@
import React from 'react';
import type { ReactElement } from 'react';
import Head from 'next/head';
import LayoutGuest from '../layouts/Guest';
import PublicMarketingShell from '../components/marketing/PublicMarketingShell';
import { getPageTitle } from '../config';
import { lemonCheckoutUrl, pricingTiers } from '../data/rrlabsContent';
export default function PricingPage() {
return (
<>
<Head>
<title>{getPageTitle('Pricing')}</title>
<meta
name="description"
content="Compare Revenue Recovery Labs plans and purchase through Lemon Squeezy."
/>
</Head>
<PublicMarketingShell>
<section className="mx-auto max-w-7xl px-4 py-16 sm:px-6 lg:px-8 lg:py-20">
<div className="mx-auto max-w-3xl text-center">
<p className="text-sm font-semibold uppercase tracking-[0.3em] text-[#06b6d4]">
Pricing
</p>
<h1 className="mt-4 text-4xl font-semibold tracking-tight text-white sm:text-5xl lg:text-6xl">
Premium revenue recovery plans that scale from launch to enterprise ops.
</h1>
<p className="mt-6 text-lg leading-8 text-slate-300">
Every plan includes the RRLabs brand experience, recovery workspace,
public compliance pages, and a direct path into your admin interface.
</p>
</div>
<div className="mt-14 grid gap-6 lg:grid-cols-3">
{pricingTiers.map((tier) => (
<article
key={tier.name}
className={`flex h-full flex-col rounded-[32px] border p-8 backdrop-blur-xl ${
tier.featured
? 'border-[#6366f1]/45 bg-gradient-to-b from-[#6366f1]/15 via-slate-950/85 to-[#06b6d4]/10 shadow-[0_35px_90px_-35px_rgba(99,102,241,0.75)]'
: 'border-white/10 bg-white/5 shadow-[0_25px_80px_-40px_rgba(15,23,42,1)]'
}`}
>
<div className="flex items-start justify-between gap-4">
<div>
<h2 className="text-2xl font-semibold text-white">{tier.name}</h2>
<p className="mt-4 text-sm leading-7 text-slate-300">{tier.tagline}</p>
</div>
<span className="rounded-full border border-white/10 bg-slate-950/70 px-3 py-1 text-[11px] uppercase tracking-[0.25em] text-slate-300">
{tier.highlight}
</span>
</div>
<div className="mt-8 flex items-end gap-1 text-white">
<span className="text-5xl font-semibold tracking-tight">{tier.price}</span>
<span className="mb-1 text-slate-400">{tier.cadence}</span>
</div>
<ul className="mt-8 space-y-4 text-sm leading-7 text-slate-200">
{tier.features.map((feature) => (
<li key={feature} className="flex gap-3">
<span className="mt-1 h-2.5 w-2.5 rounded-full bg-gradient-to-r from-[#6366f1] to-[#06b6d4]" />
<span>{feature}</span>
</li>
))}
</ul>
<div className="mt-10 pt-2">
<a
href={lemonCheckoutUrl}
target="_blank"
rel="noreferrer"
className={`inline-flex w-full items-center justify-center rounded-full px-5 py-3 text-sm font-semibold transition-transform duration-300 hover:scale-[1.02] ${
tier.featured
? 'bg-gradient-to-r from-[#6366f1] to-[#06b6d4] text-white shadow-[0_16px_40px_rgba(99,102,241,0.45)]'
: 'border border-white/10 bg-white/5 text-white hover:bg-white/10'
}`}
>
{tier.ctaLabel}
</a>
</div>
</article>
))}
</div>
<div className="mt-12 rounded-[28px] border border-white/10 bg-slate-950/65 p-6 text-sm leading-7 text-slate-300 shadow-[0_25px_80px_-40px_rgba(6,182,212,0.55)] backdrop-blur-xl sm:p-8">
Need procurement support, security review help, or a custom rollout plan?
Enterprise buyers can start with the same checkout flow and continue the
conversation with onboarding after purchase.
</div>
</section>
</PublicMarketingShell>
</>
);
}
PricingPage.getLayout = function getLayout(page: ReactElement) {
return <LayoutGuest>{page}</LayoutGuest>;
};

View File

@ -1,292 +1,13 @@
import React, { useEffect, useState } from 'react';
import React from 'react';
import type { ReactElement } from 'react';
import Head from 'next/head';
import LayoutGuest from '../layouts/Guest';
import { getPageTitle } from '../config';
import LegalPage from '../components/marketing/LegalPage';
import { legalDocuments } from '../data/rrlabsContent';
export default function PrivacyPolicy() {
const title = 'Revenue Recovery Labs'
const [projectUrl, setProjectUrl] = useState('');
useEffect(() => {
setProjectUrl(location.origin);
}, []);
const Introduction = () => {
return (
<>
<h3>1. Introduction</h3>
<p>
{/* eslint-disable-next-line react/no-unescaped-entities */}
We at <span>{title}</span> ("we", "us", "our") are committed to
protecting your privacy. This Privacy Policy explains how we collect,
use, disclose, and safeguard your information when you visit our
website <a href={projectUrl}>{projectUrl}</a>, use our services, or
interact with us in other ways. By using our services, you agree to
the collection and use of information in accordance with this policy.
</p>
</>
);
};
const Information = () => {
return (
<>
<h3>2. Information We Collect</h3>
<div className='ml-2'>
<h4>2.1 Personal Identification Information</h4>
<p>
We collect various types of personal information in connection with
the services we provide, including:
</p>
<ul role='list'>
<li>
Contact Information: Name, email address, phone number, mailing
address.
</li>
<li>Account Information: Username, password, profile picture.</li>
<li>Payment Information: Credit card details, billing address.</li>
<li>Demographic Information: Age, gender, interests.</li>
</ul>
<h4>2.2 Technical Data</h4>
<p>
We automatically collect certain information when you visit, use, or
navigate our services. This information may include:
</p>
<ul role='list'>
<li>
Device Information: IP address, browser type, operating system,
device type.
</li>
<li>
Usage Data: Pages visited, time spent on each page, links clicked,
and other actions taken on our site.
</li>
</ul>
<h4>2.3 Cookies and Tracking Technologies</h4>
<p>
We use cookies and similar tracking technologies to track the
activity on our service and hold certain information. You can
instruct your browser to refuse all cookies or to indicate when a
cookie is being sent.
</p>
</div>
</>
);
};
const HowToUser = () => {
return (
<>
<h3>3. How We Use Your Information</h3>
<p>We use the information we collect in various ways, including to:</p>
<ul role='list' className=''>
<li>Provide, operate, and maintain our website and services.</li>
<li>Improve, personalize, and expand our website and services.</li>
<li>Understand and analyze how you use our website and services.</li>
<li>Develop new products, services, features, and functionality.</li>
<li>
Communicate with you, either directly or through one of our
partners, including for customer service, to provide you with
updates and other information relating to the website, and for
marketing and promotional purposes.
</li>
<li>
Process your transactions and send you related information,
including purchase confirmations and invoices.
</li>
<li>Find and prevent fraud.</li>
<li>Comply with legal obligations.</li>
</ul>
</>
);
};
const DataProtection = () => {
return (
<>
<h3>4. Data Protection and Security</h3>
<p>
We implement a variety of security measures to maintain the safety of
your personal information. These measures include:
</p>
<ul role='list'>
<li>
Encryption: We use encryption to protect sensitive information
transmitted online. Access Controls: We restrict access to your
personal data to authorized personnel only. Regular Security Audits:
We conduct regular audits to identify and address potential security
vulnerabilities.
</li>
</ul>
</>
);
};
const Sharing = () => {
return (
<>
<h3>5. Sharing Your Information</h3>
<p>
We do not sell, trade, or otherwise transfer your Personally
Identifiable Information to outside parties without your consent,
except in the following cases:
</p>
<ul role='list'>
<li>
Service Providers: We may share your information with third-party
service providers who perform services on our behalf, such as
payment processing, data analysis, email delivery, hosting services,
customer service, and marketing assistance.
</li>
<li>
Business Transfers: In the event of a merger, acquisition, or sale
of all or a portion of our assets, your information may be
transferred as part of that transaction.
</li>
<li>
Legal Requirements: We may disclose your information if required to
do so by law or in response to valid requests by public authorities
(e.g., a court or a government agency).
</li>
</ul>
</>
);
};
const ProtectionRights = () => {
return (
<>
<h3>6. Your Data Protection Rights</h3>
<p>
Depending on your location, you may have the following rights
regarding your personal data:
</p>
<ul role='list'>
<li>
The Right to Access: You have the right to request copies of your
personal data.
</li>
<li>
The Right to Rectification: You have the right to request that we
correct any information you believe is inaccurate or complete
information you believe is incomplete.
</li>
<li>
The Right to Erasure: You have the right to request that we erase
your personal data, under certain conditions.
</li>
<li>
The Right to Restrict Processing: You have the right to request that
we restrict the processing of your personal data, under certain
conditions.
</li>
<li>
The Right to Object to Processing: You have the right to object to
our processing of your personal data, under certain conditions.
</li>
<li>
The Right to Data Portability: You have the right to request that we
transfer the data that we have collected to another organization, or
directly to you, under certain conditions.
</li>
</ul>
</>
);
};
const DataTransfers = () => {
return (
<>
<h3>7. International Data Transfers</h3>
<p>
Your information, including personal data, may be transferred to and
maintained on computers located outside of your state, province,
country, or other governmental jurisdiction where the data protection
laws may differ from those of your jurisdiction. We will take all
steps reasonably necessary to ensure that your data is treated
securely and in accordance with this Privacy Policy.
</p>
</>
);
};
const RetentionOfData = () => {
return (
<>
<h3>8. Retention of Data</h3>
<p>
We will retain your personal data only for as long as is necessary for
the purposes set out in this Privacy Policy. We will retain and use
your personal data to the extent necessary to comply with our legal
obligations, resolve disputes, and enforce our policies.
</p>
</>
);
};
const ChangePrivacy = () => {
return (
<>
<h3>9. Changes to This Privacy Policy</h3>
<p>
We may update our Privacy Policy from time to time. We will notify you
of any changes by posting the new Privacy Policy on this page. You are
advised to review this Privacy Policy periodically for any changes.
Changes to this Privacy Policy are effective when they are posted on
this page.
</p>
</>
);
};
const ContactUs = () => {
return (
<>
<h3>10. Contact Us</h3>
<p>
If you have any questions about this Privacy Policy, please contact
us:
</p>
<div>
By email:{' '}
<a href='mailto:support@flatlogic.com'> [support@flatlogic.com]</a>
</div>
<div>
By visiting this page on our website:{' '}
<a href='https://flatlogic.com/contact'>Contact Us</a>
</div>
</>
);
};
return (
<div className='prose prose-slate mx-auto max-w-none'>
<Head>
<title>{getPageTitle('Privacy Policy')}</title>
</Head>
<div className='flex justify-center'>
<div className='z-10 md:w-10/12 my-4 bg-white border border-pavitra-400 rounded'>
<div className='p-8 lg:px-12 lg:py-10'>
<h1>Privacy Policy</h1>
<Introduction />
<Information />
<HowToUser />
<DataProtection />
<Sharing />
<ProtectionRights />
<DataTransfers />
<RetentionOfData />
<ChangePrivacy />
<ContactUs />
</div>
</div>
</div>
</div>
);
export default function PrivacyPolicyPage() {
return <LegalPage document={legalDocuments.privacyPolicy} />;
}
PrivacyPolicy.getLayout = function getLayout(page: ReactElement) {
PrivacyPolicyPage.getLayout = function getLayout(page: ReactElement) {
return <LayoutGuest>{page}</LayoutGuest>;
};

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,13 @@
import React from 'react';
import type { ReactElement } from 'react';
import LayoutGuest from '../layouts/Guest';
import LegalPage from '../components/marketing/LegalPage';
import { legalDocuments } from '../data/rrlabsContent';
export default function RefundPolicyPage() {
return <LegalPage document={legalDocuments.refundPolicy} />;
}
RefundPolicyPage.getLayout = function getLayout(page: ReactElement) {
return <LayoutGuest>{page}</LayoutGuest>;
};

View File

@ -1,9 +1,7 @@
import React, { ReactElement, useEffect, useState } from 'react';
import Head from 'next/head';
import 'react-datepicker/dist/react-datepicker.css';
import { useAppDispatch } from '../stores/hooks';
import { useAppSelector } from '../stores/hooks';
import { useAppDispatch, useAppSelector } from '../stores/hooks';
import { useRouter } from 'next/router';
import LayoutAuthenticated from '../layouts/Authenticated';

View File

@ -0,0 +1,13 @@
import React from 'react';
import type { ReactElement } from 'react';
import LayoutGuest from '../layouts/Guest';
import LegalPage from '../components/marketing/LegalPage';
import { legalDocuments } from '../data/rrlabsContent';
export default function TermsOfServicePage() {
return <LegalPage document={legalDocuments.termsOfService} />;
}
TermsOfServicePage.getLayout = function getLayout(page: ReactElement) {
return <LayoutGuest>{page}</LayoutGuest>;
};

View File

@ -1,206 +1,13 @@
import React, { useEffect, useState } from 'react';
import React from 'react';
import type { ReactElement } from 'react';
import Head from 'next/head';
import LayoutGuest from '../layouts/Guest';
import { getPageTitle } from '../config';
import LegalPage from '../components/marketing/LegalPage';
import { legalDocuments } from '../data/rrlabsContent';
export default function PrivacyPolicy() {
const title = 'Revenue Recovery Labs';
const [projectUrl, setProjectUrl] = useState('');
useEffect(() => {
setProjectUrl(location.origin);
}, []);
const Information = () => {
return (
<>
<h3>1. Acceptance of Terms</h3>
<div className=''>
<p>
By accessing and using our application, you agree to comply with and
be bound by these Terms of Use. If you do not agree to these terms,
please do not use the application.
</p>
</div>
</>
);
};
const ChangesTerms = () => {
return (
<>
<h3>2. Changes to Terms</h3>
<p>
We reserve the right to modify these Terms of Use at any time. Any
changes will be effective immediately upon posting. Your continued use
of the application after any such changes constitutes your acceptance
of the new terms.
</p>
</>
);
};
const UseApplication = () => {
return (
<>
<h3>3. Use of the Application</h3>
<p>
You agree to use the application only for lawful purposes and in a way
that does not infringe the rights of, restrict, or inhibit anyone
elses use and enjoyment of the application. Prohibited behavior
includes harassing or causing distress or inconvenience to any other
user, transmitting obscene or offensive content, or disrupting the
normal flow of dialogue within the application.
</p>
</>
);
};
const IntellectualProperty = () => {
return (
<>
<h3>4. Intellectual Property</h3>
<p>
All content included on the application, such as text, graphics,
logos, images, and software, is the property of {title} or
its content suppliers and protected by international copyright laws.
Unauthorized use of the content may violate copyright, trademark, and
other laws.
</p>
</>
);
};
const UserContent = () => {
return (
<>
<h3>5. User Content</h3>
<p>
You are responsible for any content you upload, post, or otherwise
make available through the application. You grant {title}
a worldwide, irrevocable, non-exclusive, royalty-free license to use,
reproduce, modify, publish, and distribute such content for any
purpose.
</p>
</>
);
};
const Privacy = () => {
return (
<>
<h3>6. Privacy</h3>
<p>
Your privacy is important to us. Please review our Privacy Policy to
understand our practices regarding the collection, use, and disclosure
of your personal information.
</p>
</>
);
};
const Liability = () => {
return (
<>
<h3>7. Limitation of Liability</h3>
<p>
The application is provided as is and as available without any
warranties of any kind, either express or implied. {title}
does not warrant that the application will be uninterrupted or
error-free. In no event shall {title} be liable for any
damages arising out of your use of the application.
</p>
</>
);
};
const Indemnification = () => {
return (
<>
<h3>8. Indemnification</h3>
<p>
You agree to indemnify, defend, and hold harmless {title},
its officers, directors, employees, and agents from and against any
claims, liabilities, damages, losses, and expenses, including without
limitation reasonable legal and accounting fees, arising out of or in
any way connected with your access to or use of the application or
your violation of these Terms of Use.
</p>
</>
);
};
const Termination = () => {
return (
<>
<h3>9. Termination</h3>
<p>
We reserve the right to terminate or suspend your access to the
application at our sole discretion, without notice and without
liability, for any reason, including if we believe you have violated
these Terms of Use.
</p>
</>
);
};
const GoverningLaw = () => {
return (
<>
<h3>10. Governing Law</h3>
<p>
These Terms of Use are governed by and interpreted in accordance with
applicable laws, without regard to any conflict of law principles. You
agree to submit to the exclusive jurisdiction of the courts that have
authority to resolve any dispute arising from the use of the
application.
</p>
</>
);
};
const ContactUs = () => {
return (
<>
<h3>11. Contact Information</h3>
<p>
If you have any questions about these Terms of Use, please contact us
at: <a href='mailto:support@flatlogic.com'> [support@flatlogic.com]</a>
</p>
</>
);
};
return (
<div className='prose prose-slate mx-auto max-w-none'>
<Head>
<title>{getPageTitle('Terms of Use')}</title>
</Head>
<div className='flex justify-center'>
<div className='z-10 md:w-10/12 my-4 bg-white border border-pavitra-400 rounded'>
<div className='p-8 lg:px-12 lg:py-10'>
<h1>Terms of Use</h1>
<Information />
<ChangesTerms />
<UseApplication />
<IntellectualProperty />
<UserContent />
<Privacy />
<Liability />
<Indemnification />
<Termination />
<GoverningLaw />
<ContactUs />
</div>
</div>
</div>
</div>
);
export default function TermsOfUsePage() {
return <LegalPage document={legalDocuments.termsOfService} />;
}
PrivacyPolicy.getLayout = function getLayout(page: ReactElement) {
TermsOfUsePage.getLayout = function getLayout(page: ReactElement) {
return <LayoutGuest>{page}</LayoutGuest>;
};