Skip to content

Commit 4cd15b9

Browse files
authored
Merge pull request #2630 from tekdi/release-prod-fix
Release prod fix to admin qa
2 parents a04a15d + 4daec2f commit 4cd15b9

File tree

9 files changed

+441
-59
lines changed

9 files changed

+441
-59
lines changed

apps/learner-web-app/src/components/Content/Player.tsx

Lines changed: 37 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ const App = ({
5757
const [breadCrumbs, setBreadCrumbs] = useState<any>();
5858
const [isShowMoreContent, setIsShowMoreContent] = useState(false);
5959
const [mimeType, setMemetype] = useState('');
60+
const [isVideo, setIsVideo] = useState(false);
6061
const [isDownloading, setIsDownloading] = useState(false);
6162
const [showSuccessMessage, setShowSuccessMessage] = useState(false);
6263
const [showJotFormModal, setShowJotFormModal] = useState(false);
@@ -66,6 +67,31 @@ const App = ({
6667
mimeType: string;
6768
} | null>(null);
6869

70+
const [isPortrait, setIsPortrait] = useState(false);
71+
72+
useEffect(() => {
73+
setIsVideo(mimeType=='video/mp4' || mimeType=='video/webm');
74+
}, [mimeType]);
75+
76+
// Check if device is in portrait mode (width < height)
77+
useEffect(() => {
78+
const checkOrientation = () => {
79+
setIsPortrait(window.innerWidth < window.innerHeight);
80+
};
81+
82+
// Check on mount
83+
checkOrientation();
84+
85+
// Listen for resize and orientation changes
86+
window.addEventListener('resize', checkOrientation);
87+
window.addEventListener('orientationchange', checkOrientation);
88+
89+
return () => {
90+
window.removeEventListener('resize', checkOrientation);
91+
window.removeEventListener('orientationchange', checkOrientation);
92+
};
93+
}, []);
94+
6995
let activeLink = null;
7096
if (typeof window !== 'undefined') {
7197
const searchParams = new URLSearchParams(window.location.search);
@@ -403,9 +429,13 @@ const App = ({
403429
unitId={unitId}
404430
mimeType={mimeType}
405431
{..._config?.player}
432+
isPortrait={isPortrait}
433+
isVideo={isVideo}
406434
/>
407435
{item?.content?.artifactUrl &&
436+
408437
isDownloadableMimeType(item?.content?.mimeType || mimeType) &&
438+
(!isPortrait || (isVideo && !isPortrait)) &&
409439
isDownloadContentEnabled() && (
410440
<Box
411441
sx={{
@@ -457,7 +487,8 @@ const App = ({
457487

458488
<Grid
459489
sx={{
460-
display: isShowMoreContent ? 'flex' : 'none',
490+
display: isShowMoreContent && (!isPortrait || (isVideo && !isPortrait)) ? 'flex' : 'none',
491+
461492
flexDirection: 'column',
462493
flex: { xs: 1, sm: 1, md: 9 },
463494
}}
@@ -615,13 +646,14 @@ const PlayerBox = ({
615646
trackable,
616647
isShowMoreContent,
617648
mimeType,
649+
isPortrait,
650+
isVideo
618651
}: any) => {
619652
const router = useRouter();
620653
const { t } = useTranslation();
621654
const theme = useTheme();
622655
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
623656
const [play, setPlay] = useState(false);
624-
const [isPortrait, setIsPortrait] = useState(false);
625657

626658
// Determine aspectRatio based on mimeType and mobile mode
627659
const getAspectRatio = () => {
@@ -631,25 +663,6 @@ const PlayerBox = ({
631663
return '16/9';
632664
};
633665

634-
// Check if device is in portrait mode (width < height)
635-
useEffect(() => {
636-
const checkOrientation = () => {
637-
setIsPortrait(window.innerWidth < window.innerHeight);
638-
};
639-
640-
// Check on mount
641-
checkOrientation();
642-
643-
// Listen for resize and orientation changes
644-
window.addEventListener('resize', checkOrientation);
645-
window.addEventListener('orientationchange', checkOrientation);
646-
647-
return () => {
648-
window.removeEventListener('resize', checkOrientation);
649-
window.removeEventListener('orientationchange', checkOrientation);
650-
};
651-
}, []);
652-
653666
useEffect(() => {
654667
if (checkAuth() || userIdLocalstorageName) {
655668
setPlay(true);
@@ -716,9 +729,7 @@ const PlayerBox = ({
716729
width: isShowMoreContent
717730
? '100%'
718731
: { xs: '100%', sm: '100%', md: '90%', lg: '80%', xl: '70%' },
719-
p:0,
720-
ml:-10,
721-
mr:-5,
732+
...(isPortrait && isVideo ? { p:0, ml:-10, mr:-5 } : {}),
722733
}}
723734
>
724735
<iframe
@@ -741,8 +752,8 @@ const PlayerBox = ({
741752
aspectRatio: getAspectRatio(),
742753
}}
743754
allowFullScreen
744-
width={"110%"}
745-
height={isPortrait ? '300%' : '100%'}
755+
width={isPortrait && isVideo ? "110%" : "100%"}
756+
height={isPortrait && isVideo ? '300%' : '100%'}
746757
title="Embedded Localhost"
747758
allow="accelerometer; gyroscope; autoplay; encrypted-media; picture-in-picture"
748759
frameBorder="0"

apps/learner-web-app/src/components/EditProfile/EditProfile.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ const EditProfile = ({ completeProfile, enrolledProgram, uponEnrollCompletion }:
143143
delete responseFormForEnroll?.schema?.properties?.district;
144144
delete responseFormForEnroll?.schema?.properties?.block;
145145
delete responseFormForEnroll?.schema?.properties?.village;
146+
delete responseFormForEnroll?.schema?.properties?.consent_file;
146147
responseFormForEnroll?.schema?.required?.pop('batch');
147148

148149
const responseFormCopy = JSON.parse(JSON.stringify(responseForm));

apps/learner-web-app/src/components/UserProfileCard/UserProfileCard.tsx

Lines changed: 127 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,24 @@ import {
99
Menu,
1010
MenuItem,
1111
IconButton,
12+
Dialog,
13+
DialogTitle,
14+
DialogContent,
15+
DialogActions,
16+
Button,
1217
} from '@mui/material';
1318
import { useEffect, useState } from 'react';
1419
import settingImage from '../../../public/images/settings.png';
1520
import Image from 'next/image';
1621
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
22+
import VisibilityIcon from '@mui/icons-material/Visibility';
1723
import { useRouter } from 'next/navigation';
1824
import { getUserDetails } from '@learner/utils/API/userService';
1925
import { Loader, useTranslation } from '@shared-lib';
2026
import { isUnderEighteen, toPascalCase } from '@learner/utils/helper';
2127
import { fetchForm } from '@shared-lib-v2/DynamicForm/components/DynamicFormCallback';
2228
import { FormContext } from '@shared-lib-v2/DynamicForm/components/DynamicFormConstant';
29+
import DocumentViewer from '@shared-lib-v2/DynamicForm/components/DocumentViewer/DocumentViewer';
2330

2431
// Helper function to get field value from userData based on schema
2532
const getFieldValue = (fieldName: string, fieldSchema: Record<string, unknown>, userData: Record<string, unknown>, customFields: Array<Record<string, unknown>> = []) => {
@@ -104,6 +111,9 @@ const UserProfileCard = ({ maxWidth = '600px' }) => {
104111
const [formSchema, setFormSchema] = useState<Record<string, unknown> | null>(null); // Form schema state
105112
const { t } = useTranslation();
106113
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
114+
const [previewUrl, setPreviewUrl] = useState<string | null>(null);
115+
const [isPreviewOpen, setIsPreviewOpen] = useState(false);
116+
const [previewTitle, setPreviewTitle] = useState<string>('');
107117

108118
const storedConfig =
109119
typeof window !== 'undefined'
@@ -184,6 +194,39 @@ const UserProfileCard = ({ maxWidth = '600px' }) => {
184194
setAnchorEl(null);
185195
};
186196

197+
const handlePreview = (url: string, title: string) => {
198+
setPreviewUrl(url);
199+
setPreviewTitle(title);
200+
setIsPreviewOpen(true);
201+
};
202+
203+
const handleClosePreview = () => {
204+
setIsPreviewOpen(false);
205+
setPreviewUrl(null);
206+
setPreviewTitle('');
207+
};
208+
209+
// Helper function to check if a field is a file field
210+
const isFileField = (fieldSchema: Record<string, unknown>): boolean => {
211+
return (
212+
fieldSchema?.field_type === 'file_upload' ||
213+
fieldSchema?.field_type === 'file' ||
214+
fieldSchema?.type === 'array' && (fieldSchema?.items as Record<string, unknown>)?.type === 'string' &&
215+
((fieldSchema?.items as Record<string, unknown>)?.format === 'data-url' || (fieldSchema?.items as Record<string, unknown>)?.format === 'uri')
216+
);
217+
};
218+
219+
// Helper function to check if a value is a valid URL
220+
const isValidUrl = (value: unknown): boolean => {
221+
if (typeof value !== 'string') return false;
222+
try {
223+
const url = new URL(value);
224+
return url.protocol === 'http:' || url.protocol === 'https:';
225+
} catch {
226+
return false;
227+
}
228+
};
229+
187230
const handleOpen = (option: string) => {
188231
console.log(option);
189232
if (option === t('LEARNER_APP.USER_PROFILE_CARD.EDIT_PROFILE')) {
@@ -564,13 +607,46 @@ const UserProfileCard = ({ maxWidth = '600px' }) => {
564607
{otherSectionFields.map((field) => {
565608
const fieldTitle = (field.schema.title as string) || field.name;
566609
const labelKey = `FORM.${fieldTitle}`;
610+
const isFile = isFileField(field.schema);
611+
const hasValidUrl = isValidUrl(field.rawValue);
567612

568613
return (
569614
<Grid item xs={6} key={field.name}>
570615
<Typography sx={labelStyle}>
571616
{t(labelKey, { defaultValue: toPascalCase(String(fieldTitle).replace(/_/g, ' ')) })}
572617
</Typography>
573-
<Typography sx={valueStyle}>{t(`FORM.${String(field.value).toUpperCase()}`, { defaultValue: toPascalCase(String(field.value)) })}</Typography>
618+
{isFile && hasValidUrl ? (
619+
<Box
620+
sx={{
621+
display: 'flex',
622+
alignItems: 'center',
623+
gap: 0.5,
624+
cursor: 'pointer',
625+
'&:hover': {
626+
'& .view-text': {
627+
textDecoration: 'underline',
628+
}
629+
}
630+
}}
631+
onClick={() => handlePreview(String(field.rawValue), fieldTitle)}
632+
>
633+
<VisibilityIcon sx={{ fontSize: '1rem', color: 'primary.main' }} />
634+
<Typography
635+
className="view-text"
636+
sx={{
637+
...valueStyle,
638+
color: 'primary.main',
639+
fontWeight: 500
640+
}}
641+
>
642+
{t('VIEW_FILE', { defaultValue: 'View File' })}
643+
</Typography>
644+
</Box>
645+
) : (
646+
<Typography sx={valueStyle}>
647+
{t(`FORM.${String(field.value).toUpperCase()}`, { defaultValue: toPascalCase(String(field.value)) })}
648+
</Typography>
649+
)}
574650
</Grid>
575651
);
576652
})}
@@ -615,6 +691,56 @@ const UserProfileCard = ({ maxWidth = '600px' }) => {
615691
>
616692
Open PDF
617693
</a> */}
694+
695+
{/* File Preview Dialog */}
696+
<Dialog
697+
open={isPreviewOpen}
698+
onClose={handleClosePreview}
699+
maxWidth="lg"
700+
fullWidth
701+
PaperProps={{
702+
sx: {
703+
height: '90vh',
704+
maxHeight: '90vh',
705+
},
706+
}}
707+
>
708+
<DialogTitle>
709+
{t('FILE_PREVIEW', { defaultValue: 'File Preview' })} - {t(`FORM.${previewTitle}`, { defaultValue: toPascalCase(String(previewTitle).replace(/_/g, ' ')) })}
710+
</DialogTitle>
711+
<DialogContent
712+
sx={{
713+
p: 0,
714+
display: 'flex',
715+
flexDirection: 'column',
716+
overflow: 'hidden',
717+
}}
718+
>
719+
{previewUrl && (
720+
<DocumentViewer
721+
url={previewUrl}
722+
width="100%"
723+
height="100%"
724+
showError={true}
725+
showDownloadButton={false}
726+
/>
727+
)}
728+
</DialogContent>
729+
<DialogActions>
730+
<Button onClick={handleClosePreview}>{t('COMMON.CLOSE', { defaultValue: 'Close' })}</Button>
731+
{/* {previewUrl && (
732+
<Button
733+
variant="contained"
734+
href={previewUrl}
735+
download
736+
target="_blank"
737+
rel="noopener noreferrer"
738+
>
739+
{t('COMMON.DOWNLOAD', { defaultValue: 'Download' })}
740+
</Button> */}
741+
742+
</DialogActions>
743+
</Dialog>
618744
</Box>
619745
);
620746
};

apps/teachers/public/locales/en/common.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@
5252
"CLOSE": "Close",
5353
"CANCEL": "Cancel",
5454
"OK": "OK",
55+
"VIEW_FILE": "View File",
56+
"FILE_PREVIEW": "File Preview",
57+
"DOWNLOAD": "Download",
5558
"FROM_TO_DATE": "From - To dates",
5659
"REMOVE": "Remove",
5760
"SEND_REQUEST": "Send Request",

libs/shared-lib-v2/src/DynamicForm/components/DocumentViewer/DocumentViewer.tsx

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,15 @@ interface DocumentViewerProps {
3131
width?: string | number;
3232
height?: string | number;
3333
showError?: boolean;
34+
showDownloadButton?: boolean;
3435
}
3536

3637
const DocumentViewer: React.FC<DocumentViewerProps> = ({
3738
url,
3839
width = '100%',
3940
height = '600px',
4041
showError = true,
42+
showDownloadButton = true,
4143
}) => {
4244
const [loading, setLoading] = useState(true);
4345
const [error, setError] = useState<string | null>(null);
@@ -626,16 +628,18 @@ const DocumentViewer: React.FC<DocumentViewerProps> = ({
626628
</>
627629
)}
628630
</Box>
629-
<IconButton
630-
size="small"
631-
href={url}
632-
download
633-
target="_blank"
634-
rel="noopener noreferrer"
635-
title="Download"
636-
>
637-
<Download />
638-
</IconButton>
631+
{showDownloadButton && (
632+
<IconButton
633+
size="small"
634+
href={url}
635+
download
636+
target="_blank"
637+
rel="noopener noreferrer"
638+
title="Download"
639+
>
640+
<Download />
641+
</IconButton>
642+
)}
639643
</Box>
640644
)}
641645

0 commit comments

Comments
 (0)