Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
a3f913d
Initial cut of move away from single-page app.
vishakh Apr 14, 2026
746b2aa
Bit of SEO
vishakh Apr 14, 2026
48d7ddf
Refactored the onboarding experience.
vishakh Apr 23, 2026
45ebdb6
Fixed the accuracy and performance of Explore search.
vishakh Apr 24, 2026
ac9f234
Updated Homepage for succinctness and clarity
vishakh Apr 24, 2026
5ffea35
Changed social links.
vishakh Apr 24, 2026
b39d1c4
Made the app responsive.
vishakh Apr 24, 2026
f5b1333
Improved study page.
vishakh Apr 24, 2026
4de0898
Broke up and refactored the Premium tab.
vishakh Apr 27, 2026
0a071b1
Refactored the layout of DNA Chat.
vishakh Apr 27, 2026
eec3320
Refactored payment flow.
vishakh Apr 27, 2026
629d3d1
Refactored analaytics.
vishakh Apr 27, 2026
13d4751
More guided tours and layout changes for mobile.
vishakh Apr 28, 2026
7402cf8
Refined Google Analytics usage.
vishakh Apr 28, 2026
f46662e
Switch to new Gemma model.
vishakh Apr 28, 2026
57907ed
Switch to new Gemma model, using expanded context to support bigger d…
vishakh Apr 28, 2026
b490954
Made some onboasrding journey tweaks.
vishakh Apr 28, 2026
066f7d1
Added OpenGraph tags.
vishakh Apr 30, 2026
afc9472
Changed the order of social media icons.
vishakh Apr 30, 2026
fe8c6e0
Tweaked the DNA Chat tour.
vishakh Apr 30, 2026
df31d6d
Tweaked the DNA Chat tour.
vishakh Apr 30, 2026
0167d99
Fixed an LLM caching issues.
vishakh Apr 30, 2026
ccd2959
Tweked OVerview Report render.
vishakh May 1, 2026
e8446ea
Added some time staggering to Overview Report generation.
vishakh May 1, 2026
5e0d634
Fixed some build errors.
vishakh May 1, 2026
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,5 @@ yarn-error.log*
/localdata/
/venv/
/embeddings_backup.npz
/AGENTS.md
/CLAUDE.md
Binary file added Onboarding Wireframes.pdf
Binary file not shown.
195 changes: 195 additions & 0 deletions app/api/sample-genotype/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
import { NextResponse } from 'next/server';
import { inflateRawSync } from 'node:zlib';

const SAMPLE_DATA_URL = 'https://drive.google.com/uc?export=download&id=1WK3zZbqmu3_m6LvoQCylyIbWBkoO5pGI';

const textDecoder = new TextDecoder();

function isZipPayload(bytes: Uint8Array): boolean {
return bytes.length >= 4
&& bytes[0] === 0x50
&& bytes[1] === 0x4b
&& bytes[2] === 0x03
&& bytes[3] === 0x04;
}

function findEndOfCentralDirectory(bytes: Uint8Array): number {
const minOffset = Math.max(0, bytes.length - 0xffff - 22);

for (let offset = bytes.length - 22; offset >= minOffset; offset -= 1) {
if (
bytes[offset] === 0x50
&& bytes[offset + 1] === 0x4b
&& bytes[offset + 2] === 0x05
&& bytes[offset + 3] === 0x06
) {
return offset;
}
}

return -1;
}

function extractFirstZipEntry(bytes: Uint8Array): { filename: string; data: Uint8Array } | null {
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
const eocdOffset = findEndOfCentralDirectory(bytes);

if (eocdOffset < 0) return null;

const centralDirectoryOffset = view.getUint32(eocdOffset + 16, true);
if (centralDirectoryOffset + 46 > bytes.length) return null;
if (view.getUint32(centralDirectoryOffset, true) !== 0x02014b50) return null;

const compressionMethod = view.getUint16(centralDirectoryOffset + 10, true);
const compressedSize = view.getUint32(centralDirectoryOffset + 20, true);
const fileNameLength = view.getUint16(centralDirectoryOffset + 28, true);
const extraLength = view.getUint16(centralDirectoryOffset + 30, true);
const commentLength = view.getUint16(centralDirectoryOffset + 32, true);
const localHeaderOffset = view.getUint32(centralDirectoryOffset + 42, true);
const filenameStart = centralDirectoryOffset + 46;
const filenameEnd = filenameStart + fileNameLength;

if (filenameEnd > bytes.length) return null;

const filename = textDecoder.decode(bytes.slice(filenameStart, filenameEnd));
const nextEntryOffset = filenameEnd + extraLength + commentLength;
if (nextEntryOffset > bytes.length) return null;

if (localHeaderOffset + 30 > bytes.length) return null;
if (view.getUint32(localHeaderOffset, true) !== 0x04034b50) return null;

const localNameLength = view.getUint16(localHeaderOffset + 26, true);
const localExtraLength = view.getUint16(localHeaderOffset + 28, true);
const dataStart = localHeaderOffset + 30 + localNameLength + localExtraLength;
const dataEnd = dataStart + compressedSize;

if (dataEnd > bytes.length) return null;

const compressed = bytes.slice(dataStart, dataEnd);

if (compressionMethod === 0) {
return { filename, data: compressed };
}

if (compressionMethod === 8) {
return { filename, data: inflateRawSync(compressed) };
}

return null;
}

