Skip to content

Commit a9666fa

Browse files
author
MargeBot
committed
Merge branch 'lumo/account-signin-helper' into 'main'
Addition of Lumo helper in signin page See merge request web/clients!25303
2 parents 9362958 + 50c4fd1 commit a9666fa

5 files changed

Lines changed: 164 additions & 2 deletions

File tree

applications/account/src/app/login/LoginForm.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ import SupportDropdown from '../public/SupportDropdown';
5252
import { defaultPersistentKey } from '../public/helper';
5353
import { RememberMode } from './LoginContainer';
5454
import SignupButton from './SignupButton';
55+
import { IcArrowOutSquare } from '@proton/icons/icons/IcArrowOutSquare';
5556

5657
export interface LoginFormRef {
5758
getIsLoading: () => boolean;
@@ -716,6 +717,15 @@ const LoginForm = ({
716717
</Link>
717718
)
718719
}
720+
<hr className='m-0'/>
721+
<Href
722+
href={getKnowledgeBaseUrl('/common-login-problems')}
723+
className="dropdown-item-link w-full px-4 py-2 flex flex-nowrap gap-2 items-center text-no-decoration text-left"
724+
>
725+
726+
{c('Link').t`Sign-in help`}
727+
<IcArrowOutSquare className="color-weak ml-auto" />
728+
</Href>
719729
</SupportDropdown>
720730
</div>
721731
</>

applications/account/src/app/login/LoginRender.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import Content from '../public/Content';
66
import Header from '../public/Header';
77
import Layout from '../public/Layout';
88
import Main from '../public/Main';
9-
import PublicHelpLink from '../public/PublicHelpLink';
9+
import TroubleshootWithLumo from '../public/TroubleshootWithLumo';
1010

1111
export interface RenderProps {
1212
title: ReactNode;
@@ -39,7 +39,7 @@ export const defaultLoginRender = (data: RenderProps) => {
3939
hasWelcome
4040
onBack={data.onBack}
4141
hasDecoration={data.hasDecoration}
42-
bottomRight={<PublicHelpLink />}
42+
bottomRight={<TroubleshootWithLumo />}
4343
>
4444
{data.beforeMain}
4545
<Main>
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
@use '~@proton/styles/scss/lib/config' as *;
2+
@use '~@proton/styles/scss/lib/include-media' as im;
3+
4+
im.$breakpoints: $breakpoints-product;
5+
im.$media-expressions: $media-expressions;
6+
7+
.lumo-troubleshoot {
8+
&-panel {
9+
inset-block-end: calc(100% + 0.75rem);
10+
inset-inline-end: 0;
11+
inline-size: min(26rem, calc(100vw - 3rem));
12+
block-size: min(70vh, 38rem);
13+
visibility: hidden;
14+
opacity: 0;
15+
transform: translateY(0.5rem);
16+
pointer-events: none;
17+
transition:
18+
opacity 0.15s ease,
19+
transform 0.15s ease;
20+
21+
&.is-open {
22+
visibility: visible;
23+
opacity: 1;
24+
transform: translateY(0);
25+
pointer-events: auto;
26+
}
27+
28+
// On mobile the launcher sits in the corner and the panel would overflow off-screen,
29+
// so promote it to a full-screen sheet (modal-like) without unmounting the iframe.
30+
@include im.media('<=small') {
31+
position: fixed;
32+
inset: 0;
33+
inline-size: 100%;
34+
block-size: 100%;
35+
border: none;
36+
border-radius: 0;
37+
transform: translateY(1rem);
38+
39+
&.is-open {
40+
transform: translateY(0);
41+
}
42+
}
43+
}
44+
45+
&-avatar {
46+
inline-size: 1.75rem;
47+
}
48+
49+
&-avatar-icon {
50+
inline-size: 1.1rem;
51+
}
52+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import { useState } from 'react';
2+
3+
import { c } from 'ttag';
4+
5+
import { Button } from '@proton/atoms/Button/Button';
6+
import { useTheme } from '@proton/components';
7+
import { IcCross } from '@proton/icons/icons/IcCross';
8+
import { getAppHref } from '@proton/shared/lib/apps/helper';
9+
import { APPS, LUMO_SHORT_APP_NAME } from '@proton/shared/lib/constants';
10+
import lumoCatIcon from '@proton/styles/assets/img/lumo/lumo-cat-icon.svg';
11+
import { useFlag } from '@proton/unleash/useFlag';
12+
import clsx from '@proton/utils/clsx';
13+
14+
import './TroubleshootWithLumo.scss';
15+
16+
// Opens the minimal Lumo "agent" surface pre-loaded with the account-protection agent. Using
17+
// getAppHref keeps the host correct per environment (e.g. lumo.proton.me vs lumo.proton.dev).
18+
// `theme` makes the embedded Lumo surface match this page's light/dark appearance.
19+
const buildLumoSrc = (theme: 'light' | 'dark') =>
20+
getAppHref(`/agent?skill=proton-account-recovery&theme=${theme}`, APPS.PROTONLUMO);
21+
22+
// Bottom-right entry point on the sign-in page. Opens the account-protection Lumo agent in a
23+
// floating, non-blocking panel. Unlike a modal, the panel lets users keep following recovery
24+
// steps on the page while the chat stays open, and the iframe is kept mounted across close/reopen
25+
// so the conversation is preserved (we only toggle visibility instead of unmounting).
26+
const TroubleshootWithLumo = () => {
27+
const [isOpen, setIsOpen] = useState(false);
28+
// Resolved once on first open: this lazy-loads the iframe AND freezes its `src`, so a later
29+
// theme toggle on the host page can't reload it and wipe the conversation.
30+
const [src, setSrc] = useState<string | undefined>(undefined);
31+
const theme = useTheme();
32+
const lumoSignInHelperEnabled = useFlag('LumoSignInHelp');
33+
34+
const title = c('Title').t`Troubleshoot with ${LUMO_SHORT_APP_NAME}`;
35+
36+
const open = () => {
37+
if (!src) {
38+
setSrc(buildLumoSrc(theme.information.dark ? 'dark' : 'light'));
39+
}
40+
setIsOpen(true);
41+
};
42+
43+
// Gated behind the rollout flag: when off, no Lumo entry point appears on the sign-in page.
44+
if (!lumoSignInHelperEnabled) {
45+
return null;
46+
}
47+
48+
return (
49+
<div className="relative inline-block text-left">
50+
{src && (
51+
<div
52+
role="dialog"
53+
aria-label={title}
54+
aria-hidden={!isOpen}
55+
className={clsx(
56+
'lumo-troubleshoot-panel absolute z-50 bg-norm border border-weak rounded-xl sm:shadow-lifted shadow-color-primary overflow-hidden flex flex-column flex-nowrap',
57+
isOpen && 'is-open'
58+
)}
59+
>
60+
<div className="flex flex-row flex-nowrap items-center gap-2 px-3 py-2 border-bottom border-weak shrink-0">
61+
<span className="lumo-troubleshoot-avatar flex items-center justify-center rounded-full bg-weak shrink-0 ratio-square">
62+
<img src={lumoCatIcon} alt="" aria-hidden="true" className="lumo-troubleshoot-avatar-icon" />
63+
</span>
64+
<span className="text-semibold text-ellipsis flex-1">{title}</span>
65+
<Button
66+
icon
67+
shape="ghost"
68+
size="small"
69+
className="shrink-0"
70+
onClick={() => setIsOpen(false)}
71+
title={c('Action').t`Close`}
72+
>
73+
<IcCross />
74+
</Button>
75+
</div>
76+
<iframe
77+
title={title}
78+
src={src}
79+
className="w-full flex-1 border-none"
80+
allow="clipboard-write"
81+
/>
82+
</div>
83+
)}
84+
<button
85+
type="button"
86+
className="lumo-troubleshoot-launcher link-focus inline-flex items-center gap-2 rounded-full bg-norm border border-weak shadow-norm color-norm py-1 pl-1 pr-3"
87+
aria-expanded={isOpen}
88+
onClick={() => (isOpen ? setIsOpen(false) : open())}
89+
>
90+
<span className="lumo-troubleshoot-avatar flex items-center justify-center rounded-full bg-weak shrink-0 ratio-square">
91+
<img src={lumoCatIcon} alt="" aria-hidden="true" className="lumo-troubleshoot-avatar-icon" />
92+
</span>
93+
<span className="text-sm text-semibold">{c('Action').t`Get help from ${LUMO_SHORT_APP_NAME}`}</span>
94+
</button>
95+
</div>
96+
);
97+
};
98+
99+
export default TroubleshootWithLumo;

packages/unleash/UnleashFeatureFlags.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ export enum CommonFeatureFlag {
5555
LumoSmoothedRendering = 'LumoSmoothedRendering',
5656
LumoHighLoad = 'LumoHighLoad',
5757
LumoDeactivateGuestModeFrontend = 'LumoDeactivateGuestModeFrontend',
58+
LumoSignInHelp = 'LumoSignInHelp',
5859
AllowGuestInit = 'AllowGuestInit',
5960
NewScheduleOption = 'NewScheduleOption',
6061
PMVC2025 = 'PMVC2025',

0 commit comments

Comments
 (0)