mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	feat: pulsing avatar for import (#2961)
This commit is contained in:
		
							parent
							
								
									c41f888810
								
							
						
					
					
						commit
						bfb038c725
					
				| @ -1,4 +1,4 @@ | ||||
| import React, { FC, useCallback } from 'react'; | ||||
| import React, { FC, useCallback, useEffect } from 'react'; | ||||
| import { useDropzone } from 'react-dropzone'; | ||||
| import { Box } from '@mui/material'; | ||||
| 
 | ||||
| @ -7,10 +7,11 @@ const formatJSON = (json: string) => JSON.stringify(JSON.parse(json), null, 4); | ||||
| interface IFileDropZoneProps { | ||||
|     onSuccess: (message: string) => void; | ||||
|     onError: (error: string) => void; | ||||
|     onDragStatusChange: (dragOn: boolean) => void; | ||||
| } | ||||
| 
 | ||||
| const onFileDropped = | ||||
|     ({ onSuccess, onError }: IFileDropZoneProps) => | ||||
|     ({ onSuccess, onError }: Omit<IFileDropZoneProps, 'onDragStatusChange'>) => | ||||
|     (e: ProgressEvent<FileReader>) => { | ||||
|         const contents = e?.target?.result; | ||||
|         if (typeof contents === 'string') { | ||||
| @ -30,6 +31,7 @@ export const FileDropZone: FC<IFileDropZoneProps> = ({ | ||||
|     onSuccess, | ||||
|     onError, | ||||
|     children, | ||||
|     onDragStatusChange, | ||||
|     ...props | ||||
| }) => { | ||||
|     const onDrop = useCallback(([file]) => { | ||||
| @ -37,13 +39,16 @@ export const FileDropZone: FC<IFileDropZoneProps> = ({ | ||||
|         reader.onload = onFileDropped({ onSuccess, onError }); | ||||
|         reader.readAsText(file); | ||||
|     }, []); | ||||
|     const { getRootProps, getInputProps } = useDropzone({ | ||||
|     const { getRootProps, getInputProps, isDragActive } = useDropzone({ | ||||
|         onDrop, | ||||
|         accept: { | ||||
|             'application/json': ['.json'], | ||||
|         }, | ||||
|         maxFiles: 1, | ||||
|     }); | ||||
|     useEffect(() => { | ||||
|         onDragStatusChange(isDragActive); | ||||
|     }, [isDragActive]); | ||||
| 
 | ||||
|     return ( | ||||
|         <Box {...getRootProps()} {...props}> | ||||
|  | ||||
| @ -0,0 +1,47 @@ | ||||
| import React, { FC } from 'react'; | ||||
| import { Box, styled, Typography } from '@mui/material'; | ||||
| 
 | ||||
| const ImportExplanationContainer = styled(Box)(({ theme }) => ({ | ||||
|     backgroundColor: theme.palette.secondaryContainer, | ||||
|     borderRadius: theme.shape.borderRadiusLarge, | ||||
|     padding: theme.spacing(3), | ||||
| })); | ||||
| 
 | ||||
| const ImportExplanationHeader = styled(Typography)(({ theme }) => ({ | ||||
|     marginBottom: theme.spacing(0.5), | ||||
| })); | ||||
| 
 | ||||
| const ImportExplanationDescription = styled(Typography)(({ theme }) => ({ | ||||
|     marginBottom: theme.spacing(3), | ||||
|     color: theme.palette.text.secondary, | ||||
|     fontSize: theme.fontSizes.smallBody, | ||||
| })); | ||||
| 
 | ||||
| export const ImportExplanation: FC = () => ( | ||||
|     <ImportExplanationContainer> | ||||
|         <ImportExplanationHeader> | ||||
|             What is being imported? | ||||
|         </ImportExplanationHeader> | ||||
|         <ImportExplanationDescription> | ||||
|             Feature toggles will be imported with full configuration: | ||||
|             <ul> | ||||
|                 <li>strategies</li> | ||||
|                 <li>context fields</li> | ||||
|                 <li>variants</li> | ||||
|                 <li>tags</li> | ||||
|                 <li>feature toggle status</li> | ||||
|             </ul> | ||||
|         </ImportExplanationDescription> | ||||
|         <ImportExplanationHeader>Exceptions?</ImportExplanationHeader> | ||||
|         <ImportExplanationDescription> | ||||
|             If the feature toggle already exists in the new instance, it will be | ||||
|             overwritten | ||||
|         </ImportExplanationDescription> | ||||
|         <ImportExplanationHeader>What is not imported?</ImportExplanationHeader> | ||||
|         <ImportExplanationDescription sx={{ marginBottom: 0 }}> | ||||
|             If we detect segments or custom strategies in your imported file, we | ||||
|             will stop the import. You need to create them first in the new | ||||
|             instance and run the import again | ||||
|         </ImportExplanationDescription> | ||||
|     </ImportExplanationContainer> | ||||
| ); | ||||
| @ -6,7 +6,6 @@ import { | ||||
|     TextField, | ||||
|     Box, | ||||
|     Typography, | ||||
|     Avatar, | ||||
| } from '@mui/material'; | ||||
| import { SidebarModal } from 'component/common/SidebarModal/SidebarModal'; | ||||
| import { ArrowUpward } from '@mui/icons-material'; | ||||
| @ -16,11 +15,13 @@ import { StyledFileDropZone } from './StyledFileDropZone'; | ||||
| import { ConditionallyRender } from '../../../common/ConditionallyRender/ConditionallyRender'; | ||||
| import useToast from 'hooks/useToast'; | ||||
| import { ImportOptions } from './ImportOptions'; | ||||
| import { ImportExplanation } from './ImportExplanation'; | ||||
| import { PulsingAvatar } from './PulsingAvatar'; | ||||
| 
 | ||||
| const LayoutContainer = styled('div')(({ theme }) => ({ | ||||
|     backgroundColor: '#fff', | ||||
|     height: '100vh', | ||||
|     padding: theme.spacing(4, 8, 4, 8), | ||||
|     minHeight: '100vh', | ||||
|     padding: theme.spacing(3, 8, 3, 8), | ||||
|     display: 'flex', | ||||
|     flexDirection: 'column', | ||||
|     gap: theme.spacing(3), | ||||
| @ -42,7 +43,7 @@ const SelectFileMessage = styled(Typography)(({ theme }) => ({ | ||||
| })); | ||||
| 
 | ||||
| const MaxSizeMessage = styled(Typography)(({ theme }) => ({ | ||||
|     marginTop: theme.spacing(9), | ||||
|     marginTop: theme.spacing(4), | ||||
|     color: theme.palette.text.secondary, | ||||
| })); | ||||
| 
 | ||||
| @ -50,7 +51,7 @@ const ActionsContainer = styled(Box)(({ theme }) => ({ | ||||
|     width: '100%', | ||||
|     borderTop: `1px solid ${theme.palette.dividerAlternative}`, | ||||
|     marginTop: 'auto', | ||||
|     paddingTop: theme.spacing(4), | ||||
|     paddingTop: theme.spacing(3), | ||||
|     display: 'flex', | ||||
|     justifyContent: 'flex-end', | ||||
| })); | ||||
| @ -70,6 +71,7 @@ export const ImportModal = ({ open, setOpen, project }: IImportModalProps) => { | ||||
|     const [environment, setEnvironment] = useState(''); | ||||
|     const [importPayload, setImportPayload] = useState(''); | ||||
|     const [activeTab, setActiveTab] = useState<ImportMode>('file'); | ||||
|     const [dragActive, setDragActive] = useState(false); | ||||
| 
 | ||||
|     const onSubmit = async () => { | ||||
|         await createImport({ | ||||
| @ -132,11 +134,16 @@ export const ImportModal = ({ open, setOpen, project }: IImportModalProps) => { | ||||
|                                     title: error, | ||||
|                                 }); | ||||
|                             }} | ||||
|                             onDragStatusChange={setDragActive} | ||||
|                         > | ||||
|                             <Avatar sx={{ width: 80, height: 80 }}> | ||||
|                             <PulsingAvatar active={dragActive}> | ||||
|                                 <ArrowUpward fontSize="large" /> | ||||
|                             </Avatar> | ||||
|                             <DropMessage>Drop your file here</DropMessage> | ||||
|                             </PulsingAvatar> | ||||
|                             <DropMessage> | ||||
|                                 {dragActive | ||||
|                                     ? 'Drop your file to upload' | ||||
|                                     : 'Drop your file here'} | ||||
|                             </DropMessage> | ||||
|                             <SelectFileMessage> | ||||
|                                 or select a file from your device | ||||
|                             </SelectFileMessage> | ||||
| @ -155,11 +162,12 @@ export const ImportModal = ({ open, setOpen, project }: IImportModalProps) => { | ||||
|                             } | ||||
|                             value={importPayload} | ||||
|                             multiline | ||||
|                             minRows={17} | ||||
|                             maxRows={17} | ||||
|                             minRows={13} | ||||
|                             maxRows={13} | ||||
|                         /> | ||||
|                     } | ||||
|                 /> | ||||
|                 <ImportExplanation /> | ||||
|                 <ActionsContainer> | ||||
|                     <Button | ||||
|                         sx={{ position: 'static' }} | ||||
|  | ||||
| @ -0,0 +1,21 @@ | ||||
| import { alpha, Avatar, styled } from '@mui/material'; | ||||
| 
 | ||||
| export const PulsingAvatar = styled(Avatar, { | ||||
|     shouldForwardProp: prop => prop !== 'active', | ||||
| })<{ active: boolean }>(({ theme, active }) => ({ | ||||
|     width: '80px', | ||||
|     height: '80px', | ||||
|     transition: 'background-color 0.5s ease', | ||||
|     backgroundColor: active | ||||
|         ? theme.palette.primary.main | ||||
|         : theme.palette.tertiary.main, | ||||
|     '@keyframes pulse': { | ||||
|         '0%': { | ||||
|             boxShadow: `0 0 0 0px ${alpha(theme.palette.primary.main, 0.7)}`, | ||||
|         }, | ||||
|         '100%': { | ||||
|             boxShadow: `0 0 0 20px ${alpha(theme.palette.primary.main, 0.0)}`, | ||||
|         }, | ||||
|     }, | ||||
|     animation: active ? 'pulse 2s infinite' : '', | ||||
| })); | ||||
| @ -3,7 +3,7 @@ import { FileDropZone } from './FileDropZone'; | ||||
| import React from 'react'; | ||||
| 
 | ||||
| export const StyledFileDropZone = styled(FileDropZone)(({ theme }) => ({ | ||||
|     padding: theme.spacing(10, 2, 2, 2), | ||||
|     padding: theme.spacing(4, 2, 2, 2), | ||||
|     border: `1px dashed ${theme.palette.secondary.border}`, | ||||
|     borderRadius: theme.shape.borderRadiusLarge, | ||||
|     display: 'flex', | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user