Session 9: api-football + FootApi + Tank01 adapters, grace period middleware, cookie consent, /pricing page, OOM fix documented (1240 tests)
This commit is contained in:
@@ -10,6 +10,7 @@ import InstallPrompt from '@/components/InstallPrompt';
|
||||
import PushPrompt from '@/components/PushPrompt';
|
||||
import MFAPrompt from '@/components/MFAPrompt';
|
||||
import MFAChallenge from '@/components/MFAChallenge';
|
||||
import CookieConsent from '@/components/CookieConsent';
|
||||
import './globals.css';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
@@ -19,7 +20,7 @@ export const metadata: Metadata = {
|
||||
template: '%s · VYNDR',
|
||||
},
|
||||
description:
|
||||
"Grade NBA, MLB, and WNBA props with intelligence the books don't want you to have. Built in Detroit.",
|
||||
"Grade NBA, MLB, WNBA, and soccer props with intelligence the books don't want you to have. World Cup 2026 intelligence: xG regression, altitude, referee, penalty taker. Built in Detroit.",
|
||||
applicationName: 'VYNDR',
|
||||
authors: [{ name: 'VYNDR', url: 'https://vyndr.app' }],
|
||||
manifest: '/manifest.json',
|
||||
@@ -28,6 +29,9 @@ export const metadata: Metadata = {
|
||||
'NBA prop bet analysis',
|
||||
'MLB prop intelligence',
|
||||
'WNBA prop grading',
|
||||
'soccer prop intelligence',
|
||||
'World Cup 2026 props',
|
||||
'xG regression analysis',
|
||||
'parlay correlation analysis',
|
||||
'prop betting tools',
|
||||
],
|
||||
@@ -104,6 +108,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
||||
<PushPrompt />
|
||||
<MFAPrompt />
|
||||
<MFAChallenge />
|
||||
<CookieConsent />
|
||||
</ParlayProvider>
|
||||
</ExplainModeProvider>
|
||||
</AuthProvider>
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import type { Metadata } from 'next';
|
||||
import Pricing from '@/components/Pricing';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Pricing — VYNDR',
|
||||
description: 'Founder pricing locks for life. $14.99/mo Analyst, $44.99/mo Desk. First 100 seats only.',
|
||||
openGraph: {
|
||||
title: 'VYNDR — Founder Pricing',
|
||||
description: 'Sports prop intelligence. Beta pricing locks for life. First 100 seats.',
|
||||
type: 'website',
|
||||
url: 'https://vyndr.app/pricing',
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
title: 'VYNDR — Founder Pricing',
|
||||
description: 'Sports prop intelligence. Beta pricing locks for life.',
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Dedicated /pricing route. The Pricing component is also embedded on
|
||||
* the landing page under the `#pricing` anchor; this page exists so:
|
||||
* - The renewal email at `web/src/services/email.ts` (which links
|
||||
* to `/pricing`) lands on something real instead of 404'ing.
|
||||
* - Nav / CTA links can hand users a single stable URL whether
|
||||
* they're already authenticated or not.
|
||||
* - SEO crawlers see pricing on a canonical URL, not deep in the
|
||||
* landing-page anchor.
|
||||
*
|
||||
* The component itself is fully client-side rendered (it owns
|
||||
* checkout state + AuthContext access), so this page wraps it in a
|
||||
* minimal scroll-restoration-friendly shell.
|
||||
*/
|
||||
export default function PricingPage() {
|
||||
return (
|
||||
<main style={{ minHeight: '100vh', paddingTop: 64 }}>
|
||||
<Pricing />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -18,7 +18,7 @@ const SECTIONS: { title: string; body: string[]; emphasized?: boolean }[] = [
|
||||
body: [
|
||||
'Account data: email, password hash, age confirmation, signup timestamp.',
|
||||
'Usage data: reads you run (player, stat, line, sport, grade), parlays you build, page views.',
|
||||
'Payment data: NexaPay processes all card data — we never see or store your card. We retain a NexaPay customer ID and your subscription status.',
|
||||
'Payment data: Stripe processes all card data — we never see or store your card number, CVC, or any other payment instrument detail. We retain a Stripe customer ID, Stripe subscription ID, and your subscription status (active, canceled, grace period, expired) so we can gate paid features and respond to renewal/cancellation events from Stripe webhooks.',
|
||||
'Device data: IP address, browser type, basic device info (for fraud prevention and analytics).',
|
||||
],
|
||||
},
|
||||
@@ -54,6 +54,16 @@ const SECTIONS: { title: string; body: string[]; emphasized?: boolean }[] = [
|
||||
'Analytics: PostHog (anonymized IPs, no third-party trackers). You can opt out by setting your browser to "Do Not Track" or contacting us.',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Sub-processors',
|
||||
body: [
|
||||
'We rely on a small set of third-party processors. They handle data on our behalf under their own privacy commitments; we do not give them permission to use your data for their own purposes.',
|
||||
'Stripe — payment processing and subscription management. Receives: name (if you provide), email, billing address (if you provide), and the card details you enter on their hosted checkout page. We never see your card number.',
|
||||
'Supabase — authentication, database, and file storage. Receives: everything in the "Data we collect" section above. Supabase is our primary backend.',
|
||||
'PostHog — product analytics. Receives: anonymized event data (page views, button clicks). IPs are anonymized before storage.',
|
||||
'Resend — transactional email (account confirmations, payment receipts, renewal reminders). Receives: your email address and the message contents.',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Notifications',
|
||||
body: [
|
||||
|
||||
@@ -29,7 +29,7 @@ const SECTIONS: { title: string; body: string[]; emphasized?: boolean }[] = [
|
||||
{
|
||||
title: 'Subscription terms',
|
||||
body: [
|
||||
'Paid tiers (Analyst, Desk) are billed monthly through NexaPay. You may cancel at any time from your profile page. Cancellation takes effect at the end of the current billing period. We do not refund for partial months.',
|
||||
'Paid tiers (Analyst, Desk) are billed monthly through Stripe, our payment processor. You may cancel at any time from your profile page. Cancellation takes effect at the end of the current billing period. We do not refund for partial months. If a payment fails, we honor a 48-hour grace period before reverting your account to the free tier.',
|
||||
'Founder pricing ($14.99/mo Analyst, $44.99/mo Desk) is locked for the lifetime of your continuous subscription. Lapsed subscriptions revert to standard pricing ($24.99 Analyst, $49.99 Desk) on re-subscription. After the first 100 founder seats are taken, new subscribers pay standard pricing.',
|
||||
'We may change regular pricing with 30 days notice. Founder pricing is locked.',
|
||||
],
|
||||
@@ -38,6 +38,7 @@ const SECTIONS: { title: string; body: string[]; emphasized?: boolean }[] = [
|
||||
title: 'Acceptable use',
|
||||
body: [
|
||||
'Do not scrape, reverse engineer, or attempt to replicate the grading engine. Do not resell reads, share account credentials, or attempt to circumvent the read limit. Do not use the service to abuse, harass, or defraud others.',
|
||||
'VYNDR does NOT offer API access at any tier. The grading engine is consumer-only — we do not provide programmatic access to grades, factor weights, model outputs, or any other engine surface, regardless of plan or pricing. Requests for API access will be declined.',
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user