function collectCookieHeader(response: Response): string {
const header = response.headers.get('set-cookie');
if (!header) return '';
return header
.split(/,(?=[^;]+=[^;]+)/)
.map((cookie) => cookie.split(';')[0]?.trim())
.filter(Boolean)
.join('; ');
}

function extractConfirmedDownloadUrl(html: string): string | null {
const formMatch = html.match(/<form[^>]+id="download-form"[^>]+action="([^"]+)"/i);
if (formMatch) {
const action = formMatch[1];
const url = new URL(action, 'https://drive.google.com');
const inputRegex = /<input[^>]+type="hidden"[^>]+name="([^"]+)"[^>]+value="([^"]*)"/gi;
let inputMatch: RegExpExecArray | null;

while ((inputMatch = inputRegex.exec(html)) !== null) {
url.searchParams.set(inputMatch[1], inputMatch[2]);
}

return url.toString();
}

const hrefMatch = html.match(/href="(\/uc\?export=download[^"]+)"/i);
if (hrefMatch) {
return new URL(hrefMatch[1].replace(/&amp;/g, '&'), 'https://drive.google.com').toString();
}

return null;
}

export async function GET() {
try {
let upstream = await fetch(SAMPLE_DATA_URL, {
method: 'GET',
redirect: 'follow',
cache: 'no-store',
});

if (!upstream.ok) {
return NextResponse.json(
{ error: `Sample data upstream failed with status ${upstream.status}` },
{ status: 502 }
);
}

let contentType = upstream.headers.get('content-type') || 'text/plain; charset=utf-8';

if (contentType.includes('text/html')) {
const html = await upstream.text();
const confirmedUrl = extractConfirmedDownloadUrl(html);

if (!confirmedUrl) {
return NextResponse.json(
{ error: 'Sample data upstream returned an HTML confirmation page instead of the genotype file.' },
{ status: 502 }
);
}

upstream = await fetch(confirmedUrl, {
method: 'GET',
redirect: 'follow',
cache: 'no-store',
headers: {
Cookie: collectCookieHeader(upstream),
},
});

if (!upstream.ok) {
return NextResponse.json(
{ error: `Sample data confirmed download failed with status ${upstream.status}` },
{ status: 502 }
);
}

contentType = upstream.headers.get('content-type') || 'text/plain; charset=utf-8';
}

const data = await upstream.arrayBuffer();
let outputBytes = new Uint8Array(data);
let outputFileName = 'monadicdna-sample-data.txt';

if (isZipPayload(outputBytes)) {
const extracted = extractFirstZipEntry(outputBytes);

if (!extracted) {
return NextResponse.json(
{ error: 'Sample data upstream returned a ZIP archive that could not be unpacked.' },
{ status: 502 }
);
}

outputBytes = new Uint8Array(extracted.data) as unknown as Uint8Array<ArrayBuffer>;
outputFileName = extracted.filename || outputFileName;
contentType = 'text/plain; charset=utf-8';
}

return new NextResponse(outputBytes, {
status: 200,
headers: {
'Content-Type': contentType,
'Content-Disposition': `inline; filename="${outputFileName}"`,
'Cache-Control': 'no-store',
},
});
} catch (error) {
console.error('[sample-genotype] Failed to fetch sample data:', error);
return NextResponse.json(
{ error: error instanceof Error ? error.message : 'Failed to fetch sample genotype data' },
{ status: 500 }
);
}
}
6 changes: 2 additions & 4 deletions app/api/stripe/create-checkout/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ const stripe = new Stripe(process.env.STRIPE_SECRET_KEY || 'sk_test_placeholder'
apiVersion: '2025-02-24.acacia',
});

const DAYS_PER_MONTH = 30;

export async function POST(request: NextRequest) {
try {
const { walletAddress, couponCode } = await request.json();
Expand Down Expand Up @@ -99,8 +97,8 @@ export async function POST(request: NextRequest) {
},
],
discounts,
success_url: `${origin}/payment/success?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${origin}/payment/cancel`,
success_url: `${origin}/subscribe/confirmed?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${origin}/subscribe?payment=cancelled`,
metadata: {
walletAddress: walletAddress.toLowerCase(),
},
Expand Down
Loading
Loading