Skip to content

Commit dcb0c4e

Browse files
authored
Merge pull request #2991 from tekdi/feat-landing-page
Feat landing page toprod fix branch
2 parents 6a8bb18 + c12809c commit dcb0c4e

20 files changed

Lines changed: 3230 additions & 528 deletions

File tree

Lines changed: 374 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,374 @@
1+
'use client';
2+
3+
import { useParams, useRouter } from 'next/navigation';
4+
import { useEffect, useState } from 'react';
5+
import {
6+
Box,
7+
Button,
8+
CircularProgress,
9+
Container,
10+
Typography,
11+
} from '@mui/material';
12+
import Header from '@learner/components/Header/Header';
13+
import { getTenantInfo } from '@learner/utils/API/ProgramService';
14+
import { useTranslation } from '@shared-lib';
15+
import EnrolModal from '@learner/components/EnrolModal/EnrolModal';
16+
import LanguageSelectionPopup from '@learner/components/LanguageSelectionPopup/LanguageSelectionPopup';
17+
import DOMPurify from 'dompurify';
18+
19+
interface Program {
20+
tenantId: string;
21+
name: string;
22+
description?: string;
23+
type?: string;
24+
params?: {
25+
uiConfig?: { [key: string]: any };
26+
[key: string]: any;
27+
} | null;
28+
programImages?: (
29+
| string
30+
| { label?: string; description?: string; [key: string]: any }
31+
)[];
32+
}
33+
34+
function getFirstImage(program: Program): string {
35+
console.log('Program images:', program?.params?.uiConfig);
36+
const item = program?.params?.uiConfig?.landingImage|| program.programImages?.[0];
37+
if (!item) return '/images/default.png';
38+
return typeof item === 'string'
39+
? item
40+
: (item as any).description || '/images/default.png';
41+
}
42+
43+
export default function ProgramDetailPage() {
44+
const params = useParams();
45+
const router = useRouter();
46+
const { t } = useTranslation();
47+
const programName = decodeURIComponent(params?.programName as string);
48+
49+
const [program, setProgram] = useState<Program | null>(null);
50+
const [loading, setLoading] = useState(true);
51+
const [notFound, setNotFound] = useState(false);
52+
const [enrolModalOpen, setEnrolModalOpen] = useState(false);
53+
54+
useEffect(() => {
55+
if (!programName) return;
56+
const fetchProgram = async () => {
57+
try {
58+
const res = await getTenantInfo();
59+
const all: Program[] = res?.result || [];
60+
const found = all.find(
61+
(p) => p.name.toLowerCase() === programName.toLowerCase()
62+
);
63+
if (found) {
64+
setProgram(found);
65+
} else {
66+
setNotFound(true);
67+
}
68+
} catch {
69+
setNotFound(true);
70+
} finally {
71+
setLoading(false);
72+
}
73+
};
74+
fetchProgram();
75+
}, [programName]);
76+
77+
const handleEnrolNow = () => {
78+
setEnrolModalOpen(true);
79+
};
80+
81+
const handleLogin = () => {
82+
router.push(`/login?tenantId=${program?.tenantId}`);
83+
};
84+
85+
if (loading) {
86+
return (
87+
<>
88+
<Header isLogin />
89+
<Box
90+
sx={{
91+
display: 'flex',
92+
justifyContent: 'center',
93+
alignItems: 'center',
94+
minHeight: '60vh',
95+
}}
96+
>
97+
<CircularProgress sx={{ color: '#FDBE16' }} />
98+
</Box>
99+
</>
100+
);
101+
}
102+
103+
if (notFound || !program) {
104+
return (
105+
<>
106+
<Header isLogin />
107+
<Box
108+
sx={{
109+
display: 'flex',
110+
flexDirection: 'column',
111+
alignItems: 'center',
112+
justifyContent: 'center',
113+
minHeight: '60vh',
114+
gap: 2,
115+
}}
116+
>
117+
<Typography variant="h5" sx={{ fontFamily: 'Poppins', fontWeight: 600 }}>
118+
{t('LANDING.PROGRAM_NOT_FOUND') || 'Program not found'}
119+
</Typography>
120+
<Button
121+
variant="outlined"
122+
onClick={() => router.push('/landing')}
123+
sx={{ textTransform: 'none', borderColor: '#FDBE16', color: '#1F1B13' }}
124+
>
125+
{t('LANDING.GO_BACK') || 'Go Back'}
126+
</Button>
127+
</Box>
128+
</>
129+
);
130+
}
131+
132+
const heroImage = getFirstImage(program);
133+
134+
return (
135+
<>
136+
<LanguageSelectionPopup />
137+
<Header />
138+
139+
<Box sx={{ backgroundColor: '#fff', minHeight: '100vh' }}>
140+
{/* ── Hero Banner ── */}
141+
<Box sx={{ position: 'relative', width: '100%' }}>
142+
<Box
143+
component="img"
144+
src={heroImage}
145+
alt={program.name}
146+
onError={(e: React.SyntheticEvent<HTMLImageElement>) => {
147+
e.currentTarget.src = '/images/default.png';
148+
}}
149+
sx={{
150+
width: '100%',
151+
height: { xs: 260, md: 380 },
152+
objectFit: 'cover',
153+
display: 'block',
154+
}}
155+
/>
156+
157+
{/* Program name badge — bottom-left of hero */}
158+
<Box
159+
sx={{
160+
position: 'absolute',
161+
bottom: 0,
162+
left: 0,
163+
backgroundColor: '#FDBE16',
164+
px: 3,
165+
py: 1.2,
166+
display: 'flex',
167+
alignItems: 'center',
168+
}}
169+
>
170+
<Typography
171+
sx={{
172+
fontFamily: 'Poppins',
173+
fontWeight: 700,
174+
fontSize: { xs: '13px', md: '16px' },
175+
letterSpacing: '0.08em',
176+
color: '#1F1B13',
177+
textTransform: 'uppercase',
178+
}}
179+
>
180+
{program.name}
181+
</Typography>
182+
</Box>
183+
</Box>
184+
185+
{/* ── Body ── */}
186+
<Box sx={{ backgroundColor: '#FFFDF7', py: { xs: 4, md: 6 } }}>
187+
<Container maxWidth="lg" sx={{ px: { xs: 2, md: 3 } }}>
188+
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 5 }}>
189+
190+
{/* Enrol card — centred, fixed width */}
191+
<Box
192+
sx={{
193+
mx: 'auto',
194+
width: '100%',
195+
maxWidth: 480,
196+
border: '1px solid #E0E0E0',
197+
borderRadius: '12px',
198+
p: { xs: 3, md: 4 },
199+
display: 'flex',
200+
flexDirection: 'column',
201+
gap: 2,
202+
backgroundColor: '#fff',
203+
boxShadow: '0px 4px 16px rgba(0,0,0,0.08)',
204+
}}
205+
>
206+
<Typography
207+
sx={{
208+
fontFamily: 'Poppins',
209+
fontWeight: 700,
210+
fontSize: { xs: '16px', md: '18px' },
211+
color: '#1F1B13',
212+
textAlign: 'center',
213+
lineHeight: 1.4,
214+
}}
215+
>
216+
{`${t('LANDING.READY_TO_JOIN') || 'Ready to join'} ${program.name}?`}
217+
</Typography>
218+
219+
<Button
220+
fullWidth
221+
variant="contained"
222+
onClick={handleEnrolNow}
223+
disableElevation
224+
sx={{
225+
backgroundColor: '#FDBE16',
226+
color: '#1F1B13',
227+
fontFamily: 'Poppins',
228+
fontWeight: 700,
229+
fontSize: '15px',
230+
textTransform: 'none',
231+
borderRadius: '8px',
232+
py: 1.4,
233+
'&:hover': { backgroundColor: '#f0b000' },
234+
}}
235+
>
236+
{t('LANDING.ENROL_NOW') || 'Enrol Now'}
237+
</Button>
238+
239+
<Button
240+
fullWidth
241+
variant="outlined"
242+
onClick={handleLogin}
243+
disableElevation
244+
sx={{
245+
borderColor: '#D0D0D0',
246+
color: '#1F1B13',
247+
fontFamily: 'Poppins',
248+
fontWeight: 500,
249+
fontSize: '14px',
250+
textTransform: 'none',
251+
borderRadius: '8px',
252+
py: 1.2,
253+
'&:hover': { borderColor: '#FDBE16', backgroundColor: 'transparent' },
254+
}}
255+
>
256+
{t('LANDING.ALREADY_ENROLLED_LOGIN') || 'Already enrolled? Log in'}
257+
</Button>
258+
259+
<Typography
260+
variant="caption"
261+
sx={{
262+
textAlign: 'center',
263+
color: '#888',
264+
fontFamily: 'Poppins',
265+
lineHeight: 1.5,
266+
fontSize: '12px',
267+
}}
268+
>
269+
{t('LANDING.ENROL_HINT') ||
270+
"New or already enrolled — just enter your phone number. We'll guide you through."}
271+
</Typography>
272+
</Box>
273+
274+
{/* About the Program */}
275+
<Box>
276+
<Box
277+
sx={{
278+
display: 'inline-flex',
279+
backgroundColor: '#FDBE16',
280+
px: 2.5,
281+
py: 0.8,
282+
mb: 3,
283+
}}
284+
>
285+
<Typography
286+
sx={{
287+
fontFamily: 'Poppins',
288+
fontWeight: 700,
289+
fontSize: '13px',
290+
letterSpacing: '0.08em',
291+
color: '#1F1B13',
292+
textTransform: 'uppercase',
293+
}}
294+
>
295+
{t('LANDING.ABOUT_THE_PROGRAM') || 'About the Program'}
296+
</Typography>
297+
</Box>
298+
299+
<Typography
300+
component="div"
301+
variant="body1"
302+
sx={{
303+
fontFamily: 'Poppins',
304+
fontSize: { xs: '14px', md: '15px' },
305+
lineHeight: 1.9,
306+
//color: '#3D6B5E',
307+
'& ul': { paddingLeft: '20px', margin: '4px 0' },
308+
'& li': { marginBottom: '2px' },
309+
}}
310+
dangerouslySetInnerHTML={{
311+
__html: DOMPurify.sanitize((t(program?.params?.uiConfig?.aboutText) || '').replace(/\n/g, '<br/>'))
312+
}}
313+
/>
314+
</Box>
315+
316+
{/* Contact */}
317+
<Box>
318+
<Box
319+
sx={{
320+
display: 'inline-flex',
321+
backgroundColor: '#FDBE16',
322+
px: 2.5,
323+
py: 0.8,
324+
mb: 3,
325+
}}
326+
>
327+
<Typography
328+
sx={{
329+
fontFamily: 'Poppins',
330+
fontWeight: 700,
331+
fontSize: '13px',
332+
letterSpacing: '0.08em',
333+
color: '#1F1B13',
334+
textTransform: 'uppercase',
335+
}}
336+
>
337+
{t('LANDING.CONTACT') || 'Contact'}
338+
</Typography>
339+
</Box>
340+
341+
<Typography
342+
variant="body1"
343+
sx={{
344+
fontFamily: 'Poppins',
345+
fontSize: { xs: '14px', md: '15px' },
346+
color: '#3D6B5E',
347+
lineHeight: 1.9,
348+
}}
349+
>
350+
{t('LANDING.WRITE_TO_US') || 'Write to us at'}{' '}
351+
<Box
352+
component="a"
353+
href={`mailto:${program.params?.uiConfig?.contactEmail || program.params?.uiConfig?.contact || 'info@pratham.org'}`}
354+
sx={{ color: '#0D599E', textDecoration: 'underline' }}
355+
>
356+
{program.params?.uiConfig?.contactEmail || program.params?.uiConfig?.contact || 'info@pratham.org'}
357+
</Box>
358+
</Typography>
359+
</Box>
360+
361+
</Box>
362+
</Container>
363+
</Box>
364+
</Box>
365+
366+
<EnrolModal
367+
open={enrolModalOpen}
368+
onClose={() => setEnrolModalOpen(false)}
369+
programName={program.name}
370+
tenantId={program.tenantId}
371+
/>
372+
</>
373+
);
374+
}

0 commit comments

Comments
 (0)