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:
Tom Moor
2022-01-26 22:47:26 -08:00
committed by GitHub
parent afb0dad0a5
commit 76e98c31e3
5 changed files with 129 additions and 142 deletions

View File

@@ -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);

View File

@@ -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);

View 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};
}
`;

View File

@@ -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,
}; };

View File

@@ -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.",