Fix OG image, CBT filters, syllabus split, and trial duration
This commit is contained in:
@@ -12,7 +12,9 @@ export async function GET(req: NextRequest) {
|
|||||||
const accuracy = searchParams.get('accuracy') || '0';
|
const accuracy = searchParams.get('accuracy') || '0';
|
||||||
const name = searchParams.get('name') || 'Student';
|
const name = searchParams.get('name') || 'Student';
|
||||||
|
|
||||||
// Renders a high-end, minimalist graphic using pure Tailwind and React
|
const target = searchParams.get('target') || 'JEE';
|
||||||
|
const plan = searchParams.get('plan') || 'Free';
|
||||||
|
|
||||||
return new ImageResponse(
|
return new ImageResponse(
|
||||||
(
|
(
|
||||||
<div
|
<div
|
||||||
@@ -21,38 +23,60 @@ export async function GET(req: NextRequest) {
|
|||||||
width: '100%',
|
width: '100%',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
alignItems: 'center',
|
justifyContent: 'space-between',
|
||||||
justifyContent: 'center',
|
backgroundColor: '#0a0a0a',
|
||||||
backgroundColor: '#0a0a0a', // Deep premium dark mode
|
|
||||||
color: '#ffffff',
|
color: '#ffffff',
|
||||||
fontFamily: 'sans-serif',
|
fontFamily: 'sans-serif',
|
||||||
padding: '40px',
|
padding: '60px',
|
||||||
border: '8px solid #333',
|
border: '8px solid #222',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div style={{ display: 'flex', fontSize: 60, fontWeight: 800, marginBottom: 20, letterSpacing: '-0.05em' }}>
|
{/* Header */}
|
||||||
PragatiVeer
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', width: '100%' }}>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||||
|
<div style={{ width: 40, height: 40, backgroundColor: '#06b6d4', borderRadius: 8, marginRight: 16 }} />
|
||||||
|
<span style={{ fontSize: 32, fontWeight: 900, letterSpacing: '0.05em' }}>PRAGATIVEER</span>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ display: 'flex', fontSize: 32, color: '#a3a3a3', marginBottom: 60 }}>
|
<div style={{ fontSize: 20, fontWeight: 700, color: '#06b6d4', backgroundColor: 'rgba(6,182,212,0.1)', padding: '8px 16px', borderRadius: 20, border: '1px solid rgba(6,182,212,0.2)' }}>
|
||||||
{name}'s JEE Preparation Progress
|
Weekly Report
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style={{ display: 'flex', width: '100%', justifyContent: 'space-around' }}>
|
|
||||||
{/* Streak Metric */}
|
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
|
|
||||||
<span style={{ fontSize: 80, fontWeight: 900, color: '#f59e0b' }}>{streak}🔥</span>
|
|
||||||
<span style={{ fontSize: 24, color: '#d4d4d4', marginTop: 10 }}>Day Streak</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Accuracy Metric */}
|
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
|
|
||||||
<span style={{ fontSize: 80, fontWeight: 900, color: '#10b981' }}>{accuracy}%</span>
|
|
||||||
<span style={{ fontSize: 24, color: '#d4d4d4', marginTop: 10 }}>Top Accuracy</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{ display: 'flex', position: 'absolute', bottom: 40, fontSize: 20, color: '#525252' }}>
|
{/* Main Content */}
|
||||||
Join me on pragativeer.com/invite
|
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', width: '100%', marginTop: 20 }}>
|
||||||
|
<h3 style={{ fontSize: 48, fontWeight: 900, marginBottom: 40 }}>You crushed it this week!</h3>
|
||||||
|
|
||||||
|
{/* 2x2 Grid */}
|
||||||
|
<div style={{ display: 'flex', flexWrap: 'wrap', width: '600px', justifyContent: 'center', gap: '24px' }}>
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', width: '280px', height: '160px', backgroundColor: 'rgba(255,255,255,0.05)', borderRadius: 24, border: '1px solid rgba(255,255,255,0.1)' }}>
|
||||||
|
<span style={{ fontSize: 48, fontWeight: 900, color: '#f59e0b', marginBottom: 8 }}>{streak}</span>
|
||||||
|
<span style={{ fontSize: 16, color: 'rgba(255,255,255,0.7)', textTransform: 'uppercase', fontWeight: 800, letterSpacing: '0.05em' }}>Day Streak</span>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', width: '280px', height: '160px', backgroundColor: 'rgba(255,255,255,0.05)', borderRadius: 24, border: '1px solid rgba(255,255,255,0.1)' }}>
|
||||||
|
<span style={{ fontSize: 48, fontWeight: 900, color: '#4ade80', marginBottom: 8 }}>{accuracy}%</span>
|
||||||
|
<span style={{ fontSize: 16, color: 'rgba(255,255,255,0.7)', textTransform: 'uppercase', fontWeight: 800, letterSpacing: '0.05em' }}>Accuracy</span>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', width: '280px', height: '160px', backgroundColor: 'rgba(255,255,255,0.05)', borderRadius: 24, border: '1px solid rgba(255,255,255,0.1)' }}>
|
||||||
|
<span style={{ fontSize: 48, fontWeight: 900, color: '#22d3ee', marginBottom: 8 }}>4</span>
|
||||||
|
<span style={{ fontSize: 16, color: 'rgba(255,255,255,0.7)', textTransform: 'uppercase', fontWeight: 800, letterSpacing: '0.05em' }}>Tests Taken</span>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', width: '280px', height: '160px', backgroundColor: 'rgba(255,255,255,0.05)', borderRadius: 24, border: '1px solid rgba(255,255,255,0.1)' }}>
|
||||||
|
<span style={{ fontSize: 48, fontWeight: 900, color: '#c084fc', marginBottom: 8 }}>78%</span>
|
||||||
|
<span style={{ fontSize: 16, color: 'rgba(255,255,255,0.7)', textTransform: 'uppercase', fontWeight: 800, letterSpacing: '0.05em' }}>Avg Score</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Footer */}
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-end', width: '100%', marginTop: 20 }}>
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
||||||
|
<div style={{ fontSize: 24, fontWeight: 800 }}>{name}</div>
|
||||||
|
<div style={{ fontSize: 18, color: 'rgba(255,255,255,0.7)', marginTop: 8 }}>{plan} Plan</div>
|
||||||
|
<div style={{ fontSize: 18, color: '#06b6d4', marginTop: 4 }}>{target} Aspirant</div>
|
||||||
|
</div>
|
||||||
|
<div style={{ fontSize: 18, color: 'rgba(255,255,255,0.5)', letterSpacing: '0.05em' }}>
|
||||||
|
contact@pragativeer.com
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -68,7 +68,9 @@ export default function CBTPage() {
|
|||||||
if (testRes.ok) {
|
if (testRes.ok) {
|
||||||
let testsData = await testRes.json();
|
let testsData = await testRes.json();
|
||||||
if (selectedPattern === "JEE") {
|
if (selectedPattern === "JEE") {
|
||||||
testsData = testsData.filter((t: any) => t.exam_pattern === "JEE_Main" || t.exam_pattern === "JEE_Advanced");
|
testsData = testsData.filter((t: any) => t.exam_pattern?.includes("JEE"));
|
||||||
|
} else if (selectedPattern === "NEET") {
|
||||||
|
testsData = testsData.filter((t: any) => t.exam_pattern === "NEET");
|
||||||
}
|
}
|
||||||
setExams(testsData);
|
setExams(testsData);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -376,7 +376,7 @@ export default function DashboardLayout({ children }: { children: ReactNode }) {
|
|||||||
{trialInfo.isTrialActive && (
|
{trialInfo.isTrialActive && (
|
||||||
<div className="bg-primary text-primary-foreground px-4 py-2.5 text-center text-sm font-bold flex justify-center items-center gap-2 shadow-sm shrink-0 z-30">
|
<div className="bg-primary text-primary-foreground px-4 py-2.5 text-center text-sm font-bold flex justify-center items-center gap-2 shadow-sm shrink-0 z-30">
|
||||||
<span className="size-2 rounded-full bg-primary-foreground animate-pulse"></span>
|
<span className="size-2 rounded-full bg-primary-foreground animate-pulse"></span>
|
||||||
15 Days Free Trial — Day {trialInfo.trialDayNumber}
|
{trialInfo.trialTotalDays} Days Free Trial — Day {trialInfo.trialDayNumber}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -81,20 +81,7 @@ const DEMO_SUBJECTS: Subject[] = [
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
// Math/Bio dynamic subjects handled below
|
||||||
name: "Mathematics / Biology",
|
|
||||||
completionPct: 22,
|
|
||||||
chapters: [
|
|
||||||
{
|
|
||||||
name: "Calculus",
|
|
||||||
isExpanded: false,
|
|
||||||
topics: [
|
|
||||||
{ name: "Limits", status: "In Progress", accuracy: 60, questionsAttempted: 45 },
|
|
||||||
{ name: "Derivatives", status: "Not Started", accuracy: 0, questionsAttempted: 0 },
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
|
|
||||||
const ZERO_SUBJECTS: Subject[] = [
|
const ZERO_SUBJECTS: Subject[] = [
|
||||||
@@ -143,20 +130,7 @@ const ZERO_SUBJECTS: Subject[] = [
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
// Math/Bio dynamic subjects handled below
|
||||||
name: "Mathematics / Biology",
|
|
||||||
completionPct: 0,
|
|
||||||
chapters: [
|
|
||||||
{
|
|
||||||
name: "Core Foundation",
|
|
||||||
isExpanded: false,
|
|
||||||
topics: [
|
|
||||||
{ name: "Limits / Cell Structure", status: "Not Started", accuracy: 0, questionsAttempted: 0 },
|
|
||||||
{ name: "Derivatives / Genetics", status: "Not Started", accuracy: 0, questionsAttempted: 0 },
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
|
|
||||||
export default function ProgressTrackerPage() {
|
export default function ProgressTrackerPage() {
|
||||||
@@ -166,19 +140,72 @@ export default function ProgressTrackerPage() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const targetExam = user?.target_exam || "JEE_Main";
|
const targetExam = user?.target_exam || "JEE_Main";
|
||||||
const mathOrBio = targetExam === "NEET" ? "Biology" : "Mathematics";
|
const isNeet = targetExam === "NEET";
|
||||||
|
|
||||||
const getSubjects = (demo: boolean) => {
|
const mathDemoSubject: Subject = {
|
||||||
const base = demo ? DEMO_SUBJECTS : ZERO_SUBJECTS;
|
name: "Mathematics",
|
||||||
return base.map(s => {
|
completionPct: 22,
|
||||||
if (s.name === "Mathematics / Biology") {
|
chapters: [
|
||||||
return { ...s, name: mathOrBio };
|
{
|
||||||
|
name: "Calculus",
|
||||||
|
isExpanded: true,
|
||||||
|
topics: [
|
||||||
|
{ name: "Limits", status: "In Progress", accuracy: 60, questionsAttempted: 45 },
|
||||||
|
{ name: "Derivatives", status: "Not Started", accuracy: 0, questionsAttempted: 0 },
|
||||||
|
]
|
||||||
}
|
}
|
||||||
return s;
|
]
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
setSubjects(getSubjects(isDemo));
|
const bioDemoSubject: Subject = {
|
||||||
|
name: "Biology",
|
||||||
|
completionPct: 35,
|
||||||
|
chapters: [
|
||||||
|
{
|
||||||
|
name: "Cell Structure",
|
||||||
|
isExpanded: true,
|
||||||
|
topics: [
|
||||||
|
{ name: "Cell Wall", status: "Practiced", accuracy: 80, questionsAttempted: 60 },
|
||||||
|
{ name: "Nucleus", status: "In Progress", accuracy: 50, questionsAttempted: 25 },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
const mathZeroSubject: Subject = {
|
||||||
|
name: "Mathematics",
|
||||||
|
completionPct: 0,
|
||||||
|
chapters: [
|
||||||
|
{
|
||||||
|
name: "Calculus",
|
||||||
|
isExpanded: true,
|
||||||
|
topics: [
|
||||||
|
{ name: "Limits", status: "Not Started", accuracy: 0, questionsAttempted: 0 },
|
||||||
|
{ name: "Derivatives", status: "Not Started", accuracy: 0, questionsAttempted: 0 },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
const bioZeroSubject: Subject = {
|
||||||
|
name: "Biology",
|
||||||
|
completionPct: 0,
|
||||||
|
chapters: [
|
||||||
|
{
|
||||||
|
name: "Cell Structure",
|
||||||
|
isExpanded: true,
|
||||||
|
topics: [
|
||||||
|
{ name: "Cell Wall", status: "Not Started", accuracy: 0, questionsAttempted: 0 },
|
||||||
|
{ name: "Nucleus", status: "Not Started", accuracy: 0, questionsAttempted: 0 },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
const base = isDemo ? [...DEMO_SUBJECTS] : [...ZERO_SUBJECTS];
|
||||||
|
base.push(isDemo ? (isNeet ? bioDemoSubject : mathDemoSubject) : (isNeet ? bioZeroSubject : mathZeroSubject));
|
||||||
|
|
||||||
|
setSubjects(base);
|
||||||
}, [isDemo, user?.target_exam]);
|
}, [isDemo, user?.target_exam]);
|
||||||
|
|
||||||
const toggleChapter = (subjectIndex: number, chapterIndex: number) => {
|
const toggleChapter = (subjectIndex: number, chapterIndex: number) => {
|
||||||
|
|||||||
@@ -14,9 +14,9 @@ export function ProgressShare() {
|
|||||||
const streak = "12"
|
const streak = "12"
|
||||||
const accuracy = "85"
|
const accuracy = "85"
|
||||||
const name = user?.name || "Student"
|
const name = user?.name || "Student"
|
||||||
|
const target = user?.target_exam === "NEET" ? "NEET" : "JEE"
|
||||||
// Use a fallback image if API endpoint is not yet ready, else use real endpoint
|
const plan = user?.tier === "Al_Coach" ? "AI Coach" : user?.tier || "Free"
|
||||||
const imageUrl = `/api/og/progress?streak=${streak}&accuracy=${accuracy}&name=${encodeURIComponent(name)}`
|
const imageUrl = `/api/og/progress?streak=${streak}&accuracy=${accuracy}&name=${encodeURIComponent(name)}&target=${target}&plan=${encodeURIComponent(plan)}`
|
||||||
|
|
||||||
const handleDownload = async () => {
|
const handleDownload = async () => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user