Add placeholder to signinup modal's secondary logo (#12079)
closes https://github.com/twentyhq/core-team-issues/issues/972 checks - - [x] If we have an icon it should be displayed as `secondary` <img width="1064" alt="Screenshot 2025-05-16 at 00 33 42" src="https://github.com/user-attachments/assets/20716df3-28be-40c5-b0fc-2133fccdad83" /> - [x] If no icon is provided, display a placeholder <img width="1062" alt="Screenshot 2025-05-16 at 00 33 09" src="https://github.com/user-attachments/assets/0241dafe-3d31-4f8e-a9e4-321634f6d41c" /> - [x] Add story for auth logos <img width="550" alt="Screenshot 2025-05-16 at 16 16 24" src="https://github.com/user-attachments/assets/3859547d-32c5-469f-879b-a130a36a23fa" /> <img width="548" alt="Screenshot 2025-05-16 at 16 16 30" src="https://github.com/user-attachments/assets/4937fa6a-459a-4c75-86ae-2080b1c8a5ae" /> <img width="487" alt="Screenshot 2025-05-16 at 16 16 16" src="https://github.com/user-attachments/assets/2c634be1-4a51-4b06-a590-cdd7eff91b76" /> <img width="556" alt="Screenshot 2025-05-16 at 16 16 07" src="https://github.com/user-attachments/assets/e4a518b4-c338-45c1-a329-5c9f85319c52" />
This commit is contained in:
@ -1,6 +1,7 @@
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { isNonEmptyString } from '@sniptt/guards';
|
import { isNonEmptyString } from '@sniptt/guards';
|
||||||
import { getImageAbsoluteURI } from 'twenty-shared/utils';
|
import { getImageAbsoluteURI, isDefined } from 'twenty-shared/utils';
|
||||||
|
import { Avatar } from 'twenty-ui/display';
|
||||||
import { UndecoratedLink } from 'twenty-ui/navigation';
|
import { UndecoratedLink } from 'twenty-ui/navigation';
|
||||||
import { REACT_APP_SERVER_BASE_URL } from '~/config';
|
import { REACT_APP_SERVER_BASE_URL } from '~/config';
|
||||||
import { useRedirectToDefaultDomain } from '~/modules/domain-manager/hooks/useRedirectToDefaultDomain';
|
import { useRedirectToDefaultDomain } from '~/modules/domain-manager/hooks/useRedirectToDefaultDomain';
|
||||||
@ -9,6 +10,7 @@ import { AppPath } from '~/modules/types/AppPath';
|
|||||||
type LogoProps = {
|
type LogoProps = {
|
||||||
primaryLogo?: string | null;
|
primaryLogo?: string | null;
|
||||||
secondaryLogo?: string | null;
|
secondaryLogo?: string | null;
|
||||||
|
placeholder?: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const StyledContainer = styled.div`
|
const StyledContainer = styled.div`
|
||||||
@ -47,23 +49,27 @@ const StyledPrimaryLogo = styled.div<{ src: string }>`
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const Logo = (props: LogoProps) => {
|
export const Logo = ({
|
||||||
|
primaryLogo,
|
||||||
|
secondaryLogo,
|
||||||
|
placeholder,
|
||||||
|
}: LogoProps) => {
|
||||||
const { redirectToDefaultDomain } = useRedirectToDefaultDomain();
|
const { redirectToDefaultDomain } = useRedirectToDefaultDomain();
|
||||||
const defaultPrimaryLogoUrl = `${window.location.origin}/images/icons/android/android-launchericon-192-192.png`;
|
const defaultPrimaryLogoUrl = `${window.location.origin}/images/icons/android/android-launchericon-192-192.png`;
|
||||||
|
|
||||||
const primaryLogoUrl = getImageAbsoluteURI({
|
const primaryLogoUrl = getImageAbsoluteURI({
|
||||||
imageUrl: props.primaryLogo ?? defaultPrimaryLogoUrl,
|
imageUrl: primaryLogo ?? defaultPrimaryLogoUrl,
|
||||||
baseUrl: REACT_APP_SERVER_BASE_URL,
|
baseUrl: REACT_APP_SERVER_BASE_URL,
|
||||||
});
|
});
|
||||||
|
|
||||||
const secondaryLogoUrl = isNonEmptyString(props.secondaryLogo)
|
const secondaryLogoUrl = isNonEmptyString(secondaryLogo)
|
||||||
? getImageAbsoluteURI({
|
? getImageAbsoluteURI({
|
||||||
imageUrl: props.secondaryLogo,
|
imageUrl: secondaryLogo,
|
||||||
baseUrl: REACT_APP_SERVER_BASE_URL,
|
baseUrl: REACT_APP_SERVER_BASE_URL,
|
||||||
})
|
})
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
const isUsingDefaultLogo = !props.primaryLogo;
|
const isUsingDefaultLogo = !isDefined(primaryLogo);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledContainer>
|
<StyledContainer>
|
||||||
@ -72,15 +78,26 @@ export const Logo = (props: LogoProps) => {
|
|||||||
to={AppPath.SignInUp}
|
to={AppPath.SignInUp}
|
||||||
onClick={redirectToDefaultDomain}
|
onClick={redirectToDefaultDomain}
|
||||||
>
|
>
|
||||||
<StyledPrimaryLogo src={primaryLogoUrl ?? ''} />
|
<StyledPrimaryLogo src={primaryLogoUrl} />
|
||||||
</UndecoratedLink>
|
</UndecoratedLink>
|
||||||
) : (
|
) : (
|
||||||
<StyledPrimaryLogo src={primaryLogoUrl ?? ''} />
|
<StyledPrimaryLogo src={primaryLogoUrl} />
|
||||||
)}
|
)}
|
||||||
{secondaryLogoUrl && (
|
{isDefined(secondaryLogoUrl) ? (
|
||||||
<StyledSecondaryLogoContainer>
|
<StyledSecondaryLogoContainer>
|
||||||
<StyledSecondaryLogo src={secondaryLogoUrl} />
|
<StyledSecondaryLogo src={secondaryLogoUrl} />
|
||||||
</StyledSecondaryLogoContainer>
|
</StyledSecondaryLogoContainer>
|
||||||
|
) : (
|
||||||
|
isDefined(placeholder) && (
|
||||||
|
<StyledSecondaryLogoContainer>
|
||||||
|
<Avatar
|
||||||
|
size="lg"
|
||||||
|
placeholder={placeholder}
|
||||||
|
type="squared"
|
||||||
|
placeholderColorSeed={placeholder}
|
||||||
|
/>
|
||||||
|
</StyledSecondaryLogoContainer>
|
||||||
|
)
|
||||||
)}
|
)}
|
||||||
</StyledContainer>
|
</StyledContainer>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -0,0 +1,51 @@
|
|||||||
|
import { Meta, StoryObj } from '@storybook/react';
|
||||||
|
|
||||||
|
import {
|
||||||
|
ComponentDecorator,
|
||||||
|
RecoilRootDecorator,
|
||||||
|
RouterDecorator,
|
||||||
|
} from 'twenty-ui/testing';
|
||||||
|
import { Logo } from '../Logo';
|
||||||
|
|
||||||
|
const logoUrl = 'https://picsum.photos/192/192';
|
||||||
|
|
||||||
|
const meta: Meta<typeof Logo> = {
|
||||||
|
title: 'Modules/Auth/Logo',
|
||||||
|
component: Logo,
|
||||||
|
decorators: [ComponentDecorator, RecoilRootDecorator, RouterDecorator],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof Logo>;
|
||||||
|
|
||||||
|
export const WithSecondaryLogo: Story = {
|
||||||
|
args: {
|
||||||
|
primaryLogo: null,
|
||||||
|
secondaryLogo: logoUrl,
|
||||||
|
placeholder: 'A',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WithPlaceholder: Story = {
|
||||||
|
args: {
|
||||||
|
primaryLogo: null,
|
||||||
|
secondaryLogo: null,
|
||||||
|
placeholder: 'B',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WithPrimaryAndSecondaryLogo: Story = {
|
||||||
|
args: {
|
||||||
|
primaryLogo: logoUrl,
|
||||||
|
secondaryLogo: logoUrl,
|
||||||
|
placeholder: 'C',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WithPrimaryLogoAndPlaceholder: Story = {
|
||||||
|
args: {
|
||||||
|
primaryLogo: logoUrl,
|
||||||
|
secondaryLogo: null,
|
||||||
|
placeholder: 'D',
|
||||||
|
},
|
||||||
|
};
|
||||||
@ -18,7 +18,7 @@ const VerifyEmailEffectErrorState = ({ email = 'user@example.com' }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const meta: Meta<typeof VerifyEmailEffectErrorState> = {
|
const meta: Meta<typeof VerifyEmailEffectErrorState> = {
|
||||||
title: 'Pages/Auth/VerifyEmailEffect',
|
title: 'Modules/Auth/VerifyEmailEffect',
|
||||||
component: VerifyEmailEffectErrorState,
|
component: VerifyEmailEffectErrorState,
|
||||||
decorators: [
|
decorators: [
|
||||||
(Story) => (
|
(Story) => (
|
||||||
|
|||||||
@ -19,7 +19,7 @@ const RenderWithModal = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
const meta: Meta<typeof EmailVerificationSent> = {
|
const meta: Meta<typeof EmailVerificationSent> = {
|
||||||
title: 'Pages/Auth/EmailVerificationSent',
|
title: 'Modules/Auth/EmailVerificationSent',
|
||||||
component: EmailVerificationSent,
|
component: EmailVerificationSent,
|
||||||
decorators: [ComponentDecorator, SnackBarDecorator],
|
decorators: [ComponentDecorator, SnackBarDecorator],
|
||||||
parameters: {
|
parameters: {
|
||||||
|
|||||||
@ -173,7 +173,10 @@ export const PasswordReset = () => {
|
|||||||
<Modal.Content isVerticalCentered isHorizontalCentered>
|
<Modal.Content isVerticalCentered isHorizontalCentered>
|
||||||
<StyledMainContainer>
|
<StyledMainContainer>
|
||||||
<AnimatedEaseIn>
|
<AnimatedEaseIn>
|
||||||
<Logo secondaryLogo={workspacePublicData?.logo} />
|
<Logo
|
||||||
|
secondaryLogo={workspacePublicData?.logo}
|
||||||
|
placeholder={workspacePublicData?.displayName}
|
||||||
|
/>
|
||||||
</AnimatedEaseIn>
|
</AnimatedEaseIn>
|
||||||
<Title animate>
|
<Title animate>
|
||||||
<Trans>Reset Password</Trans>
|
<Trans>Reset Password</Trans>
|
||||||
|
|||||||
@ -41,7 +41,10 @@ const StandardContent = ({
|
|||||||
return (
|
return (
|
||||||
<Modal.Content isVerticalCentered isHorizontalCentered>
|
<Modal.Content isVerticalCentered isHorizontalCentered>
|
||||||
<AnimatedEaseIn>
|
<AnimatedEaseIn>
|
||||||
<Logo secondaryLogo={workspacePublicData?.logo} />
|
<Logo
|
||||||
|
secondaryLogo={workspacePublicData?.logo}
|
||||||
|
placeholder={workspacePublicData?.displayName}
|
||||||
|
/>
|
||||||
</AnimatedEaseIn>
|
</AnimatedEaseIn>
|
||||||
<Title animate>{title}</Title>
|
<Title animate>{title}</Title>
|
||||||
{signInUpForm}
|
{signInUpForm}
|
||||||
|
|||||||
Reference in New Issue
Block a user