fix: Team logo shows as white in settings (#3015)
* fix: Team logo shows as white in settings fix: Team logo doesnt update in sidebar immediately after updating refactor to ImageUpload component * text
This commit is contained in:
@@ -3,28 +3,26 @@ import { TeamIcon } from "outline-icons";
|
|||||||
import { useRef, useState } from "react";
|
import { useRef, useState } from "react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { useTranslation, Trans } from "react-i18next";
|
import { useTranslation, Trans } from "react-i18next";
|
||||||
import styled from "styled-components";
|
|
||||||
import Button from "~/components/Button";
|
import Button from "~/components/Button";
|
||||||
import Flex from "~/components/Flex";
|
|
||||||
import Heading from "~/components/Heading";
|
import Heading from "~/components/Heading";
|
||||||
import HelpText from "~/components/HelpText";
|
import HelpText from "~/components/HelpText";
|
||||||
import Input, { LabelText } from "~/components/Input";
|
import Input from "~/components/Input";
|
||||||
import Scene from "~/components/Scene";
|
import Scene from "~/components/Scene";
|
||||||
import env from "~/env";
|
import env from "~/env";
|
||||||
import useCurrentTeam from "~/hooks/useCurrentTeam";
|
import useCurrentTeam from "~/hooks/useCurrentTeam";
|
||||||
import useStores from "~/hooks/useStores";
|
import useStores from "~/hooks/useStores";
|
||||||
import useToasts from "~/hooks/useToasts";
|
import useToasts from "~/hooks/useToasts";
|
||||||
import ImageUpload from "./components/ImageUpload";
|
import ImageInput from "./components/ImageInput";
|
||||||
|
|
||||||
function Details() {
|
function Details() {
|
||||||
const { auth } = useStores();
|
const { auth } = useStores();
|
||||||
const { showToast } = useToasts();
|
const { showToast } = useToasts();
|
||||||
const team = useCurrentTeam();
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const team = useCurrentTeam();
|
||||||
const form = useRef<HTMLFormElement>(null);
|
const form = useRef<HTMLFormElement>(null);
|
||||||
const [name, setName] = useState(team.name);
|
const [name, setName] = useState(team.name);
|
||||||
const [subdomain, setSubdomain] = useState(team.subdomain);
|
const [subdomain, setSubdomain] = useState(team.subdomain);
|
||||||
const [avatarUrl, setAvatarUrl] = useState<string>();
|
const [avatarUrl, setAvatarUrl] = useState<string>(team.avatarUrl);
|
||||||
|
|
||||||
const handleSubmit = React.useCallback(
|
const handleSubmit = React.useCallback(
|
||||||
async (event?: React.SyntheticEvent) => {
|
async (event?: React.SyntheticEvent) => {
|
||||||
@@ -64,13 +62,15 @@ function Details() {
|
|||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleAvatarUpload = React.useCallback(
|
const handleAvatarUpload = async (avatarUrl: string) => {
|
||||||
(avatarUrl: string) => {
|
setAvatarUrl(avatarUrl);
|
||||||
setAvatarUrl(avatarUrl);
|
await auth.updateTeam({
|
||||||
handleSubmit();
|
avatarUrl,
|
||||||
},
|
});
|
||||||
[handleSubmit]
|
showToast(t("Logo updated"), {
|
||||||
);
|
type: "success",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const handleAvatarError = React.useCallback(
|
const handleAvatarError = React.useCallback(
|
||||||
(error: string | null | undefined) => {
|
(error: string | null | undefined) => {
|
||||||
@@ -90,22 +90,14 @@ function Details() {
|
|||||||
</Trans>
|
</Trans>
|
||||||
</HelpText>
|
</HelpText>
|
||||||
|
|
||||||
<ProfilePicture column>
|
<ImageInput
|
||||||
<LabelText>{t("Logo")}</LabelText>
|
label={t("Logo")}
|
||||||
<AvatarContainer>
|
onSuccess={handleAvatarUpload}
|
||||||
<ImageUpload
|
onError={handleAvatarError}
|
||||||
onSuccess={handleAvatarUpload}
|
src={avatarUrl}
|
||||||
onError={handleAvatarError}
|
borderRadius={0}
|
||||||
submitText={t("Crop logo")}
|
/>
|
||||||
borderRadius={0}
|
|
||||||
>
|
|
||||||
<Avatar src={avatarUrl} />
|
|
||||||
<Flex auto align="center" justify="center">
|
|
||||||
<Trans>Upload</Trans>
|
|
||||||
</Flex>
|
|
||||||
</ImageUpload>
|
|
||||||
</AvatarContainer>
|
|
||||||
</ProfilePicture>
|
|
||||||
<form onSubmit={handleSubmit} ref={form}>
|
<form onSubmit={handleSubmit} ref={form}>
|
||||||
<Input
|
<Input
|
||||||
label={t("Name")}
|
label={t("Name")}
|
||||||
@@ -144,43 +136,4 @@ function Details() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const ProfilePicture = styled(Flex)`
|
|
||||||
margin-bottom: 24px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const avatarStyles = `
|
|
||||||
width: 80px;
|
|
||||||
height: 80px;
|
|
||||||
border-radius: 8px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const AvatarContainer = styled(Flex)`
|
|
||||||
${avatarStyles};
|
|
||||||
position: relative;
|
|
||||||
box-shadow: 0 0 0 1px #dae1e9;
|
|
||||||
background: ${(props) => props.theme.white};
|
|
||||||
|
|
||||||
div div {
|
|
||||||
${avatarStyles};
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
opacity: 0;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 250ms;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover div {
|
|
||||||
opacity: 1;
|
|
||||||
background: rgba(0, 0, 0, 0.75);
|
|
||||||
color: ${(props) => props.theme.white};
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Avatar = styled.img`
|
|
||||||
${avatarStyles};
|
|
||||||
`;
|
|
||||||
|
|
||||||
export default observer(Details);
|
export default observer(Details);
|
||||||
|
|||||||
@@ -6,36 +6,44 @@ import styled from "styled-components";
|
|||||||
import { languageOptions } from "@shared/i18n";
|
import { languageOptions } from "@shared/i18n";
|
||||||
import UserDelete from "~/scenes/UserDelete";
|
import UserDelete from "~/scenes/UserDelete";
|
||||||
import Button from "~/components/Button";
|
import Button from "~/components/Button";
|
||||||
import Flex from "~/components/Flex";
|
|
||||||
import Heading from "~/components/Heading";
|
import Heading from "~/components/Heading";
|
||||||
import HelpText from "~/components/HelpText";
|
import HelpText from "~/components/HelpText";
|
||||||
import Input, { LabelText } from "~/components/Input";
|
import Input from "~/components/Input";
|
||||||
import InputSelect from "~/components/InputSelect";
|
import InputSelect from "~/components/InputSelect";
|
||||||
import Scene from "~/components/Scene";
|
import Scene from "~/components/Scene";
|
||||||
|
import useCurrentUser from "~/hooks/useCurrentUser";
|
||||||
import useStores from "~/hooks/useStores";
|
import useStores from "~/hooks/useStores";
|
||||||
import useToasts from "~/hooks/useToasts";
|
import useToasts from "~/hooks/useToasts";
|
||||||
import ImageUpload from "./components/ImageUpload";
|
import ImageInput from "./components/ImageInput";
|
||||||
|
|
||||||
const Profile = () => {
|
const Profile = () => {
|
||||||
const { auth } = useStores();
|
const { auth } = useStores();
|
||||||
|
const user = useCurrentUser();
|
||||||
const form = React.useRef<HTMLFormElement>(null);
|
const form = React.useRef<HTMLFormElement>(null);
|
||||||
const [name, setName] = React.useState<string>(auth.user?.name || "");
|
const [name, setName] = React.useState<string>(user.name || "");
|
||||||
const [avatarUrl, setAvatarUrl] = React.useState<string | null | undefined>();
|
const [avatarUrl, setAvatarUrl] = React.useState<string>(user.avatarUrl);
|
||||||
const [showDeleteModal, setShowDeleteModal] = React.useState(false);
|
const [showDeleteModal, setShowDeleteModal] = React.useState(false);
|
||||||
const [language, setLanguage] = React.useState(auth.user?.language);
|
const [language, setLanguage] = React.useState(user.language);
|
||||||
const { showToast } = useToasts();
|
const { showToast } = useToasts();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handleSubmit = async (ev: React.SyntheticEvent) => {
|
const handleSubmit = async (ev: React.SyntheticEvent) => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
await auth.updateUser({
|
|
||||||
name,
|
try {
|
||||||
avatarUrl,
|
await auth.updateUser({
|
||||||
language,
|
name,
|
||||||
});
|
avatarUrl,
|
||||||
showToast(t("Profile saved"), {
|
language,
|
||||||
type: "success",
|
});
|
||||||
});
|
showToast(t("Profile saved"), {
|
||||||
|
type: "success",
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
showToast(err.message, {
|
||||||
|
type: "error",
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleNameChange = (ev: React.ChangeEvent<HTMLInputElement>) => {
|
const handleNameChange = (ev: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
@@ -67,26 +75,19 @@ const Profile = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const isValid = form.current && form.current.checkValidity();
|
const isValid = form.current && form.current.checkValidity();
|
||||||
const { user, isSaving } = auth;
|
const { isSaving } = auth;
|
||||||
if (!user) return null;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Scene title={t("Profile")} icon={<ProfileIcon color="currentColor" />}>
|
<Scene title={t("Profile")} icon={<ProfileIcon color="currentColor" />}>
|
||||||
<Heading>{t("Profile")}</Heading>
|
<Heading>{t("Profile")}</Heading>
|
||||||
<ProfilePicture column>
|
|
||||||
<LabelText>{t("Photo")}</LabelText>
|
<ImageInput
|
||||||
<AvatarContainer>
|
label={t("Photo")}
|
||||||
<ImageUpload
|
onSuccess={handleAvatarUpload}
|
||||||
onSuccess={handleAvatarUpload}
|
onError={handleAvatarError}
|
||||||
onError={handleAvatarError}
|
src={avatarUrl}
|
||||||
>
|
/>
|
||||||
<Avatar src={avatarUrl || user?.avatarUrl} />
|
|
||||||
<Flex auto align="center" justify="center">
|
|
||||||
{t("Upload")}
|
|
||||||
</Flex>
|
|
||||||
</ImageUpload>
|
|
||||||
</AvatarContainer>
|
|
||||||
</ProfilePicture>
|
|
||||||
<form onSubmit={handleSubmit} ref={form}>
|
<form onSubmit={handleSubmit} ref={form}>
|
||||||
<Input
|
<Input
|
||||||
label={t("Full name")}
|
label={t("Full name")}
|
||||||
@@ -145,41 +146,4 @@ const DangerZone = styled.div`
|
|||||||
margin-top: 60px;
|
margin-top: 60px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const ProfilePicture = styled(Flex)`
|
|
||||||
margin-bottom: 24px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const avatarStyles = `
|
|
||||||
width: 80px;
|
|
||||||
height: 80px;
|
|
||||||
border-radius: 8px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const AvatarContainer = styled(Flex)`
|
|
||||||
${avatarStyles};
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
div div {
|
|
||||||
${avatarStyles};
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
opacity: 0;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 250ms;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover div {
|
|
||||||
opacity: 1;
|
|
||||||
background: rgba(0, 0, 0, 0.75);
|
|
||||||
color: ${(props) => props.theme.white};
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Avatar = styled.img`
|
|
||||||
${avatarStyles};
|
|
||||||
`;
|
|
||||||
|
|
||||||
export default observer(Profile);
|
export default observer(Profile);
|
||||||
|
|||||||
70
app/scenes/Settings/components/ImageInput.tsx
Normal file
70
app/scenes/Settings/components/ImageInput.tsx
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import styled from "styled-components";
|
||||||
|
import Flex from "~/components/Flex";
|
||||||
|
import { LabelText } from "~/components/Input";
|
||||||
|
import ImageUpload, { Props as ImageUploadProps } from "./ImageUpload";
|
||||||
|
|
||||||
|
type Props = ImageUploadProps & {
|
||||||
|
label: string;
|
||||||
|
src?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function ImageInput({ label, src, ...rest }: Props) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<InputWrapper column>
|
||||||
|
<LabelText>{label}</LabelText>
|
||||||
|
<ImageBox>
|
||||||
|
<ImageUpload {...rest}>
|
||||||
|
<Avatar src={src} />
|
||||||
|
<Flex auto align="center" justify="center">
|
||||||
|
{t("Upload")}
|
||||||
|
</Flex>
|
||||||
|
</ImageUpload>
|
||||||
|
</ImageBox>
|
||||||
|
</InputWrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const InputWrapper = styled(Flex)`
|
||||||
|
margin-bottom: 24px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const avatarStyles = `
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Avatar = styled.img`
|
||||||
|
${avatarStyles};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ImageBox = styled(Flex)`
|
||||||
|
${avatarStyles};
|
||||||
|
position: relative;
|
||||||
|
font-size: 14px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 0 0 1px ${(props) => props.theme.secondaryBackground};
|
||||||
|
background: ${(props) => props.theme.background};
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
div div {
|
||||||
|
${avatarStyles};
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
opacity: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 250ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover div {
|
||||||
|
opacity: 1;
|
||||||
|
background: rgba(0, 0, 0, 0.75);
|
||||||
|
color: ${(props) => props.theme.white};
|
||||||
|
}
|
||||||
|
`;
|
||||||
@@ -16,16 +16,16 @@ import { uploadFile, dataUrlToBlob } from "~/utils/uploadFile";
|
|||||||
|
|
||||||
const EMPTY_OBJECT = {};
|
const EMPTY_OBJECT = {};
|
||||||
|
|
||||||
type Props = RootStore & {
|
export type Props = {
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
onSuccess: (arg0: string) => void | Promise<void>;
|
onSuccess: (url: string) => void | Promise<void>;
|
||||||
onError: (arg0: string) => void;
|
onError: (error: string) => void;
|
||||||
submitText?: string;
|
submitText?: string;
|
||||||
borderRadius?: number;
|
borderRadius?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
class ImageUpload extends React.Component<Props> {
|
class ImageUpload extends React.Component<RootStore & Props> {
|
||||||
@observable
|
@observable
|
||||||
isUploading = false;
|
isUploading = false;
|
||||||
|
|
||||||
@@ -41,7 +41,7 @@ class ImageUpload extends React.Component<Props> {
|
|||||||
avatarEditorRef = React.createRef<AvatarEditor>();
|
avatarEditorRef = React.createRef<AvatarEditor>();
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
submitText: "Crop Picture",
|
submitText: "Crop Image",
|
||||||
borderRadius: 150,
|
borderRadius: 150,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -517,6 +517,7 @@
|
|||||||
"Error": "Error",
|
"Error": "Error",
|
||||||
"All collections": "All collections",
|
"All collections": "All collections",
|
||||||
"{{userName}} requested": "{{userName}} requested",
|
"{{userName}} requested": "{{userName}} requested",
|
||||||
|
"Upload": "Upload",
|
||||||
"Last active": "Last active",
|
"Last active": "Last active",
|
||||||
"Suspended": "Suspended",
|
"Suspended": "Suspended",
|
||||||
"Shared": "Shared",
|
"Shared": "Shared",
|
||||||
@@ -534,11 +535,10 @@
|
|||||||
"Active": "Active",
|
"Active": "Active",
|
||||||
"Everyone": "Everyone",
|
"Everyone": "Everyone",
|
||||||
"Admins": "Admins",
|
"Admins": "Admins",
|
||||||
|
"Logo updated": "Logo updated",
|
||||||
"Unable to upload new logo": "Unable to upload new logo",
|
"Unable to upload new logo": "Unable to upload new logo",
|
||||||
"These details affect the way that your Outline appears to everyone on the team.": "These details affect the way that your Outline appears to everyone on the team.",
|
"These details affect the way that your Outline appears to everyone on the team.": "These details affect the way that your Outline appears to everyone on the team.",
|
||||||
"Logo": "Logo",
|
"Logo": "Logo",
|
||||||
"Crop logo": "Crop logo",
|
|
||||||
"Upload": "Upload",
|
|
||||||
"Subdomain": "Subdomain",
|
"Subdomain": "Subdomain",
|
||||||
"Your knowledge base will be accessible at": "Your knowledge base will be accessible at",
|
"Your knowledge base will be accessible at": "Your knowledge base will be accessible at",
|
||||||
"Manage optional and beta features. Changing these settings will affect the experience for all team members.": "Manage optional and beta features. Changing these settings will affect the experience for all team members.",
|
"Manage optional and beta features. Changing these settings will affect the experience for all team members.": "Manage optional and beta features. Changing these settings will affect the experience for all team members.",
|
||||||
|
|||||||
Reference in New Issue
Block a user