Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add abity to remove uploaded brand logo #4451

Merged
merged 6 commits into from
Oct 17, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 109 additions & 35 deletions apps/web/src/pages/brand/tabs/BrandingForm.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { Flex, Grid, Group, Input, LoadingOverlay, Stack, useMantineTheme } from '@mantine/core';
import { Flex, Grid, Group, Input, LoadingOverlay, Stack, UnstyledButton, useMantineTheme } from '@mantine/core';
import { Dropzone } from '@mantine/dropzone';
import { useMutation } from '@tanstack/react-query';
import axios from 'axios';
import { useEffect } from 'react';
import { useEffect, useRef } from 'react';
import { Controller, useForm } from 'react-hook-form';
import styled from '@emotion/styled';
import { useOutletContext } from 'react-router-dom';
import { IOrganizationEntity } from '@novu/shared';

Expand All @@ -12,7 +13,7 @@ import { getSignedUrl } from '../../../api/storage';
import Card from '../../../components/layout/components/Card';
import { Button, ColorInput, colors, Select } from '../../../design-system';
import { inputStyles } from '../../../design-system/config/inputs.styles';
import { Upload } from '../../../design-system/icons';
import { Trash, Upload } from '../../../design-system/icons';
import { successMessage } from '../../../utils/notifications';

const mimeTypes = {
Expand Down Expand Up @@ -55,6 +56,11 @@ export function BrandingForm() {
}
}, [organization, setValue]);

function removeFile() {
setValue('file', '');
setValue('image', '');
}

async function handleUpload(files: File[]) {
const file = files[0];
if (!file) return;
Expand Down Expand Up @@ -82,10 +88,12 @@ export function BrandingForm() {
setValue('image', path);
}

const dropzoneRef = useRef<() => void>(null);

Copy link
Contributor Author

@michaldziuba03 michaldziuba03 Oct 6, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I created ref for Dropzone, because one of the buttons must trigger openning file explorer

async function saveBrandsForm({ color, fontFamily, image }) {
const brandData = {
color,
logo: image,
logo: image || null,
fontFamily,
Copy link
Contributor Author

@michaldziuba03 michaldziuba03 Oct 6, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"removed" image is an empty string, but backend has validation rule for strings to check if it's URL. This always fails.

logo is optional, so null will pass that validation rule and successfully remove the uploaded logo.

};

Expand All @@ -110,38 +118,62 @@ export function BrandingForm() {
label="Your Logo"
description="Will be used on email templates and inbox"
>
<Dropzone
styles={{
root: {
borderRadius: '7px',
border: ` 1px solid ${
theme.colorScheme === 'dark' ? theme.colors.dark[5] : theme.colors.gray[5]
}`,
background: 'none',
},
}}
accept={Object.keys(mimeTypes)}
multiple={false}
onDrop={handleUpload}
data-test-id="upload-image-button"
>
<Group
position="center"
spacing="xl"
style={{ minHeight: 100, minWidth: 100, pointerEvents: 'none' }}
<DropzoneWrapper>
{field.value && (
<DropzoneOverlay>
<DropzoneButton type="button" onClick={() => dropzoneRef.current?.()}>
<Upload style={{ width: 20, height: 20 }} />
Update
</DropzoneButton>

<DropzoneButton type="button" onClick={removeFile}>
<Trash style={{ width: 20, height: 20 }} />
Remove
</DropzoneButton>
</DropzoneOverlay>
)}
<Dropzone
styles={{
root: {
background: 'none',
border: 'none',
},
}}
openRef={dropzoneRef}
accept={Object.keys(mimeTypes)}
multiple={false}
onDrop={handleUpload}
data-test-id="upload-image-button"
>
{!field.value ? (
<Upload style={{ width: 80, height: 80, color: colors.B60 }} />
) : (
<img
data-test-id="logo-image-wrapper"
src={field.value}
style={{ width: 100, height: 100, objectFit: 'contain' }}
alt="avatar"
/>
)}
</Group>
</Dropzone>
<Group
position="center"
spacing="xl"
style={{ minHeight: 100, minWidth: 100, pointerEvents: 'none' }}
>
{!field.value ? (
<div
style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
color: colors.B60,
gap: '4px',
}}
>
<Upload style={{ width: 20, height: 20 }} />
Upload
</div>
) : (
<img
data-test-id="logo-image-wrapper"
src={field.value}
style={{ width: 100, height: 100, objectFit: 'contain' }}
alt="avatar"
/>
)}
</Group>
</Dropzone>
</DropzoneWrapper>
</Input.Wrapper>
)}
control={control}
Expand Down Expand Up @@ -211,3 +243,45 @@ export function BrandingForm() {
</Stack>
);
}

const DropzoneButton: any = styled(UnstyledButton)`
color: ${colors.B70};
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;

&:hover {
color: ${colors.white};
}
`;

const DropzoneOverlay = styled('div')`
display: none;
justify-content: center;
align-items: center;
gap: 1.5rem;
z-index: 20;
border-radius: 7px;
position: absolute;
top: 0;
left: 0;
background-color: ${colors.BGDark + 'D6'};
backdrop-filter: blur(5px);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

D6 is alpha channel for hex color constant.

width: 100%;
height: 100%;
`;

const DropzoneWrapper = styled('div')`
position: relative;
border-radius: 7px;
border: 1px solid ${({ theme }) => (theme.colorScheme === 'dark' ? theme.colors.dark[5] : theme.colors.gray[5])};

&:hover {
cursor: pointer;

${DropzoneOverlay} {
display: flex;
}
}
`;
Comment on lines +280 to +286
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

show overlay on hover

Loading