Filepicker component and logo upload for org (#250)
This commit is contained in:
parent
5d820c7203
commit
8afa900044
|
|
@ -1,5 +1,5 @@
|
|||
import { ReduxActionTypes } from "constants/ReduxActionConstants";
|
||||
import { SaveOrgRequest } from "api/OrgApi";
|
||||
import { SaveOrgLogo, SaveOrgRequest } from "api/OrgApi";
|
||||
|
||||
export const fetchOrg = (orgId: string) => {
|
||||
return {
|
||||
|
|
@ -66,3 +66,19 @@ export const saveOrg = (orgSettings: SaveOrgRequest) => {
|
|||
payload: orgSettings,
|
||||
};
|
||||
};
|
||||
|
||||
export const uploadOrgLogo = (orgLogo: SaveOrgLogo) => {
|
||||
return {
|
||||
type: ReduxActionTypes.UPLOAD_ORG_LOGO,
|
||||
payload: orgLogo,
|
||||
};
|
||||
};
|
||||
|
||||
export const deleteOrgLogo = (id: string) => {
|
||||
return {
|
||||
type: ReduxActionTypes.REMOVE_ORG_LOGO,
|
||||
payload: {
|
||||
id: id,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -52,6 +52,12 @@ export interface SaveOrgRequest {
|
|||
email?: string;
|
||||
}
|
||||
|
||||
export interface SaveOrgLogo {
|
||||
id: string;
|
||||
logo: File;
|
||||
progress: (progressEvent: ProgressEvent) => void;
|
||||
}
|
||||
|
||||
export interface CreateOrgRequest {
|
||||
name: string;
|
||||
}
|
||||
|
|
@ -100,5 +106,26 @@ class OrgApi extends Api {
|
|||
roleName: null,
|
||||
});
|
||||
}
|
||||
static saveOrgLogo(request: SaveOrgLogo): AxiosPromise<ApiResponse> {
|
||||
const formData = new FormData();
|
||||
if (request.logo) {
|
||||
formData.append("file", request.logo);
|
||||
}
|
||||
|
||||
return Api.post(
|
||||
OrgApi.orgsURL + "/" + request.id + "/logo",
|
||||
formData,
|
||||
null,
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "multipart/form-data",
|
||||
},
|
||||
onUploadProgress: request.progress,
|
||||
},
|
||||
);
|
||||
}
|
||||
static deleteOrgLogo(request: { id: string }): AxiosPromise<ApiResponse> {
|
||||
return Api.delete(OrgApi.orgsURL + "/" + request.id + "/logo");
|
||||
}
|
||||
}
|
||||
export default OrgApi;
|
||||
|
|
|
|||
3
app/client/src/assets/icons/ads/upload.svg
Normal file
3
app/client/src/assets/icons/ads/upload.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="42" height="29" viewBox="0 0 42 29" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M34.3636 29H28.6364H23.8636V21.2667H27.6818L21 13.5333L14.3182 21.2667H18.1364V29H13.3636H7.63636C3.41918 29 0 25.5374 0 21.2667C0 17.6552 2.457 14.645 5.76164 13.7963C6.12818 6.11707 12.3728 0 20.0455 0C27.6322 0 33.8253 5.9798 34.3159 13.5391C38.598 13.5082 42 17.0191 42 21.2667C42 25.5374 38.5808 29 34.3636 29Z" fill="#9F9F9F"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 486 B |
353
app/client/src/components/ads/FilePicker.tsx
Normal file
353
app/client/src/components/ads/FilePicker.tsx
Normal file
|
|
@ -0,0 +1,353 @@
|
|||
import React, { useEffect, useRef, useState } from "react";
|
||||
import styled from "styled-components";
|
||||
import Button, { Category, Size } from "./Button";
|
||||
import axios from "axios";
|
||||
import { ReactComponent as UploadIcon } from "../../assets/icons/ads/upload.svg";
|
||||
import { DndProvider, useDrop, DropTargetMonitor } from "react-dnd";
|
||||
import HTML5Backend, { NativeTypes } from "react-dnd-html5-backend";
|
||||
import Text, { TextType } from "./Text";
|
||||
import { Classes, Variant } from "./common";
|
||||
import { Toaster } from "./Toast";
|
||||
|
||||
const CLOUDINARY_PRESETS_NAME = "";
|
||||
const CLOUDINARY_CLOUD_NAME = "";
|
||||
|
||||
type FilePickerProps = {
|
||||
onFileUploaded?: (fileUrl: string) => void;
|
||||
onFileRemoved?: () => void;
|
||||
fileUploader?: FileUploader;
|
||||
url?: string;
|
||||
logoUploadError?: string;
|
||||
};
|
||||
|
||||
const ContainerDiv = styled.div<{
|
||||
isUploaded: boolean;
|
||||
isActive: boolean;
|
||||
canDrop: boolean;
|
||||
}>`
|
||||
width: 320px;
|
||||
height: 190px;
|
||||
background-color: ${props => props.theme.colors.filePicker.bg};
|
||||
position: relative;
|
||||
|
||||
#fileInput {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.drag-drop-text {
|
||||
margin: ${props => props.theme.spaces[6]}px 0
|
||||
${props => props.theme.spaces[6]}px 0;
|
||||
color: ${props => props.theme.colors.filePicker.color};
|
||||
}
|
||||
|
||||
.bg-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-size: contain;
|
||||
}
|
||||
|
||||
.file-description {
|
||||
width: 95%;
|
||||
margin-top: auto;
|
||||
margin-bottom: ${props => props.theme.spaces[6] + 1}px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.file-spec {
|
||||
margin-bottom: ${props => props.theme.spaces[2]}px;
|
||||
span {
|
||||
margin-right: ${props => props.theme.spaces[4]}px;
|
||||
}
|
||||
}
|
||||
|
||||
.progress-container {
|
||||
width: 100%;
|
||||
background: ${props => props.theme.colors.filePicker.progress};
|
||||
transition: height 0.2s;
|
||||
}
|
||||
|
||||
.progress-inner {
|
||||
background-color: ${props => props.theme.colors.success.light};
|
||||
transition: width 0.4s ease;
|
||||
height: ${props => props.theme.spaces[1]}px;
|
||||
border-radius: ${props => props.theme.spaces[1] - 1}px;
|
||||
width: 0%;
|
||||
}
|
||||
|
||||
.button-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.remove-button {
|
||||
display: none;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
${props => props.theme.colors.filePicker.shadow.from},
|
||||
${props => props.theme.colors.filePicker.shadow.to}
|
||||
);
|
||||
opacity: 0.6;
|
||||
width: 100%;
|
||||
|
||||
a {
|
||||
width: 110px;
|
||||
margin: ${props => props.theme.spaces[13]}px
|
||||
${props => props.theme.spaces[3]}px ${props => props.theme.spaces[3]}px
|
||||
auto;
|
||||
.${Classes.ICON} {
|
||||
margin-right: ${props => props.theme.spaces[2] - 1}px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.remove-button {
|
||||
display: ${props => (props.isUploaded ? "block" : "none")};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export type SetProgress = (percentage: number) => void;
|
||||
export type UploadCallback = (url: string) => void;
|
||||
export type FileUploader = (
|
||||
file: any,
|
||||
setProgress: SetProgress,
|
||||
onUpload: UploadCallback,
|
||||
) => void;
|
||||
|
||||
export function CloudinaryUploader(
|
||||
file: any,
|
||||
setProgress: SetProgress,
|
||||
onUpload: UploadCallback,
|
||||
) {
|
||||
const formData = new FormData();
|
||||
formData.append("upload_preset", CLOUDINARY_PRESETS_NAME);
|
||||
if (file) {
|
||||
formData.append("file", file);
|
||||
}
|
||||
axios
|
||||
.post(
|
||||
`https://api.cloudinary.com/v1_1/${CLOUDINARY_CLOUD_NAME}/image/upload`,
|
||||
formData,
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "multipart/form-data",
|
||||
},
|
||||
onUploadProgress: function(progressEvent: ProgressEvent) {
|
||||
const uploadPercentage = Math.round(
|
||||
(progressEvent.loaded / progressEvent.total) * 100,
|
||||
);
|
||||
setProgress(uploadPercentage);
|
||||
},
|
||||
},
|
||||
)
|
||||
.then(data => {
|
||||
onUpload(data.data.url);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("error in file uploading", error);
|
||||
});
|
||||
}
|
||||
|
||||
const FilePickerComponent = (props: FilePickerProps) => {
|
||||
const { logoUploadError } = props;
|
||||
const [fileInfo, setFileInfo] = useState<{ name: string; size: number }>({
|
||||
name: "",
|
||||
size: 0,
|
||||
});
|
||||
const [isUploaded, setIsUploaded] = useState<boolean>(false);
|
||||
const [fileUrl, setFileUrl] = useState("");
|
||||
|
||||
const [{ canDrop, isOver }, drop] = useDrop({
|
||||
accept: [NativeTypes.FILE],
|
||||
drop(item, monitor) {
|
||||
onDrop(monitor);
|
||||
},
|
||||
collect: monitor => ({
|
||||
isOver: monitor.isOver(),
|
||||
canDrop: monitor.canDrop(),
|
||||
}),
|
||||
});
|
||||
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const bgRef = useRef<HTMLDivElement>(null);
|
||||
const progressRef = useRef<HTMLDivElement>(null);
|
||||
const fileDescRef = useRef<HTMLDivElement>(null);
|
||||
const fileContainerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
function ButtonClick(event: React.MouseEvent<HTMLElement>) {
|
||||
event.preventDefault();
|
||||
if (inputRef.current) {
|
||||
inputRef.current.click();
|
||||
}
|
||||
}
|
||||
|
||||
function onDrop(monitor: DropTargetMonitor) {
|
||||
if (monitor) {
|
||||
const files = monitor.getItem().files;
|
||||
if (files) {
|
||||
handleFileUpload(files);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setProgress(uploadPercentage: number) {
|
||||
if (progressRef.current) {
|
||||
progressRef.current.style.width = `${uploadPercentage}%`;
|
||||
}
|
||||
if (uploadPercentage === 100) {
|
||||
setIsUploaded(true);
|
||||
if (fileDescRef.current && bgRef.current) {
|
||||
fileDescRef.current.style.display = "none";
|
||||
bgRef.current.style.opacity = "1";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onUpload(url: string) {
|
||||
props.onFileUploaded && props.onFileUploaded(url);
|
||||
}
|
||||
|
||||
function handleFileUpload(files: FileList | null) {
|
||||
const file = files && files[0];
|
||||
let fileSize = 0;
|
||||
|
||||
if (file) {
|
||||
fileSize = Math.floor(file.size / 1024);
|
||||
setFileInfo({ name: file.name, size: fileSize });
|
||||
}
|
||||
|
||||
if (fileSize < 250) {
|
||||
if (bgRef.current) {
|
||||
bgRef.current.style.backgroundImage = `url(${URL.createObjectURL(
|
||||
file,
|
||||
)})`;
|
||||
bgRef.current.style.opacity = "0.5";
|
||||
}
|
||||
if (fileDescRef.current) {
|
||||
fileDescRef.current.style.display = "block";
|
||||
}
|
||||
if (fileContainerRef.current) {
|
||||
fileContainerRef.current.style.display = "none";
|
||||
}
|
||||
|
||||
/* set form data and send api request */
|
||||
props.fileUploader && props.fileUploader(file, setProgress, onUpload);
|
||||
} else {
|
||||
Toaster.show({
|
||||
text: "File size should be less than 250kb!",
|
||||
variant: Variant.warning,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function removeFile() {
|
||||
if (fileContainerRef.current && bgRef.current) {
|
||||
setFileUrl("");
|
||||
fileContainerRef.current.style.display = "flex";
|
||||
bgRef.current.style.backgroundImage = "url('')";
|
||||
setIsUploaded(false);
|
||||
props.onFileRemoved && props.onFileRemoved();
|
||||
}
|
||||
}
|
||||
|
||||
const isActive = canDrop && isOver;
|
||||
|
||||
useEffect(() => {
|
||||
if (props.url) {
|
||||
const urlKeys = props.url.split("/");
|
||||
if (urlKeys[urlKeys.length - 1] !== "null") {
|
||||
setFileUrl(props.url);
|
||||
} else {
|
||||
setFileUrl("");
|
||||
}
|
||||
}
|
||||
}, [props.url]);
|
||||
|
||||
useEffect(() => {
|
||||
if (fileUrl && !isUploaded) {
|
||||
setIsUploaded(true);
|
||||
if (bgRef.current) {
|
||||
bgRef.current.style.backgroundImage = `url(${fileUrl})`;
|
||||
bgRef.current.style.opacity = "1";
|
||||
}
|
||||
if (fileDescRef.current) {
|
||||
fileDescRef.current.style.display = "none";
|
||||
}
|
||||
if (fileContainerRef.current) {
|
||||
fileContainerRef.current.style.display = "none";
|
||||
}
|
||||
}
|
||||
}, [fileUrl, logoUploadError]);
|
||||
|
||||
return (
|
||||
<ContainerDiv
|
||||
isActive={isActive}
|
||||
canDrop={canDrop}
|
||||
isUploaded={isUploaded}
|
||||
ref={drop}
|
||||
>
|
||||
<div ref={bgRef} className="bg-image">
|
||||
<div className="button-wrapper" ref={fileContainerRef}>
|
||||
<UploadIcon />
|
||||
<Text type={TextType.P2} className="drag-drop-text">
|
||||
Drag & Drop files to upload or
|
||||
</Text>
|
||||
<form>
|
||||
<input
|
||||
type="file"
|
||||
id="fileInput"
|
||||
multiple={false}
|
||||
ref={inputRef}
|
||||
accept=".jpeg,.png,.svg"
|
||||
value={""}
|
||||
onChange={el => handleFileUpload(el.target.files)}
|
||||
/>
|
||||
<Button
|
||||
text="Browse"
|
||||
category={Category.tertiary}
|
||||
size={Size.medium}
|
||||
onClick={el => ButtonClick(el)}
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
<div className="file-description" ref={fileDescRef} id="fileDesc">
|
||||
<div className="file-spec">
|
||||
<Text type={TextType.H6}>{fileInfo.name}</Text>
|
||||
<Text type={TextType.H6}>{fileInfo.size}KB</Text>
|
||||
</div>
|
||||
<div className="progress-container">
|
||||
<div className="progress-inner" ref={progressRef}></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="remove-button">
|
||||
<Button
|
||||
text="remove"
|
||||
icon="delete"
|
||||
size={Size.medium}
|
||||
category={Category.tertiary}
|
||||
onClick={el => removeFile()}
|
||||
/>
|
||||
</div>
|
||||
</ContainerDiv>
|
||||
);
|
||||
};
|
||||
|
||||
const FilePicker = (props: FilePickerProps) => {
|
||||
return (
|
||||
<DndProvider backend={HTML5Backend}>
|
||||
<FilePickerComponent {...props} />
|
||||
</DndProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default FilePicker;
|
||||
|
|
@ -81,7 +81,7 @@ const boxStyles = (
|
|||
const StyledInput = styled.input<
|
||||
TextInputProps & { inputStyle: boxReturnType; isValid: boolean }
|
||||
>`
|
||||
width: ${props => (props.fill ? "100%" : "260px")};
|
||||
width: ${props => (props.fill ? "100%" : "320px")};
|
||||
border-radius: 0;
|
||||
outline: 0;
|
||||
box-shadow: none;
|
||||
|
|
|
|||
18
app/client/src/components/stories/FilePicker.stories.tsx
Normal file
18
app/client/src/components/stories/FilePicker.stories.tsx
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import React from "react";
|
||||
import FilePicker, { CloudinaryUploader } from "../ads/FilePicker";
|
||||
|
||||
export default {
|
||||
title: "FilePicker",
|
||||
component: FilePicker,
|
||||
};
|
||||
|
||||
function ShowUploadedFile(data: any) {
|
||||
console.log(data);
|
||||
}
|
||||
|
||||
export const withDynamicProps = () => (
|
||||
<FilePicker
|
||||
onFileUploaded={data => ShowUploadedFile(data)}
|
||||
fileUploader={CloudinaryUploader}
|
||||
/>
|
||||
);
|
||||
|
|
@ -690,6 +690,15 @@ type ColorType = {
|
|||
light: ShadeColor;
|
||||
dark: ShadeColor;
|
||||
};
|
||||
filePicker: {
|
||||
bg: ShadeColor;
|
||||
color: ShadeColor;
|
||||
progress: ShadeColor;
|
||||
shadow: {
|
||||
from: string;
|
||||
to: string;
|
||||
};
|
||||
};
|
||||
formFooter: {
|
||||
cancelBtn: ShadeColor;
|
||||
};
|
||||
|
|
@ -838,7 +847,7 @@ export const dark: ColorType = {
|
|||
border: darkShades[2],
|
||||
},
|
||||
normal: {
|
||||
bg: darkShades[0],
|
||||
bg: lightShades[10],
|
||||
text: darkShades[9],
|
||||
border: darkShades[0],
|
||||
},
|
||||
|
|
@ -967,6 +976,15 @@ export const dark: ColorType = {
|
|||
light: darkShades[2],
|
||||
dark: darkShades[4],
|
||||
},
|
||||
filePicker: {
|
||||
bg: darkShades[1],
|
||||
color: darkShades[7],
|
||||
progress: darkShades[6],
|
||||
shadow: {
|
||||
from: "rgba(21, 17, 17, 0.0001)",
|
||||
to: "rgba(9, 7, 7, 0.883386)",
|
||||
},
|
||||
},
|
||||
formFooter: {
|
||||
cancelBtn: darkShades[9],
|
||||
},
|
||||
|
|
@ -1244,6 +1262,15 @@ export const light: ColorType = {
|
|||
light: lightShades[2],
|
||||
dark: lightShades[4],
|
||||
},
|
||||
filePicker: {
|
||||
bg: lightShades[2],
|
||||
color: lightShades[7],
|
||||
progress: lightShades[6],
|
||||
shadow: {
|
||||
from: "rgba(253, 253, 253, 0.0001)",
|
||||
to: "rgba(250, 250, 250, 0.898847)",
|
||||
},
|
||||
},
|
||||
formFooter: {
|
||||
cancelBtn: lightShades[9],
|
||||
},
|
||||
|
|
@ -1259,7 +1286,7 @@ export const light: ColorType = {
|
|||
export const theme: Theme = {
|
||||
radii: [0, 4, 8, 10, 20, 50],
|
||||
fontSizes: [0, 10, 12, 14, 16, 18, 24, 28, 32, 48, 64],
|
||||
spaces: [0, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 30, 36],
|
||||
spaces: [0, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 30, 36, 38, 40, 42, 44],
|
||||
fontWeights: [0, 400, 500, 700],
|
||||
typography: {
|
||||
h1: {
|
||||
|
|
|
|||
|
|
@ -148,6 +148,8 @@ export const ReduxActionTypes: { [key: string]: string } = {
|
|||
FETCH_ORGS_INIT: "FETCH_ORGS_INIT",
|
||||
SAVE_ORG_INIT: "SAVE_ORG_INIT",
|
||||
SAVE_ORG_SUCCESS: "SAVE_ORG_SUCCESS",
|
||||
UPLOAD_ORG_LOGO: "UPLOAD_ORG_LOGO",
|
||||
REMOVE_ORG_LOGO: "REMOVE_ORG_LOGO",
|
||||
SAVING_ORG_INFO: "SAVING_ORG_INFO",
|
||||
SET_CURRENT_ORG: "SET_CURRENT_ORG",
|
||||
SET_CURRENT_ORG_ID: "SET_CURRENT_ORG_ID",
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ export type Org = {
|
|||
name: string;
|
||||
website?: string;
|
||||
email?: string;
|
||||
logoUrl?: string;
|
||||
uploadProgress?: number;
|
||||
userPermissions?: string[];
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import React from "react";
|
||||
|
||||
import { saveOrg } from "actions/orgActions";
|
||||
import { deleteOrgLogo, saveOrg, uploadOrgLogo } from "actions/orgActions";
|
||||
import { SaveOrgRequest } from "api/OrgApi";
|
||||
import { debounce } from "lodash";
|
||||
import TextInput, {
|
||||
|
|
@ -8,11 +8,19 @@ import TextInput, {
|
|||
notEmptyValidator,
|
||||
} from "components/ads/TextInput";
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
import { getCurrentOrg } from "selectors/organizationSelectors";
|
||||
import {
|
||||
getCurrentError,
|
||||
getCurrentOrg,
|
||||
} from "selectors/organizationSelectors";
|
||||
import { useParams } from "react-router-dom";
|
||||
import styled from "styled-components";
|
||||
import Text, { TextType } from "components/ads/Text";
|
||||
import { Classes } from "@blueprintjs/core";
|
||||
import { getOrgLoadingStates } from "selectors/organizationSelectors";
|
||||
import FilePicker, {
|
||||
SetProgress,
|
||||
UploadCallback,
|
||||
} from "components/ads/FilePicker";
|
||||
import { getIsFetchingApplications } from "selectors/applicationSelectors";
|
||||
|
||||
const InputLabelWrapper = styled.div`
|
||||
|
|
@ -36,7 +44,13 @@ export const SettingsHeading = styled(Text)`
|
|||
|
||||
const Loader = styled.div`
|
||||
height: 38px;
|
||||
width: 260px;
|
||||
width: 320px;
|
||||
border-radius: 0;
|
||||
`;
|
||||
|
||||
const FilePickerLoader = styled.div`
|
||||
height: 190px;
|
||||
width: 333px;
|
||||
border-radius: 0;
|
||||
`;
|
||||
|
||||
|
|
@ -73,6 +87,36 @@ export function GeneralSettings() {
|
|||
});
|
||||
}, timeout);
|
||||
|
||||
const { isFetchingOrg } = useSelector(getOrgLoadingStates);
|
||||
const logoUploadError = useSelector(getCurrentError);
|
||||
|
||||
const FileUploader = (
|
||||
file: File,
|
||||
setProgress: SetProgress,
|
||||
onUpload: UploadCallback,
|
||||
) => {
|
||||
const progress = (progressEvent: ProgressEvent) => {
|
||||
const uploadPercentage = Math.round(
|
||||
(progressEvent.loaded / progressEvent.total) * 100,
|
||||
);
|
||||
if (uploadPercentage === 100) {
|
||||
onUpload(currentOrg.logoUrl || "");
|
||||
}
|
||||
setProgress(uploadPercentage);
|
||||
};
|
||||
|
||||
dispatch(
|
||||
uploadOrgLogo({
|
||||
id: orgId as string,
|
||||
logo: file,
|
||||
progress: progress,
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const DeleteLogo = () => {
|
||||
dispatch(deleteOrgLogo(orgId));
|
||||
};
|
||||
const isFetchingApplications = useSelector(getIsFetchingApplications);
|
||||
|
||||
return (
|
||||
|
|
@ -96,6 +140,23 @@ export function GeneralSettings() {
|
|||
)}
|
||||
</SettingWrapper>
|
||||
|
||||
<SettingWrapper>
|
||||
<InputLabelWrapper>
|
||||
<Text type={TextType.H4}>Upload Logo</Text>
|
||||
</InputLabelWrapper>
|
||||
{isFetchingOrg && (
|
||||
<FilePickerLoader className={Classes.SKELETON}></FilePickerLoader>
|
||||
)}
|
||||
{!isFetchingOrg && (
|
||||
<FilePicker
|
||||
url={currentOrg && currentOrg.logoUrl}
|
||||
fileUploader={FileUploader}
|
||||
onFileRemoved={DeleteLogo}
|
||||
logoUploadError={logoUploadError.message}
|
||||
/>
|
||||
)}
|
||||
</SettingWrapper>
|
||||
|
||||
<SettingWrapper>
|
||||
<InputLabelWrapper>
|
||||
<Text type={TextType.H4}>Website</Text>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { call, takeLatest, put, all } from "redux-saga/effects";
|
||||
import { call, takeLatest, put, all, select } from "redux-saga/effects";
|
||||
import {
|
||||
ReduxActionTypes,
|
||||
ReduxAction,
|
||||
|
|
@ -22,10 +22,12 @@ import OrgApi, {
|
|||
DeleteOrgUserRequest,
|
||||
ChangeUserRoleRequest,
|
||||
FetchAllRolesRequest,
|
||||
SaveOrgLogo,
|
||||
} from "api/OrgApi";
|
||||
import { ApiResponse } from "api/ApiResponses";
|
||||
import { Toaster } from "components/ads/Toast";
|
||||
import { Variant } from "components/ads/common";
|
||||
import { getCurrentOrg } from "selectors/organizationSelectors";
|
||||
|
||||
export function* fetchRolesSaga() {
|
||||
try {
|
||||
|
|
@ -229,6 +231,60 @@ export function* createOrgSaga(
|
|||
}
|
||||
}
|
||||
|
||||
export function* uploadOrgLogoSaga(action: ReduxAction<SaveOrgLogo>) {
|
||||
try {
|
||||
const request = action.payload;
|
||||
const response: ApiResponse = yield call(OrgApi.saveOrgLogo, request);
|
||||
const isValidResponse = yield validateResponse(response);
|
||||
if (isValidResponse) {
|
||||
const currentOrg = yield select(getCurrentOrg);
|
||||
if (currentOrg && currentOrg.id === request.id) {
|
||||
const updatedOrg = {
|
||||
...currentOrg,
|
||||
logoUrl: response.data.logoUrl,
|
||||
};
|
||||
yield put({
|
||||
type: ReduxActionTypes.SET_CURRENT_ORG,
|
||||
payload: updatedOrg,
|
||||
});
|
||||
Toaster.show({
|
||||
text: "Logo uploaded successfully",
|
||||
variant: Variant.success,
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Error occured while uploading the logo", error);
|
||||
}
|
||||
}
|
||||
|
||||
export function* deleteOrgLogoSaga(action: ReduxAction<{ id: string }>) {
|
||||
try {
|
||||
const request = action.payload;
|
||||
const response: ApiResponse = yield call(OrgApi.deleteOrgLogo, request);
|
||||
const isValidResponse = yield validateResponse(response);
|
||||
if (isValidResponse) {
|
||||
const currentOrg = yield select(getCurrentOrg);
|
||||
if (currentOrg && currentOrg.id === request.id) {
|
||||
const updatedOrg = {
|
||||
...currentOrg,
|
||||
logoUrl: response.data.logoUrl,
|
||||
};
|
||||
yield put({
|
||||
type: ReduxActionTypes.SET_CURRENT_ORG,
|
||||
payload: updatedOrg,
|
||||
});
|
||||
Toaster.show({
|
||||
text: "Logo removed successfully",
|
||||
variant: Variant.success,
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Error occured while removing the logo", error);
|
||||
}
|
||||
}
|
||||
|
||||
export default function* orgSagas() {
|
||||
yield all([
|
||||
takeLatest(ReduxActionTypes.FETCH_ORG_ROLES_INIT, fetchRolesSaga),
|
||||
|
|
@ -242,5 +298,7 @@ export default function* orgSagas() {
|
|||
ReduxActionTypes.CHANGE_ORG_USER_ROLE_INIT,
|
||||
changeOrgUserRoleSaga,
|
||||
),
|
||||
takeLatest(ReduxActionTypes.UPLOAD_ORG_LOGO, uploadOrgLogoSaga),
|
||||
takeLatest(ReduxActionTypes.REMOVE_ORG_LOGO, deleteOrgLogoSaga),
|
||||
]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,3 +58,6 @@ export const getRolesForField = createSelector(getAllRoles, (roles?: any) => {
|
|||
export const getDefaultRole = createSelector(getRoles, (roles?: OrgRole[]) => {
|
||||
return roles?.find(role => role.isDefault);
|
||||
});
|
||||
export const getCurrentError = (state: AppState) => {
|
||||
return state.ui.errors;
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user