funcionts
funcionts
if (
(!uploadedImageUrlHeroDesktop && !uploadedImageUrlHeroMobile) ||
(!uploadedImageUrlDetailsDesktop && !uploadedImageUrlDetailsMobile)
) {
setToastMessage(t('uploadOneImage'));
return;
}
type PrizeWithNewProps = {
enabled: boolean;
hasMultiplier: boolean;
hasRaffle: boolean;
hasFreeBet: boolean;
} & Omit<OriginalEditLotterySchemaParams['prizes'][number], 'risk' |
'taxAndCashDiscount' | 'selfInsuredCost'>;
delete prize.risk;
delete prize.taxAndCashDiscount;
delete prize.selfInsuredCost;
if (prize.insuranceProviderCosts) {
prize.insuranceProviderCosts =
prize.insuranceProviderCosts.map((cost) => {
const {totalCost, ...rest} = cost;
return rest;
});
}
return prize;
});
input.countryPricingThresholds.forEach((threshold) => {
threshold.pricingThresholds.reverse();
threshold.pricingThresholds.forEach((pricing) => {
if (pricing.to.amount === null) pricing.to = null;
});
});
input.purchaseStrategiesThresholds.forEach((strategy) => {
if (strategy?.to?.amount === null) strategy.to = null;
});
const newData = {
...input,
metadata: {
preselectedLines: input.preselectedLines,
},
};
newData.logoUrl = logo;
delete newData.preselectedLines;
return newData;
}
}
if (currentTheme) {
const err = new Error('Lottery is already exist');
setToastMessage(t('lotteryExist'));
throw err;
}
return (
<FormProvider {...methods}>
<FusePageCarded
header={<LotteryThemesEditHeader />}
content={
<Box className="p-16 sm:p-24 max-w-lg">
<Select
name="lotteryId"
value={lotteryId}
labelId="lottery-select-label"
disabled
displayEmpty
sx={{width: '400px', mb: '30px'}}
>
<MenuItem id="lottery-select-label" value="">
{t('lottery')}
</MenuItem>
{renderLotteries}
</Select>
<RadioGroup
name="deviceType"
value={deviceType}
onChange={(e) => setDeviceType(e.target.value as
'desktop' | 'mobile')}
sx={{flexDirection: 'row', m: '0px'}}
>
<FormControlLabel
value="desktop"
control={<Radio />}
label={t('desktop')}
labelPlacement="start"
/>
<FormControlLabel
value="mobile"
control={<Radio />}
label={t('mobile')}
labelPlacement="start"
/>
</RadioGroup>
setUploadedImageUrlHeroDesktop(uploadedImageUrl);
} else {
setUploadedImageUrlHeroMobile(uploadedImageUrl);
}
}
}}
/>
</Button>
)}
/>
</Box>
<Box sx={{mt: '30px'}}>
<Typography className="text-12" color="primary">
{t('details')}
</Typography>
<Controller
name="imageDetails"
control={control}
render={() => (
<Button
variant="outlined"
component="label"
sx={{
mt: 2,
borderRadius: '5px',
border: 'none',
backgroundColor: '#f5f5f5',
pt: (
deviceType === 'desktop'
?
uploadedImageUrlDetailsDesktop
:
uploadedImageUrlDetailsMobile
)
? '90px'
: '30px',
pb: (
deviceType === 'desktop'
?
uploadedImageUrlDetailsDesktop
:
uploadedImageUrlDetailsMobile
)
? '90px'
: '30px',
width: '300px',
height: '150px',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
overflow: 'hidden',
...(deviceType === 'desktop' &&
uploadedImageUrlDetailsDesktop
&& {
width: '100%',
height: '100%',
}),
...(deviceType === 'mobile' &&
uploadedImageUrlDetailsMobile
&& {
width: '100%',
height: '100%',
}),
}}
>
{(
deviceType === 'desktop'
?
uploadedImageUrlDetailsDesktop
: uploadedImageUrlDetailsMobile
) ? (
<img
src={
deviceType === 'desktop'
?
uploadedImageUrlDetailsDesktop.url
:
uploadedImageUrlDetailsMobile.url
}
alt="Uploaded preview"
style={{
maxWidth: '100%',
maxHeight: '100%',
objectFit: 'contain',
borderRadius: '8px',
minWidth: '150px',
minHeight: '150px',
}}
/>
) : (
t('uploadPicture')
)}
<input
accept="image/*"
className="hidden"
id="button-file"
type="file"
onChange={async (e) => {
const file = e.target.files?.
[0];
if (file) {
const uploadedImageUrl =
await readFileAsync(file);
if (deviceType ===
'desktop') {
setUploadedImageUrlDetailsDesktop(uploadedImageUrl);
} else {
setUploadedImageUrlDetailsMobile(uploadedImageUrl);
}
}
}}
/>
</Button>
)}
/>
</Box>
</Box>
<Button
className="mt-40"
variant="contained"
color="primary"
onClick={handleUpdateTheme}
disabled={isLoading}
sx={{borderRadius: '5px'}}
>
{isLoading ? <CircularProgress size={24}
color="inherit" /> : t('save')}
</Button>
</Box>
}
/>
<Snackbar
anchorOrigin={{vertical: 'bottom', horizontal: 'center'}}
open={!!toastMessage}
autoHideDuration={3000}
onClose={() => setToastMessage(null)}
message={toastMessage}
/>
</FormProvider>
);
}
import FuseLoading from '@fuse/core/FuseLoading';
import FusePageCarded from '@fuse/core/FusePageCarded';
import {useForm, FormProvider, Controller} from 'react-hook-form';
import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query';
import {useTranslation} from 'react-i18next';
import {
Snackbar,
Box,
RadioGroup,
FormControlLabel,
Radio,
Button,
Select,
MenuItem,
Typography,
CircularProgress,
} from '@mui/material';
import {useState, useMemo, useEffect} from 'react';
import {zodResolver} from '@hookform/resolvers/zod';
import {lotteryThemeSchema, TUpdateConfigurationForm} from './lottery-theme-edit-
schema';
import {fetchLotteries} from '@lottob/graphql/queries/lotteries-id';
import {updateTheme} from '@lottob/graphql/mutations/update-lottery-theme';
import {UpdateConfigurationDto} from '@lottob/gql/graphql';
import {LotteryThemesEditHeader} from './components/LotteryThemesEditHeader';
import FuseUtils from '@fuse/utils';
import {fetchLotteryThemeByName} from '@lottob/graphql/queries/lottery-value-theme-
by-name';
import {ILotteryThemeDatum} from '../list/LotteryThemesTable';
import {useNavigate, useParams} from 'react-router';
import {uploadImageBase64} from '@lottob/utils/upload-image';
import {readFileAsync, TUploadedImage} from '@lottob/utils/convertFileToBase64';
type TErrorResponse = {
response?: {
errors: {message: string}[];
};
};
interface IThemeDeviceConfig {
heroBanner: {
src: string | null;
title: string;
};
detailsImage: {
src: string | null;
title: string;
};
}
interface ITheme {
lotteryId: string;
desktop?: IThemeDeviceConfig;
mobile?: IThemeDeviceConfig;
}
useEffect(() => {
const theme = lotteryThemes?.find((t) => t.lotteryId === lotteryId);
if (theme) {
setCurrentTheme({
...theme,
desktop: {
heroBanner: {
src: theme.desktop?.heroBanner?.src || null,
title: theme.desktop?.heroBanner?.title || 'filename',
},
detailsImage: {
src: theme.desktop?.detailsImage?.src || null,
title: theme.desktop?.detailsImage?.title || 'filename',
},
},
mobile: {
heroBanner: {
src: theme.mobile?.heroBanner?.src || null,
title: theme.mobile?.heroBanner?.title || 'filename',
},
detailsImage: {
src: theme.mobile?.detailsImage?.src || null,
title: theme.mobile?.detailsImage?.title || 'filename',
},
},
});
try {
// Only upload images that are available
const imageHeroDesktop = uploadedImageUrlHeroDesktop
? await uploadImageBase64(uploadedImageUrlHeroDesktop.base64,
uploadedImageUrlHeroDesktop.fileName)
: null;
const updatedMobile = {
heroBanner: {
src: imageHeroMobile || currentTheme.mobile?.heroBanner?.src ||
'',
title: '',
},
detailsImage: {
src: imageDetailsMobile ||
currentTheme.mobile?.detailsImage?.src || '',
title: '',
},
};
//lotteriesThemesList[themeIndex] = currentTheme;
// Update the theme with new images
currentTheme.desktop = updatedDesktop;
currentTheme.mobile = updatedMobile;
useEffect(() => {
if (isSaved) {
setTimeout(() => {
navigate('/lottery-theme');
}, 500);
}
}, [isSaved]);
if (loadingLotteries) {
return <FuseLoading />;
}
return (
<FormProvider {...methods}>
<FusePageCarded
header={<LotteryThemesEditHeader />}
content={
<Box className="p-16 sm:p-24 max-w-lg">
<Select
name="lotteryId"
value={lotteryId}
labelId="lottery-select-label"
disabled
displayEmpty
sx={{width: '400px', mb: '30px'}}
>
<MenuItem id="lottery-select-label" value="">
{t('lottery')}
</MenuItem>
{renderLotteries}
</Select>
<RadioGroup
name="deviceType"
value={deviceType}
onChange={(e) => setDeviceType(e.target.value as
'desktop' | 'mobile')}
sx={{flexDirection: 'row', m: '0px'}}
>
<FormControlLabel
value="desktop"
control={<Radio />}
label={t('desktop')}
labelPlacement="start"
/>
<FormControlLabel
value="mobile"
control={<Radio />}
label={t('mobile')}
labelPlacement="start"
/>
</RadioGroup>
src={uploadedImageUrlHeroDesktop.url}
alt="Uploaded preview"
style={{
maxWidth: '100%',
maxHeight: '100%',
objectFit: 'contain',
borderRadius: '8px',
minWidth: '150px',
minHeight: '150px',
}}
/>
) : (
t('uploadPicture')
)
) : uploadedImageUrlHeroMobile ? (
<img
src={uploadedImageUrlHeroMobile.url}
alt="Uploaded preview"
style={{
maxWidth: '100%',
maxHeight: '100%',
objectFit: 'contain',
borderRadius: '8px',
minWidth: '150px',
minHeight: '150px',
}}
/>
) : (
t('uploadPicture')
)}
<input
accept="image/*"
className="hidden"
id="button-file"
type="file"
onChange={async (e) => {
const file = e?.target?.files?.
[0];
if (!file) return;
setUploadedImageUrlHeroDesktop(uploadedImageUrl);
} else {
setUploadedImageUrlHeroMobile(uploadedImageUrl);
}
}}
/>
</Button>
)}
/>
</Box>
<Box sx={{mt: '30px'}}>
<Typography className="text-12" color="primary">
{t('details')}
</Typography>
<Controller
name="imageDetails"
control={control}
render={() => (
<Button
variant="outlined"
component="label"
sx={{
mt: 2,
borderRadius: '5px',
border: 'none',
backgroundColor: '#f5f5f5',
pt: (
deviceType === 'desktop'
?
uploadedImageUrlDetailsDesktop
:
uploadedImageUrlDetailsMobile
)
? '90px'
: '30px',
pb: (
deviceType === 'desktop'
?
uploadedImageUrlDetailsDesktop
:
uploadedImageUrlDetailsMobile
)
? '90px'
: '30px',
width: '300px',
height: '150px',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
overflow: 'hidden',
...(deviceType === 'desktop' &&
uploadedImageUrlDetailsDesktop
&& {
width: '100%',
height: '100%',
}),
...(deviceType === 'mobile' &&
uploadedImageUrlDetailsMobile
&& {
width: '100%',
height: '100%',
}),
}}
>
{deviceType === 'desktop' ? (
uploadedImageUrlDetailsDesktop ? (
<img
src={uploadedImageUrlDetailsDesktop.url}
alt="Uploaded preview"
style={{
maxWidth: '100%',
maxHeight: '100%',
objectFit: 'contain',
borderRadius: '8px',
minWidth: '150px',
minHeight: '150px',
}}
/>
) : (
t('uploadPicture')
)
) : uploadedImageUrlDetailsMobile ? (
<img
src={uploadedImageUrlDetailsMobile.url}
alt="Uploaded preview"
style={{
maxWidth: '100%',
maxHeight: '100%',
objectFit: 'contain',
borderRadius: '8px',
minWidth: '150px',
minHeight: '150px',
}}
/>
) : (
t('uploadPicture')
)}
<input
accept="image/*"
className="hidden"
id="button-file"
type="file"
onChange={async (e) => {
const file = e?.target?.files?.
[0];
if (!file) return;
setUploadedImageUrlDetailsDesktop(uploadedImageUrl);
} else {
setUploadedImageUrlDetailsMobile(uploadedImageUrl);
}
}}
/>
</Button>
)}
/>
</Box>
</Box>
<Button
className="mt-40"
variant="contained"
color="primary"
onClick={handleUpdateTheme}
disabled={isLoading}
sx={{borderRadius: '5px'}}
>
{isLoading ? <CircularProgress size={24}
color="inherit" /> : t('save')}
</Button>
</Box>
}
/>
<Snackbar
anchorOrigin={{vertical: 'bottom', horizontal: 'center'}}
open={!!toastMessage}
autoHideDuration={3000}
onClose={() => setToastMessage(null)}
message={toastMessage}
/>
</FormProvider>
);
}
CHATGPT
try {
// Continuar con el flujo normal
} catch (error) {
logger.error('Error saving theme:', error);
setToastMessage(t('somethingWrong'));
}
});
Y en handleCreateTheme:
try {
const configuration = await fetchTheme();
const rawConfigurationValue: string = configuration.configuration.value ||
'[]';
let lotteriesThemesList: ITheme[] = JSON.parse(rawConfigurationValue) ||
[];
if (doesThemeExist(lotteriesThemesList, lotteryId)) {
setToastMessage(t('lotteryExist'));
return;
}
Resultado Final
<Select
{...register("lotteryId", { required: t("lotterySelect") })}
error={!!errors.lotteryId}
helperText={errors.lotteryId?.message}
onChange={(e) => setLotteryId(e.target.value)}
displayEmpty
>
<MenuItem value="">{t("lottery")}</MenuItem>
{renderLotteries}
</Select>
Para RadioGroup:
try {
// Procesamiento de imágenes y creación de tema...
} catch (error) {
setToastMessage(t("somethingWrong"));
}
});
if (!lotteryId) {
setToastMessage(t("lotterySelect"));
return;
}
Si necesitas validaciones más avanzadas, puedes considerar Yup, pero con esta
solución puedes evitarlo. 🚀