SDA-3901 (Add new implementation for welcome screen) (#1519)

* SDA-3901 - Add new welcome screen

* SDA-3901 - Change to global config

* SDA-3901 - Add locale

* SDA-3901 - Reposition protocol handling

* SDA-3901 - Fix protocol handler

* SDA-3901 - Fix protocol handler

* SDA-3901 - Fix welcome screen load

* SDA-3901 - Fix seamless login url

* SDA-3901 - Validate if pod is configured for SSO
This commit is contained in:
Kiran Niranjan 2022-10-26 08:14:16 +05:30 committed by GitHub
parent fb5a98136f
commit df14fa19d1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 511 additions and 275 deletions

View File

@ -4,6 +4,7 @@
"autoUpdateChannel": "latest",
"isAutoUpdateEnabled": true,
"autoUpdateCheckInterval": "30",
"enableSeamlessLogin": true,
"overrideUserAgent": false,
"minimizeOnClose" : "ENABLED",
"launchOnStartup" : "ENABLED",

22
package-lock.json generated
View File

@ -5013,15 +5013,21 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001265",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/caniuse-lite/-/caniuse-lite-1.0.30001265.tgz",
"integrity": "sha1-BhPJ5ski5CJ5Lm/O/fmjr+7k+MM=",
"version": "1.0.30001420",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/caniuse-lite/-/caniuse-lite-1.0.30001420.tgz",
"integrity": "sha1-9i818FHgttJVMs83Z3bUHkW0fvY=",
"dev": true,
"license": "CC-BY-4.0",
"funding": {
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/browserslist"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/caniuse-lite"
}
],
"license": "CC-BY-4.0"
},
"node_modules/capture-exit": {
"version": "2.0.0",
@ -25181,9 +25187,9 @@
"dev": true
},
"caniuse-lite": {
"version": "1.0.30001265",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/caniuse-lite/-/caniuse-lite-1.0.30001265.tgz",
"integrity": "sha1-BhPJ5ski5CJ5Lm/O/fmjr+7k+MM=",
"version": "1.0.30001420",
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/caniuse-lite/-/caniuse-lite-1.0.30001420.tgz",
"integrity": "sha1-9i818FHgttJVMs83Z3bUHkW0fvY=",
"dev": true
},
"capture-exit": {

View File

@ -264,6 +264,7 @@ export const BrowserWindow = {
export const session = {
defaultSession: {
clearCache: jest.fn(),
cookies: jest.fn(),
},
};

View File

@ -4,47 +4,93 @@ exports[`welcome should render correctly 1`] = `
<div
className="Welcome"
lang="en-US"
style={
Object {
"height": "494px",
}
}
>
<div
className="Welcome-content"
>
<div
className="Welcome-image-container"
>
<img
alt="Symphony Logo"
src="../renderer/assets/symphony-logo-plain.png"
src="../renderer/assets/welcome-symphony-logo.svg"
/>
</div>
<div
className="Welcome-main-container"
className="Welcome-about-symphony-text"
style={
Object {
"marginTop": "8px",
}
}
>
<h3
className="Welcome-name"
<span>
Welcome to the largest global community in financial services with over
</span>
<span
className="Welcome-text-bold"
>
half a million users
</span>
<span>
and more than
</span>
<span
className="Welcome-text-bold"
>
1,000 institutions.
</span>
</div>
<div>
<div
className="Welcome-login-text"
>
<span>
Log in with your pod URL
</span>
</div>
<div
className="Welcome-input-container"
>
<span>
Pod URL
</h3>
<div
className="Welcome-main-container-input-div"
>
<div
className="Welcome-main-container-input-selection"
>
</span>
<div>
<input
className="Welcome-main-container-podurl-box"
data-testid="Welcome-main-container-podurl-box"
onChange={[Function]}
type="url"
value="https://[POD].symphony.com"
/>
</div>
</div>
<label
className="Welcome-message-label"
/>
className="Welcome-input-message"
>
Find your pod URL in your invitation email.
</label>
</div>
</div>
</div>
<button
className="Welcome-continue-button-disabled"
className="Welcome-continue-button"
disabled={true}
onClick={[Function]}
style={Object {}}
>
Continue
log in
</button>
<div
className="Welcome-redirect-info-text-container"
>
<span>
Youll momentarily be redirected to your web browser.
</span>
</div>
</div>
</div>
`;

View File

@ -12,6 +12,14 @@ jest.mock('../src/common/utils', () => {
};
});
jest.mock('../src/app/window-handler', () => {
return {
windowHandler: {
url: '',
},
};
});
jest.mock('../src/common/env', () => {
return {
isWindowsOS: false,
@ -34,6 +42,14 @@ jest.mock('../src/common/logger', () => {
};
});
jest.mock('../src/app/config-handler', () => {
return {
config: {
getUserConfigFields: jest.fn(() => ''),
},
};
});
describe('protocol handler', () => {
let protocolHandlerInstance;

View File

@ -9,6 +9,8 @@ describe('welcome', () => {
url: 'https://my.symphony.com',
message: '',
urlValid: true,
isPodConfigured: false,
isSeamlessLoginEnabled: true,
};
const onLabelEvent = 'on';
const removeListenerLabelEvent = 'removeListener';
@ -94,7 +96,7 @@ describe('welcome', () => {
it('should set pod url', () => {
const spy = jest.spyOn(Welcome.prototype, 'setState');
const setPodUrlSpy = jest.spyOn(Welcome.prototype, 'setPodUrl');
const setPodUrlSpy = jest.spyOn(Welcome.prototype, 'login');
const wrapper = shallow(React.createElement(Welcome));
ipcRenderer.send('welcome', welcomeMock);
@ -104,4 +106,18 @@ describe('welcome', () => {
expect(setPodUrlSpy).toBeCalled();
expect(spy).toBeCalledWith(welcomeMock);
});
it('should not show pod url input field', () => {
const welcomeMock = {
url: 'https://my.symphony.com',
message: '',
urlValid: true,
isPodConfigured: true,
isSeamlessLoginEnabled: true,
};
const wrapper = shallow(React.createElement(Welcome));
ipcRenderer.send('welcome', welcomeMock);
const podUrlBox = `input.Welcome-main-container-podurl-box`;
expect(wrapper.find(podUrlBox).getElements()).toEqual([]);
});
});

View File

@ -58,6 +58,7 @@ export interface IConfig {
installVariant?: string;
bootCount?: number;
startedAfterAutoUpdate?: boolean;
enableSeamlessLogin: boolean;
}
export interface IGlobalConfig {

View File

@ -1,20 +1,23 @@
import {
app,
BrowserWindow,
clipboard,
desktopCapturer,
dialog,
ipcMain,
shell,
systemPreferences,
} from 'electron';
import fetch from 'electron-fetch';
import {
apiCmds,
apiName,
IApiArgs,
IAuthResponse,
INotificationData,
} from '../common/api-interface';
import { i18n, LocaleType } from '../common/i18n';
import { logger } from '../common/logger';
import { whitelistHandler } from '../common/whitelist-handler';
import { activityDetection } from './activity-detection';
import { analytics } from './analytics-handler';
import appStateHandler from './app-state-handler';
@ -63,6 +66,9 @@ const broadcastMessage = (method, data) => {
mainEvents.publish(apiCmds.onSwiftSearchMessage, [method, data]);
};
const getSeamLessLoginUrl = (pod: string) =>
`https://${pod}/login/sso/initsso?RelayState=https://${pod}/client-bff/device-login/index.html?callbackScheme=symphony&action=login`;
const AUTH_STATUS_PATH = '/login/checkauth?type=user';
/**
* Handle API related ipc messages from renderers. Only messages from windows
* we have created are allowed.
@ -350,10 +356,43 @@ ipcMain.on(
mainWebContents.focus();
}
break;
case apiCmds.setPodUrl:
case apiCmds.seamlessLogin:
if (!arg.isPodConfigured) {
await config.updateUserConfig({ url: arg.newPodUrl });
app.relaunch();
app.exit();
}
const { subdomain, domain, tld } = whitelistHandler.parseDomain(
arg.newPodUrl,
);
const loginUrl = getSeamLessLoginUrl(`${subdomain}.${domain}${tld}`);
logger.info(
'main-api-handler:',
'check if sso is enabled for the pod',
loginUrl,
);
const response = await fetch(`${loginUrl}${AUTH_STATUS_PATH}`);
const authResponse = (await response.json()) as IAuthResponse;
logger.info('main-api-handler:', 'check auth response', authResponse);
if (
arg.isSeamlessLoginEnabled &&
authResponse.authenticationType === 'sso'
) {
logger.info(
'main-api-handler:',
'seamless login is enabled - logging in',
loginUrl,
);
await shell.openExternal(loginUrl);
} else {
logger.info(
'main-api-handler:',
'seamless login is not enabled - loading main window with',
arg.newPodUrl,
);
const mainWebContents = windowHandler.getMainWebContents();
if (mainWebContents && !mainWebContents.isDestroyed()) {
mainWebContents.loadURL(arg.newPodUrl);
}
}
break;
case apiCmds.setBroadcastMessage:
if (swiftSearchInstance) {

View File

@ -1,9 +1,11 @@
import { WebContents } from 'electron';
import { session, WebContents } from 'electron';
import { apiName } from '../common/api-interface';
import { isMac } from '../common/env';
import { logger } from '../common/logger';
import { getCommandLineArgs } from '../common/utils';
import { config } from './config-handler';
import { activate } from './window-actions';
import { windowHandler } from './window-handler';
enum protocol {
SymphonyProtocol = 'symphony://',
@ -45,7 +47,10 @@ class ProtocolHandler {
* @param url {String}
* @param isAppRunning {Boolean} - whether the application is running
*/
public sendProtocol(url: string, isAppRunning: boolean = true): void {
public async sendProtocol(
url: string,
isAppRunning: boolean = true,
): Promise<void> {
if (url && url.length > 2083) {
logger.info(
`protocol-handler: protocol handler url length is greater than 2083, not performing any action!`,
@ -55,6 +60,12 @@ class ProtocolHandler {
logger.info(
`protocol handler: processing protocol request for the url ${url}!`,
);
// Handle protocol for Seamless login
if (url?.includes('skey') && url?.includes('anticsrf')) {
await this.handleSeamlessLogin(url);
return;
}
if (!this.preloadWebContents || !isAppRunning) {
logger.info(
`protocol handler: app was started from the protocol request. Caching the URL ${url}!`,
@ -62,6 +73,7 @@ class ProtocolHandler {
this.protocolUri = url;
return;
}
// This is needed for mac OS as it brings pop-outs to foreground
// (if it has been previously focused) instead of main window
if (isMac) {
@ -95,6 +107,38 @@ class ProtocolHandler {
this.sendProtocol(protocolUriFromArgv, isAppAlreadyOpen);
}
}
/**
* Sets session cookies and navigates to the pod url
*/
public async handleSeamlessLogin(protocolUri: string): Promise<void> {
const { url } = config.getUserConfigFields(['url']);
if (protocolUri) {
const urlParams = new URLSearchParams(new URL(protocolUri).search);
const skeyValue = urlParams.get('skey');
const anticsrfValue = urlParams.get('anticsrf');
if (skeyValue) {
await session.defaultSession.cookies.set({
url,
name: 'skey',
value: skeyValue,
});
}
if (anticsrfValue) {
await session.defaultSession.cookies.set({
url,
name: 'anti-csrf-cookie',
value: anticsrfValue,
});
}
logger.info('protocol-handler: cookies has been set');
const mainWebContents = windowHandler.getMainWebContents();
if (mainWebContents && !mainWebContents?.isDestroyed() && url) {
logger.info('protocol-handler: redirecting main webContents', url);
mainWebContents?.loadURL(url);
}
}
}
}
const protocolHandler = new ProtocolHandler();

View File

@ -1,7 +1,6 @@
import { ExecException, execFile } from 'child_process';
import {
app,
BrowserView,
BrowserWindow,
BrowserWindowConstructorOptions,
crashReporter,
@ -102,8 +101,6 @@ export interface ICustomBrowserView extends Electron.BrowserView {
// Default window width & height
export const DEFAULT_WIDTH: number = 900;
export const DEFAULT_HEIGHT: number = 900;
export const DEFAULT_WELCOME_SCREEN_WIDTH: number = 542;
export const DEFAULT_WELCOME_SCREEN_HEIGHT: number = 333;
export const TITLE_BAR_HEIGHT: number = 32;
export const IS_SAND_BOXED: boolean = true;
export const IS_NODE_INTEGRATION_ENABLED: boolean = false;
@ -143,7 +140,6 @@ export class WindowHandler {
public isLoggedIn: boolean = false;
public isAutoUpdating: boolean = false;
public screenShareIndicatorFrameUtil: string;
private defaultPodUrl: string = 'https://[POD].symphony.com';
private contextIsolation: boolean = true;
private backgroundThrottling: boolean = false;
private windowOpts: ICustomBrowserWindowConstructorOpts = {} as ICustomBrowserWindowConstructorOpts;
@ -155,7 +151,6 @@ export class WindowHandler {
private loadFailError: string | undefined;
private mainWindow: ICustomBrowserWindow | null = null;
private aboutAppWindow: Electron.BrowserWindow | null = null;
private welcomeScreenWindow: Electron.BrowserWindow | null = null;
private screenPickerWindow: Electron.BrowserWindow | null = null;
private screenPickerPlaceholderWindow: Electron.BrowserWindow | null = null;
private screenSharingIndicatorWindow: Electron.BrowserWindow | null = null;
@ -165,6 +160,9 @@ export class WindowHandler {
private snippingToolWindow: Electron.BrowserWindow | null = null;
private finishedLoading: boolean = false;
private readonly opts: Electron.BrowserViewConstructorOptions | undefined;
private isPodConfigured: boolean = false;
private shouldShowWelcomeScreen: boolean = true;
private didShowWelcomeScreen: boolean = false;
constructor(opts?: Electron.BrowserViewConstructorOptions) {
this.opts = opts;
@ -208,6 +206,7 @@ export class WindowHandler {
'customFlags',
'clientSwitch',
'enableRendererLogs',
'enableSeamlessLogin',
]);
logger.info(
`window-handler: main windows initialized with following config data`,
@ -233,6 +232,13 @@ export class WindowHandler {
this.isCustomTitleBar =
isWindowsOS &&
this.config.isCustomTitleBar === CloudConfigDataTypes.ENABLED;
// Get url to load from cmd line or from global config file
const urlFromCmd = getCommandLineArgs(process.argv, '--url=', false);
this.isPodConfigured = !config.isFirstTimeLaunch();
this.didShowWelcomeScreen = false;
this.shouldShowWelcomeScreen =
config.isFirstTimeLaunch() || this.config.enableSeamlessLogin;
this.windowOpts = {
...this.getWindowOpts(
{
@ -280,20 +286,6 @@ export class WindowHandler {
);
logger.info(`window-handler: setting url ${this.url} from config file!`);
// Get url to load from cmd line or from global config file
const urlFromCmd = getCommandLineArgs(process.argv, '--url=', false);
// Displays welcome screen instead of starting the main application
if (
config.isFirstTimeLaunch() &&
this.globalConfig.url.indexOf('https://my.symphony.com') >= 0 &&
urlFromCmd === null
) {
this.url = this.defaultPodUrl;
this.showWelcomeScreen();
return;
}
// set window opts with additional config
this.mainWindow = new BrowserWindow({
...this.windowOpts,
@ -377,6 +369,20 @@ export class WindowHandler {
logger.info(`Loading main window with url ${this.url}`);
const userAgent = this.getUserAgent(this.mainWindow.webContents);
// Displays welcome screen instead of starting the main application
if (this.shouldShowWelcomeScreen) {
this.url = format({
pathname: require.resolve('../renderer/react-window.html'),
protocol: 'file',
query: {
componentName: 'welcome',
locale: i18n.getLocale(),
title: i18n.t('WelcomeText', 'Welcome')(),
},
slashes: true,
});
}
if (
this.config.isCustomTitleBar === CloudConfigDataTypes.ENABLED &&
isWindowsOS &&
@ -508,9 +514,34 @@ export class WindowHandler {
await this.mainWebContents?.loadURL(url, { userAgent });
return;
}
logger.info('window-handler: did-finish-load, url: ' + this.url);
if (this.mainWebContents && !this.mainWebContents.isDestroyed()) {
// Load welcome screen
if (this.shouldShowWelcomeScreen && !this.didShowWelcomeScreen) {
const userConfigUrl =
this.userConfig.url &&
this.userConfig.url.indexOf('/login/sso/initsso') > -1
? this.userConfig.url.slice(
0,
this.userConfig.url.indexOf('/login/sso/initsso'),
)
: this.userConfig.url;
this.mainWebContents.send('page-load-welcome', {
locale: i18n.getLocale(),
resources: i18n.loadedResources,
});
this.mainWebContents.send('welcome', {
url: userConfigUrl,
message: '',
urlValid: !!userConfigUrl,
isPodConfigured: this.isPodConfigured,
isSeamlessLoginEnabled: this.config.enableSeamlessLogin,
});
this.didShowWelcomeScreen = true;
}
// Injects custom title bar and snack bar css into the webContents
await injectStyles(this.mainWebContents, this.isCustomTitleBar);
this.mainWebContents.send('page-load', {
@ -719,120 +750,6 @@ export class WindowHandler {
return this.mainWindow;
}
/**
* Handles the use case of showing
* welcome screen for first time installs
*/
public showWelcomeScreen() {
if (!this.url) {
return;
}
const opts: ICustomBrowserWindowConstructorOpts = this.getWindowOpts(
{
width: DEFAULT_WELCOME_SCREEN_WIDTH,
height: DEFAULT_WELCOME_SCREEN_HEIGHT,
frame: !this.isCustomTitleBar,
alwaysOnTop: isMac,
resizable: false,
minimizable: true,
fullscreenable: false,
},
{
devTools: isDevEnv,
},
);
this.welcomeScreenWindow = createComponentWindow('welcome', opts);
(this.welcomeScreenWindow as ICustomBrowserWindow).winName =
apiName.welcomeScreenName;
if (
this.config.isCustomTitleBar === CloudConfigDataTypes.ENABLED &&
isWindowsOS
) {
const titleBarView = new BrowserView({
webPreferences: {
sandbox: IS_SAND_BOXED,
nodeIntegration: IS_NODE_INTEGRATION_ENABLED,
preload: path.join(__dirname, '../renderer/_preload-component.js'),
devTools: isDevEnv,
disableBlinkFeatures: AUX_CLICK,
},
}) as ICustomBrowserView;
const titleBarWindowUrl = format({
pathname: require.resolve('../renderer/react-window.html'),
protocol: 'file',
query: {
componentName: 'title-bar',
locale: i18n.getLocale(),
title: i18n.t('WelcomeText', 'Welcome')(),
},
slashes: true,
});
titleBarView.webContents.once('did-finish-load', async () => {
if (!titleBarView || titleBarView.webContents.isDestroyed()) {
return;
}
titleBarView?.webContents.send('page-load', {
isWindowsOS,
locale: i18n.getLocale(),
resource: i18n.loadedResources,
isMainWindow: true,
});
// disables action buttons in title bar
titleBarView.webContents.send('disable-action-button');
});
titleBarView.webContents.loadURL(titleBarWindowUrl);
titleBarView.setBounds({
x: 0,
y: 0,
height: TITLE_BAR_HEIGHT,
width: DEFAULT_WELCOME_SCREEN_WIDTH,
});
this.welcomeScreenWindow.setBrowserView(titleBarView);
}
this.welcomeScreenWindow.webContents.on('did-finish-load', () => {
if (!this.welcomeScreenWindow || this.welcomeScreenWindow.isDestroyed()) {
return;
}
logger.info(`finished loading welcome screen.`);
const ssoValue = !!(
this.userConfig.url &&
this.userConfig.url.indexOf('/login/sso/initsso') > -1
);
this.welcomeScreenWindow.webContents.send('page-load-welcome', {
locale: i18n.getLocale(),
resource: i18n.loadedResources,
});
const userConfigUrl =
this.userConfig.url &&
this.userConfig.url.indexOf('/login/sso/initsso') > -1
? this.userConfig.url.slice(
0,
this.userConfig.url.indexOf('/login/sso/initsso'),
)
: this.userConfig.url;
this.welcomeScreenWindow.webContents.send('welcome', {
url: userConfigUrl || this.startUrl,
message: '',
urlValid: !!userConfigUrl,
sso: ssoValue,
});
this.appMenu = new AppMenu();
this.addWindow(opts.winKey, this.welcomeScreenWindow);
this.mainWindow = this.welcomeScreenWindow as ICustomBrowserWindow;
});
this.welcomeScreenWindow.once('closed', () => {
this.removeWindow(opts.winKey);
this.welcomeScreenWindow = null;
});
}
/**
* Gets the main window
*/
@ -840,13 +757,6 @@ export class WindowHandler {
return this.mainWindow;
}
/**
* Gets the welcome screen window
*/
public getWelcomeScreenWindow(): BrowserWindow | null {
return this.welcomeScreenWindow;
}
/**
* Gets the main browser webContents
*/

View File

@ -384,8 +384,7 @@ export const updateLocale = async (locale: LocaleType): Promise<void> => {
* Displays a popup menu
*/
export const showPopupMenu = (opts: Electron.PopupOptions): void => {
const browserWindow =
windowHandler.getMainWindow() || windowHandler.getWelcomeScreenWindow();
const browserWindow = windowHandler.getMainWindow();
if (
browserWindow &&
windowExists(browserWindow) &&

View File

@ -57,7 +57,6 @@ export enum apiCmds {
isAeroGlassEnabled = 'is-aero-glass-enabled',
showScreenSharePermissionDialog = 'show-screen-share-permission-dialog',
getMediaAccessStatus = 'get-media-access-status',
setPodUrl = 'set-pod-url',
setBroadcastMessage = 'set-broadcast-message',
handleSwiftSearchMessageEvents = 'handle-shift-search-message-events',
onSwiftSearchMessage = 'on-shift-search-message',
@ -71,6 +70,7 @@ export enum apiCmds {
updateAndRestart = 'update-and-restart',
downloadUpdate = 'download-update',
checkForUpdates = 'check-for-updates',
seamlessLogin = 'seamless-login',
}
export enum apiName {
@ -118,6 +118,9 @@ export interface IApiArgs {
requestId: number;
mediaStatus: IMediaPermission;
newPodUrl: string;
startUrl: string;
isPodConfigured: boolean;
isSeamlessLoginEnabled: boolean;
swiftSearchData: any;
types: string[];
thumbnailSize: Size;
@ -282,3 +285,13 @@ export interface ICloud9Pipe {
write(data: Uint8Array): void;
close(): void;
}
export type AuthType = 'password' | 'sso';
export interface IAuthResponse {
status: string;
podVersion: string;
authenticationType: AuthType;
ssoDisabledForMobile: boolean;
keymanagerUrl: string;
}

View File

@ -214,6 +214,13 @@
"Welcome": {
"Continue": "Continue",
"Enable Single Sign On": "Enable Single Sign On",
"Find your pod URL in your invitation email.": "Find your pod URL in your invitation email.",
"Welcome to the largest global community in financial services with over": "Welcome to the largest global community in financial services with over",
" half a million users": " half a million users",
" 1,000 institutions.": " 1,000 institutions.",
"Log in with your pod URL": "Log in with your pod URL",
"Youll momentarily be redirected to your web browser.": "Youll momentarily be redirected to your web browser.",
"log in": "log in",
"Please enter a valid url": "Please enter a valid url",
"Pod URL": "Pod URL",
"SSO": "SSO",

View File

@ -214,6 +214,13 @@
"Welcome": {
"Continue": "Continue",
"Enable Single Sign On": "Enable Single Sign On",
"Find your pod URL in your invitation email.": "Find your pod URL in your invitation email.",
"Welcome to the largest global community in financial services with over": "Welcome to the largest global community in financial services with over",
" half a million users": " half a million users",
" 1,000 institutions.": " 1,000 institutions.",
"Log in with your pod URL": "Log in with your pod URL",
"Youll momentarily be redirected to your web browser.": "Youll momentarily be redirected to your web browser.",
"log in": "log in",
"Please enter a valid url": "Please enter a valid url",
"Pod URL": "Pod URL",
"SSO": "SSO",

View File

@ -0,0 +1,11 @@
<svg width="296" height="70" viewBox="0 0 296 70" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M44.0497 27.1163V18.546C44.0497 16.7907 43.1018 15.1409 41.5765 14.2407C39.2929 12.8926 34.3464 10.6321 26.8796 10.6321C19.4127 10.6321 14.4663 12.8926 12.1826 14.2407C10.6573 15.1409 9.70941 16.7907 9.70941 18.546V31.4166L36.8955 39.3004V45.034C36.8955 45.8102 36.409 46.3542 35.582 46.7584L26.8796 51.126L18.1321 46.7362C17.3501 46.3542 16.8636 45.8102 16.8636 45.034V40.7338L9.70941 38.5836V45.034C9.70941 48.5753 11.7369 51.5883 14.9563 53.1579L26.8796 59.3681L38.7585 53.1801C42.0222 51.5883 44.0497 48.5753 44.0497 45.034V34.2834L16.8636 26.3996V19.8554C18.806 18.9165 22.1177 17.7991 26.8796 17.7991C31.6414 17.7991 34.9531 18.9165 36.8955 19.8554V24.9662L44.0497 27.1163Z" fill="#008EFF"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M79.6153 47.1841C75.3165 47.1841 71.6321 45.7833 68.3233 43.6982L70.3703 40.5877C73.6791 42.7072 76.7152 43.7326 80.1269 43.7326C84.1189 43.7326 85.9951 42.1941 85.9951 40.3832C85.9951 35.1886 69.1076 38.2981 69.1076 30.0275C69.1076 25.9949 73.1336 22.8167 79.6153 22.8167C83.9821 22.8167 87.7685 24.4574 89.9863 26.6782L87.4617 29.2764C85.5515 27.6013 82.6174 26.2682 79.5474 26.2682C75.7601 26.2682 73.2016 27.6357 73.2016 29.5832C73.2016 34.4022 90.1231 31.8393 90.1231 39.9733C90.1231 44.2792 85.7903 47.1841 79.6153 47.1841Z" fill="#F8F8F8"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M106.67 37.2388V46.3637H102.576V37.2044L92.3418 23.6025H97.1172L104.794 34.1283L112.061 23.6025H116.768L106.67 37.2388Z" fill="#F8F8F8"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M145.324 46.3637V28.5238L144.198 30.9501L136.146 46.3637H133.69L125.742 30.9501L124.616 28.5238V46.3637H120.692V23.6025H126.389L134.031 38.913L135.089 41.237L136.146 38.913L143.686 23.6025H149.315V46.3637H145.324Z" fill="#F8F8F8"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M171.582 30.3695C171.582 28.4899 170.32 27.089 167.216 27.089H159.676V34.4365H167.386C170.49 34.4365 171.582 32.9324 171.582 31.0871V30.3695ZM166.772 37.7859H159.676V46.3642H155.582V23.603H166.772C172.981 23.603 175.71 26.4048 175.71 30.3695V30.9849C175.71 34.9487 172.981 37.7859 166.772 37.7859Z" fill="#F8F8F8"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M198.016 46.3637V36.5555H184.335V46.3637H180.242V23.6025H184.335V33.2406H198.016V23.6025H202.11V46.3637H198.016Z" fill="#F8F8F8"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M226.83 33.5818C226.83 29.6858 224.135 26.3365 219.155 26.3365C214.173 26.3365 211.512 29.6858 211.512 33.5818V36.3501C211.512 40.246 214.173 43.6642 219.155 43.6642C224.135 43.6642 226.83 40.246 226.83 36.3501V33.5818ZM219.154 47.1837C211.853 47.1837 207.383 41.9891 207.383 36.419V33.5818C207.383 27.9085 211.853 22.8162 219.154 22.8162C226.488 22.8162 230.957 27.9085 230.957 33.5818V36.419C230.957 41.9891 226.488 47.1837 219.154 47.1837Z" fill="#F8F8F8"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M254.595 46.3637L241.87 31.9754L240.232 29.8569V46.3637H236.172V23.6025H239.959L252.275 38.0252L253.946 40.1438V23.6025H257.972V46.3637H254.595Z" fill="#F8F8F8"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M276.192 37.2388V46.3637H272.098V37.2044L261.864 23.6025H266.64L274.316 34.1283L281.583 23.6025H286.29L276.192 37.2388Z" fill="#F8F8F8"/>
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@ -7,21 +7,29 @@ interface IState {
url: string;
message: string;
urlValid: boolean;
isPodConfigured: boolean;
isSeamlessLoginEnabled: boolean;
}
const WELCOME_NAMESPACE = 'Welcome';
const DEFAULT_MESSAGE = 'Find your pod URL in your invitation email.';
const HEIGHT_WITH_POD_INPUT = '494px';
const HEIGHT_WITHOUT_POD_INPUT = '376px';
const DEFAULT_POD_URL = 'https://[POD].symphony.com';
export default class Welcome extends React.Component<{}, IState> {
private readonly eventHandlers = {
onSetPodUrl: () => this.setPodUrl(),
onLogin: () => this.login(),
};
constructor(props) {
super(props);
this.state = {
url: 'https://[POD].symphony.com',
url: DEFAULT_POD_URL,
message: '',
urlValid: false,
isPodConfigured: false,
isSeamlessLoginEnabled: true,
};
this.updateState = this.updateState.bind(this);
}
@ -30,41 +38,84 @@ export default class Welcome extends React.Component<{}, IState> {
* Render the component
*/
public render(): JSX.Element {
const { url, message, urlValid } = this.state;
const { url, message, urlValid, isPodConfigured } = this.state;
return (
<div className='Welcome' lang={i18n.getLocale()}>
<div
className='Welcome'
style={{
height: isPodConfigured
? HEIGHT_WITHOUT_POD_INPUT
: HEIGHT_WITH_POD_INPUT,
}}
lang={i18n.getLocale()}
>
<div className='Welcome-content'>
<div className='Welcome-image-container'>
<img
src='../renderer/assets/symphony-logo-plain.png'
src='../renderer/assets/welcome-symphony-logo.svg'
alt={i18n.t('Symphony Logo', WELCOME_NAMESPACE)()}
/>
</div>
<div className='Welcome-main-container'>
<h3 className='Welcome-name'>
{i18n.t('Pod URL', WELCOME_NAMESPACE)()}
</h3>
<div className='Welcome-main-container-input-div'>
<div className='Welcome-main-container-input-selection'>
<div
className='Welcome-about-symphony-text'
style={{ marginTop: isPodConfigured ? '35px' : '8px' }}
>
<span>
{i18n.t(
'Welcome to the largest global community in financial services with over',
WELCOME_NAMESPACE,
)()}
</span>
<span className='Welcome-text-bold'>
{i18n.t(' half a million users', WELCOME_NAMESPACE)()}
</span>
<span>{i18n.t(' and more than', WELCOME_NAMESPACE)()}</span>
<span className='Welcome-text-bold'>
{i18n.t(' 1,000 institutions.', WELCOME_NAMESPACE)()}
</span>
</div>
{!isPodConfigured && (
<div>
<div className='Welcome-login-text'>
<span>
{i18n.t('Log in with your pod URL', WELCOME_NAMESPACE)()}
</span>
</div>
<div className='Welcome-input-container'>
<span>{i18n.t('Pod URL', WELCOME_NAMESPACE)()}</span>
<div>
<input
data-testid={'Welcome-main-container-podurl-box'}
className='Welcome-main-container-podurl-box'
type='url'
value={url}
onChange={this.updatePodUrl.bind(this)}
></input>
/>
<label className='Welcome-input-message'>
{message
? message
: i18n.t(DEFAULT_MESSAGE, WELCOME_NAMESPACE)()}
</label>
</div>
</div>
<label className='Welcome-message-label'>{message}</label>
</div>
)}
<button
className={
!urlValid
? 'Welcome-continue-button-disabled'
: 'Welcome-continue-button'
}
disabled={!urlValid}
onClick={this.eventHandlers.onSetPodUrl}
className='Welcome-continue-button'
disabled={!isPodConfigured && !urlValid}
onClick={this.eventHandlers.onLogin}
style={isPodConfigured ? { marginTop: '40px' } : {}}
>
{i18n.t('Continue', WELCOME_NAMESPACE)()}
{i18n.t('log in', WELCOME_NAMESPACE)()}
</button>
<div className='Welcome-redirect-info-text-container'>
<span>
{i18n.t(
'Youll momentarily be redirected to your web browser.',
WELCOME_NAMESPACE,
)()}
</span>
</div>
</div>
</div>
);
@ -85,13 +136,15 @@ export default class Welcome extends React.Component<{}, IState> {
}
/**
* Set pod url and pass it to the main process
* Handle seamless login
*/
public setPodUrl(): void {
const { url } = this.state;
public login(): void {
const { url, isPodConfigured, isSeamlessLoginEnabled } = this.state;
ipcRenderer.send(apiName.symphonyApi, {
cmd: apiCmds.setPodUrl,
cmd: apiCmds.seamlessLogin,
newPodUrl: url,
isPodConfigured,
isSeamlessLoginEnabled,
});
}

View File

@ -11,7 +11,6 @@ import ScreenPicker from './components/screen-picker';
import ScreenSharingFrame from './components/screen-sharing-frame';
import ScreenSharingIndicator from './components/screen-sharing-indicator';
import SnippingTool from './components/snipping-tool';
import Welcome from './components/welcome';
import WindowsTitleBar from './components/windows-title-bar';
const enum components {
@ -87,11 +86,6 @@ const load = () => {
loadStyle(components.notificationSettings);
component = NotificationSettings;
break;
case components.welcome:
document.title = i18n.t('WelcomeText', 'Welcome')();
loadStyle(components.welcome);
component = Welcome;
break;
case components.titleBar:
if (title) {
document.title = title;

View File

@ -9,6 +9,7 @@ import DownloadManager from './components/download-manager';
import MessageBanner from './components/message-banner';
import NetworkError from './components/network-error';
import SnackBar from './components/snack-bar';
import Welcome from './components/welcome';
import { SSFApi } from './ssf-api';
interface ISSFWindow extends Window {
@ -183,3 +184,16 @@ ipcRenderer.on('exit-html-fullscreen', async () => {
await document.exitFullscreen();
}
});
ipcRenderer.on('page-load-welcome', (_event, { locale, resources }) => {
i18n.setResource(locale, resources);
document.title = i18n.t('WelcomeText', 'Welcome')();
const styles = document.createElement('link');
styles.rel = 'stylesheet';
styles.type = 'text/css';
styles.href = `./styles/welcome.css`;
document.getElementsByTagName('head')[0].appendChild(styles);
const component = Welcome;
const element = React.createElement(component);
ReactDOM.render(element, document.getElementById('Root'));
});

View File

@ -16,9 +16,9 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta charset="UTF-8" />
</head>
<body>
<div id="Root"></div>
<div id="Root" class="components-window-root"></div>
</body>
</html>

View File

@ -12,3 +12,6 @@
@graphite-20: #cdcfd4;
@graphite-05: #f1f1f3;
@graphite-80: #27292c;
@graphite-30: #b0b3ba;
@graphite-40: #8f959e;
@graphite-90: #141618;

View File

@ -9,12 +9,19 @@
}
body {
background-color: white;
background-color: @graphite-90;
margin: 0;
height: 100%;
width: 100%;
}
.components-window-root {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
.Welcome:lang(ja-JP) {
font-family: @font-family-ja;
@ -34,18 +41,64 @@ body {
}
.Welcome {
width: 360px;
height: 494px;
display: flex;
flex-direction: column;
padding: 10px;
font-family: @font-family;
background-color: @graphite-80;
box-shadow: 0 24px 48px rgba(9, 10, 11, 0.64),
0 4px 8px rgba(15, 27, 36, 0.16);
border-radius: 8px;
&-content {
width: 300px;
padding: 48px 32px 24px;
}
&-text-container {
font-size: 12px;
font-weight: 700;
line-height: 20px;
color: @graphite-05;
}
&-image-container {
text-align: center;
margin: 50px 0 10px;
}
&-image-container img {
width: 200px;
&-about-symphony-text {
span {
color: @white;
font-size: 14px;
line-height: 20px;
}
}
&-login-text {
margin-top: 32px;
span {
color: @white;
font-size: 18px;
line-height: 20px;
font-weight: 600;
}
}
&-text-bold {
font-weight: 700;
}
&-input-container {
margin-top: 24px;
span {
font-weight: 400;
font-size: 14px;
line-height: 20px;
color: @graphite-20;
}
}
&-header-content {
@ -65,17 +118,26 @@ body {
width: 100%;
}
&-main-container-input-selection {
width: 100%;
}
&-main-container-podurl-box {
float: left;
width: 100%;
border-radius: 4px;
border: 1px solid #000;
padding: 5px;
max-width: -webkit-fill-available;
height: 26px;
background-color: @graphite-90;
border: 2px solid @graphite-40;
padding: 6px 12px;
border-radius: 4px;
color: white;
font-size: 14px;
margin-top: 4px;
}
&-input-message {
font-weight: 400;
font-size: 12px;
line-height: 16px;
margin-top: 4px;
color: @graphite-20;
}
&-main-container-sso-box {
@ -140,6 +202,8 @@ body {
}
&-continue-button {
width: 300px;
height: 40px;
box-shadow: none;
border: none;
border-radius: 20px;
@ -149,40 +213,31 @@ body {
display: inline-block;
text-decoration: none;
line-height: 12px;
background-color: #3da2fd;
background-color: @electricity-ui-50;
color: #ffffff;
cursor: pointer;
text-transform: uppercase;
float: right;
margin: 24px 0px 4px 0;
margin: 24px 0 4px 0;
&:focus {
box-shadow: 0 0 10px rgba(61, 162, 253, 1);
outline: none;
}
&:disabled {
background-color: @graphite-20;
}
}
&-continue-button-disabled {
box-shadow: none;
border: none;
border-radius: 20px;
font-size: 0.8rem;
&-redirect-info-text-container {
margin-top: 32px;
span {
font-weight: 400;
font-size: 12px;
line-height: 16px;
text-align: center;
padding: 10px 32px;
display: inline-block;
text-decoration: none;
line-height: 12px;
color: #ffffff;
text-transform: uppercase;
float: right;
margin: 24px 0px 4px 0;
cursor: not-allowed;
background-color: #cccccc;
pointer-events: none;
&:focus {
box-shadow: 0 0 10px rgba(61, 162, 253, 1);
outline: none;
color: @graphite-30;
}
}
}