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
53 changes: 53 additions & 0 deletions frontend/src/app/contact/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
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: '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."
>
<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>
<a href={`mailto:${siteConfig.contactEmail}`}>
{siteConfig.contactEmail}
</a>
</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>
);
}
15 changes: 15 additions & 0 deletions frontend/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,21 @@ export const metadata: Metadata = {
alternates: {
canonical: '/',
},
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 Down
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 },
);
}
10 changes: 3 additions & 7 deletions frontend/src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
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';

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 (
Expand Down
60 changes: 60 additions & 0 deletions frontend/src/app/privacy/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
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">
<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,
};
}
59 changes: 59 additions & 0 deletions frontend/src/app/terms/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
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: 'Terms of Service',
description: `The terms governing use of the Retailytics platform from ${siteConfig.legalName}.`,
path: '/terms',
index: false,
});

export default function TermsPage() {
return (
<SitePage title="Terms of Service" updated="June 2026">
<p>
These Terms of Service (&quot;Terms&quot;) govern your access to and use
of the Retailytics platform provided by {siteConfig.legalName}
(&quot;we&quot;, &quot;us&quot;). By creating an account or using the
service, you agree to these Terms.
</p>

<h2>Use of the service</h2>
<p>
You may use Retailytics only in compliance with these Terms and
applicable law. You are responsible for activity under your account and
for keeping your credentials secure.
</p>

<h2>Data and content</h2>
<p>
You retain rights to the data you submit. You grant us the rights needed
to host, process, and analyse that data to provide the service.
</p>

<h2>Acceptable use</h2>
<p>
You agree not to misuse the service, attempt to disrupt it, or use it to
collect data unlawfully or without proper authorisation.
</p>

<h2>Availability &amp; disclaimer</h2>
<p>
The service is provided on an &quot;as is&quot; basis. We do not warrant
that it will be uninterrupted or error-free, and we are not liable for
indirect or consequential damages to the extent permitted by law.
</p>

<h2>Contact</h2>
<p>
Questions about these Terms? Contact{' '}
<a href={`mailto:${siteConfig.contactEmail}`}>
{siteConfig.contactEmail}
</a>
.
</p>
</SitePage>
);
}
1 change: 1 addition & 0 deletions frontend/src/app/twitter-image.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default, alt, size, contentType } from '~/app/opengraph-image';
Loading
Loading