Skip to content
Merged
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
9 changes: 5 additions & 4 deletions frontend/src/app/(auth)/forgot-password/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ import { Suspense } from 'react';
import type { Metadata } from 'next';
import Loader from '~/components/loader';
import { ForgotPasswordForm } from './form';
import { buildMetadata } from '~/lib/metadata';

export const metadata: Metadata = {
export const metadata: Metadata = buildMetadata({
title: 'Reset Password',
description:
'Reset your Retailytics password to regain access to your retail intelligence dashboards and store data.',
alternates: { canonical: '/forgot-password' },
robots: { index: false, follow: true },
};
path: '/forgot-password',
index: false,
});

export default async function ForgotPasswordPage() {
return (
Expand Down
9 changes: 5 additions & 4 deletions frontend/src/app/(auth)/login/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ import type { Metadata } from 'next';
import { cache, Suspense } from 'react';
import Loader from '~/components/loader';
import { redirect } from 'next/navigation';
import { buildMetadata } from '~/lib/metadata';

export const metadata: Metadata = {
export const metadata: Metadata = buildMetadata({
title: 'Sign In',
description:
'Sign in to your Retailytics account to access store enumeration dashboards, field data, and market intelligence reports.',
alternates: { canonical: '/login' },
robots: { index: false, follow: true },
};
path: '/login',
index: false,
});

const getSession = cache(() => auth());

Expand Down
9 changes: 5 additions & 4 deletions frontend/src/app/(auth)/register/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ import { RegisterForm } from './form';
import { cache, Suspense } from 'react';
import Loader from '~/components/loader';
import { redirect } from 'next/navigation';
import { buildMetadata } from '~/lib/metadata';

export const metadata: Metadata = {
export const metadata: Metadata = buildMetadata({
title: 'Create Account',
description:
'Create your Retailytics account to start collecting store data, validating field submissions, and turning local data into market intelligence.',
alternates: { canonical: '/register' },
robots: { index: false, follow: true },
};
path: '/register',
index: false,
});

const getSession = cache(() => auth());

Expand Down
55 changes: 55 additions & 0 deletions frontend/src/app/contact/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import type { Metadata } from 'next';
import { siteConfig } from '~/lib/site';
import SitePage from '~/components/site-page';
import { buildMetadata } from '~/lib/metadata';
import { TrackedMailto } from '~/components/analytics/tracked-mailto';

export const metadata: Metadata = buildMetadata({
title: 'Contact',
description:
'Get in touch with the Retailytics team at Ajared Research Inc. for demos, partnerships, support, and questions about retail intelligence.',
path: '/contact',
});

export default function ContactPage() {
return (
<SitePage
title="Contact us"
description="Questions, demos, partnerships, or support — we'd love to hear from you."
breadcrumb={{ name: 'Contact', path: '/contact' }}
>
<p>
Retailytics is built and operated by {siteConfig.legalName}. The fastest
way to reach us is by email, and we aim to respond within two business
days.
</p>

<h2>Email</h2>
<p>
<TrackedMailto email={siteConfig.contactEmail} location="contact_page">
{siteConfig.contactEmail}
</TrackedMailto>
</p>

<h2>Sales &amp; demos</h2>
<p>
Want to see Retailytics in action for your market? Email us with a short
note about your team and the regions you cover, and we&apos;ll set up a
walkthrough.
</p>

<h2>Elsewhere</h2>
<p>
Learn more about the team at{' '}
<a href={siteConfig.social.parent} target="_blank" rel="noreferrer">
ajared.ng
</a>{' '}
and{' '}
<a href={siteConfig.social.parentCa} target="_blank" rel="noreferrer">
ajared.ca
</a>
.
</p>
</SitePage>
);
}
28 changes: 28 additions & 0 deletions frontend/src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
import './globals.css';
import { env } from '~/env';
import Providers from './providers';
import type { Metadata } from 'next';
import { Suspense } from 'react';
import Loader from '~/components/loader';
import { Outfit } from 'next/font/google';
import { siteConfig } from '~/lib/site';
import { JsonLd } from '~/components/json-ld';
import { Toaster } from '~/components/ui/sonner';
import { siteGraph } from '~/lib/structured-data';
import { Analytics } from '@vercel/analytics/next';
import {
GoogleTagManager,
GoogleTagManagerNoScript,
} from '~/components/analytics/google-tag-manager';

const outfit = Outfit({
variable: '--font-outfit',
Expand All @@ -28,6 +35,24 @@ export const metadata: Metadata = {
alternates: {
canonical: '/',
},
verification: env.GOOGLE_SITE_VERIFICATION
? { google: env.GOOGLE_SITE_VERIFICATION }
: undefined,
openGraph: {
type: 'website',
siteName: siteConfig.name,
locale: siteConfig.locale,
url: siteConfig.url,
title: `${siteConfig.name} — ${siteConfig.tagline}`,
description: siteConfig.description,
},
twitter: {
card: 'summary_large_image',
site: siteConfig.twitter.handle,
creator: siteConfig.twitter.handle,
title: `${siteConfig.name} — ${siteConfig.tagline}`,
description: siteConfig.description,
},
};

export default function RootLayout({
Expand All @@ -43,8 +68,11 @@ export default function RootLayout({
crossOrigin="anonymous"
src="https://tweakcn.com/live-preview.min.js"
/>
<JsonLd data={siteGraph()} />
<GoogleTagManager />
</head>
<body className={`${outfit.variable} antialiased`}>
<GoogleTagManagerNoScript />
<Suspense fallback={<Loader />}>
<Providers>
{children}
Expand Down
40 changes: 40 additions & 0 deletions frontend/src/app/llms.txt/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { absoluteUrl, siteConfig } from '~/lib/site';

const features = [
'Assign enumerators to specific geographic areas',
'Capture detailed retail store data in the field',
'Real-time submission with quality-controlled validation',
'Market analytics and reporting',
'Location and market intelligence',
];

export function GET() {
const body = `# ${siteConfig.name}

> ${siteConfig.description}

${siteConfig.name} is a retail intelligence platform built by ${siteConfig.legalName} — it helps teams assign enumerators to geographic areas, capture detailed retail store data in the field, validate submissions through quality control, and analyse the results to answer market questions.

## Core pages
- [Home](${absoluteUrl('/')}): Product overview, how it works, and FAQ.
- [How it works](${absoluteUrl('/#how-it-works')}): The four-step enumeration-to-insight workflow.
- [FAQ](${absoluteUrl('/#faq')}): Common questions about the platform and the data it collects.
- [Contact](${absoluteUrl('/contact')}): Reach the team for demos, partnerships, and support.

## Company
${siteConfig.name} is a product of ${siteConfig.legalName} (${siteConfig.social.parent}, ${siteConfig.social.parentCa}), an AI research and product studio that builds information systems and data products. Contact: ${siteConfig.contactEmail}.

## Key features
${features.map((feature) => `- ${feature}`).join('\n')}

## Legal
- [Privacy Policy](${absoluteUrl('/privacy')})
- [Terms of Service](${absoluteUrl('/terms')})
`;

return new Response(body, {
headers: {
'Content-Type': 'text/plain; charset=utf-8',
},
});
}
72 changes: 72 additions & 0 deletions frontend/src/app/opengraph-image.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { ImageResponse } from 'next/og';
import { siteConfig } from '~/lib/site';

export const alt = `${siteConfig.name} — ${siteConfig.tagline}`;
export const size = { width: 1200, height: 630 };
export const contentType = 'image/png';

export default function OpengraphImage() {
return new ImageResponse(
<div
style={{
height: '100%',
width: '100%',
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-between',
background:
'radial-gradient(circle at 20% 20%, #1e293b 0%, #0a0a0a 60%)',
padding: '80px',
color: '#f8fafc',
fontFamily: 'sans-serif',
}}
>
<div
style={{
display: 'flex',
alignItems: 'center',
fontSize: 34,
fontWeight: 700,
letterSpacing: '-0.02em',
}}
>
{siteConfig.name}
</div>

<div style={{ display: 'flex', flexDirection: 'column', gap: 28 }}>
<div
style={{
fontSize: 76,
fontWeight: 800,
lineHeight: 1.05,
letterSpacing: '-0.03em',
maxWidth: 900,
background: 'linear-gradient(90deg, #f8fafc 0%, #94a3b8 100%)',
backgroundClip: 'text',
color: 'transparent',
}}
>
{siteConfig.tagline}
</div>
<div style={{ fontSize: 30, color: '#94a3b8', maxWidth: 880 }}>
Store enumeration, field data collection, and market analysis in one
platform.
</div>
</div>

<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
fontSize: 26,
color: '#64748b',
}}
>
<span>{siteConfig.url.replace(/^https?:\/\//, '')}</span>
<span>by {siteConfig.legalName}</span>
</div>
</div>,
{ ...size },
);
}
12 changes: 5 additions & 7 deletions frontend/src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
import CTA from '~/components/cta';
import FAQ from '~/components/faq';
import Hero from '~/components/hero';
import type { Metadata } from 'next';
import { siteConfig } from '~/lib/site';
import Footer from '~/components/footer';
import type { Metadata } from 'next';
import { buildMetadata } from '~/lib/metadata';
import HowItWorks from '~/components/how-it-works';
import { EngagementTracker } from '~/components/analytics/engagement-tracker';

export const metadata: Metadata = {
title: { absolute: `${siteConfig.name} — ${siteConfig.tagline}` },
description: siteConfig.description,
alternates: { canonical: '/' },
};
export const metadata: Metadata = buildMetadata({ path: '/' });

export default function Home() {
return (
<main className="w-full h-full flex flex-col">
<EngagementTracker />
<Hero />
<HowItWorks />
<FAQ />
Expand Down
64 changes: 64 additions & 0 deletions frontend/src/app/privacy/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import type { Metadata } from 'next';
import { siteConfig } from '~/lib/site';
import SitePage from '~/components/site-page';
import { buildMetadata } from '~/lib/metadata';

export const metadata: Metadata = buildMetadata({
title: 'Privacy Policy',
description: `How ${siteConfig.legalName} collects, uses, and protects data on Retailytics.`,
path: '/privacy',
index: false,
});

export default function PrivacyPage() {
return (
<SitePage
title="Privacy Policy"
updated="June 2026"
breadcrumb={{ name: 'Privacy Policy', path: '/privacy' }}
>
<p>
This Privacy Policy explains how {siteConfig.legalName}
(&quot;we&quot;, &quot;us&quot;) handles information in connection with
the Retailytics platform. By using Retailytics you agree to the
practices described here.
</p>

<h2>Information we collect</h2>
<p>
We collect account details you provide (such as name and email),
operational data submitted through the platform (such as store and
location records captured by enumerators), and standard technical data
such as device and usage information.
</p>

<h2>How we use information</h2>
<p>
We use information to operate and improve the platform, validate and
analyse submitted data, secure accounts, and communicate with you about
the service.
</p>

<h2>Sharing</h2>
<p>
We do not sell personal information. We share data only with service
providers who help us operate Retailytics, or where required by law.
</p>

<h2>Data retention &amp; security</h2>
<p>
We retain data for as long as needed to provide the service and meet
legal obligations, and we apply appropriate safeguards to protect it.
</p>

<h2>Contact</h2>
<p>
For privacy questions or data requests, contact{' '}
<a href={`mailto:${siteConfig.contactEmail}`}>
{siteConfig.contactEmail}
</a>
.
</p>
</SitePage>
);
}
14 changes: 14 additions & 0 deletions frontend/src/app/robots.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { MetadataRoute } from 'next';
import { siteConfig } from '~/lib/site';

export default function robots(): MetadataRoute.Robots {
return {
rules: {
userAgent: '*',
allow: '/',
disallow: ['/api/', '/admin', '/user'],
},
sitemap: `${siteConfig.url}/sitemap.xml`,
host: siteConfig.url,
};
}
Loading
Loading