1
0

Initial commit

This commit is contained in:
2022-05-11 19:49:34 +00:00
committed by GitHub
commit 05a8cd39ed
35 changed files with 10972 additions and 0 deletions
+24
View File
@@ -0,0 +1,24 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.eslintcache
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) Microsoft Corporation. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE
+60
View File
@@ -0,0 +1,60 @@
# Getting Started with Create React App and Fluent UI
This is a [Create React App](https://github.com/facebook/create-react-app) based repo that comes with Fluent UI pre-installed!
## Available Scripts
In the project directory, you can run:
### `yarn start`
Runs the app in the development mode.<br>
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
The page will reload if you make edits.<br>
You will also see any lint errors in the console.
### `yarn test`
Launches the test runner in the interactive watch mode.<br>
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
### `yarn build`
Builds the app for production to the `build` folder.<br>
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.<br>
Your app is ready to be deployed!
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
### `yarn eject`
**Note: this is a one-way operation. Once you `eject`, you cant go back!**
If you arent satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point youre on your own.
You dont have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldnt feel obligated to use this feature. However we understand that this tool wouldnt be useful if you couldnt customize it when you are ready for it.
## Learn More
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
To learn React, check out the [React documentation](https://reactjs.org/).
# Contributing
This project welcomes contributions and suggestions. Most contributions require you to agree to a
Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
the rights to use your contribution. For details, visit https://cla.microsoft.com.
When you submit a pull request, a CLA-bot will automatically determine whether you need to provide
a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions
provided by the bot. You will only need to do this once across all repos using our CLA.
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
+47
View File
@@ -0,0 +1,47 @@
{
"name": "easylogon-demo",
"version": "0.1.0",
"private": true,
"dependencies": {
"@fluentui/react": "^8.0.0",
"@testing-library/jest-dom": "^5.16.1",
"@testing-library/react": "^12.1.2",
"@testing-library/user-event": "^13.5.0",
"@types/jest": "^26.0.15",
"@types/node": "^14.0.0",
"@types/react": "^17.0.0",
"@types/react-dom": "^17.0.0",
"react": "^17.0.0",
"react-dom": "^17.0.0",
"react-localization": "^1.0.19",
"react-scripts": "5.0.1",
"recharts": "^2.1.9",
"sass": "^1.51.0",
"typescript": "^4.1.2",
"web-vitals": "^1.0.1"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

+13
View File
@@ -0,0 +1,13 @@
<svg width="96" height="96" viewBox="0 0 96 96" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M36.114 6.85713H9.59993C8.08511 6.85713 6.8571 8.08515 6.8571 9.59998V36.1142C6.8571 37.6291 8.08511 38.8571 9.59993 38.8571H36.114C37.6289 38.8571 38.8569 37.6291 38.8569 36.1142V9.59998C38.8569 8.08515 37.6289 6.85713 36.114 6.85713ZM9.59993 0C4.29804 0 0 4.29806 0 9.59998V36.1142C0 41.4161 4.29804 45.7142 9.59993 45.7142H36.114C41.4159 45.7142 45.714 41.4161 45.714 36.1142V9.59998C45.714 4.29806 41.4159 0 36.114 0H9.59993Z" fill="#0078D4"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M36.114 57.1429H9.59993C8.08511 57.1429 6.8571 58.3709 6.8571 59.8857V86.4C6.8571 87.9148 8.08511 89.1428 9.59993 89.1428H36.114C37.6289 89.1428 38.8569 87.9148 38.8569 86.4V59.8857C38.8569 58.3709 37.6289 57.1429 36.114 57.1429ZM9.59993 50.2858C4.29804 50.2858 0 54.5838 0 59.8857V86.4C0 91.7019 4.29804 96 9.59993 96H36.114C41.4159 96 45.714 91.7019 45.714 86.4V59.8857C45.714 54.5838 41.4159 50.2858 36.114 50.2858H9.59993Z" fill="#0078D4"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M86.3989 6.85713H59.8848C58.37 6.85713 57.142 8.08515 57.142 9.59998V36.1142C57.142 37.6291 58.37 38.8571 59.8848 38.8571H86.3989C87.9138 38.8571 89.1418 37.6291 89.1418 36.1142V9.59998C89.1418 8.08515 87.9138 6.85713 86.3989 6.85713ZM59.8848 0C54.5829 0 50.2849 4.29806 50.2849 9.59998V36.1142C50.2849 41.4161 54.5829 45.7142 59.8848 45.7142H86.3989C91.7008 45.7142 95.9989 41.4161 95.9989 36.1142V9.59998C95.9989 4.29806 91.7008 0 86.3989 0H59.8848Z" fill="#0078D4"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M19.2105 19.2105V26.7895H26.7895V19.2105H19.2105ZM17.3158 14C15.4845 14 14 15.4845 14 17.3158V28.6842C14 30.5155 15.4845 32 17.3158 32H28.6842C30.5155 32 32 30.5155 32 28.6842V17.3158C32 15.4845 30.5155 14 28.6842 14H17.3158Z" fill="#0078D4"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M55.3134 84.9437V90.9712H61.3409V84.9437H55.3134ZM54.8563 79.9152C52.3316 79.9152 50.2849 81.9619 50.2849 84.4866V91.4284C50.2849 93.9531 52.3316 95.9998 54.8563 95.9998H61.7981C64.3228 95.9998 66.3695 93.9531 66.3695 91.4284V84.4866C66.3695 81.9619 64.3228 79.9152 61.7981 79.9152H54.8563Z" fill="#0078D4"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M70.1286 70.129V76.1565H76.1561V70.129H70.1286ZM69.6715 65.1005C67.1468 65.1005 65.1001 67.1472 65.1001 69.6719V76.6137C65.1001 79.1384 67.1468 81.1851 69.6715 81.1851H76.6132C79.138 81.1851 81.1846 79.1384 81.1846 76.6137V69.6719C81.1846 67.1472 79.138 65.1005 76.6132 65.1005H69.6715Z" fill="#0078D4"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M55.3134 55.3143V61.3418H61.3409V55.3143H55.3134ZM54.8563 50.2858C52.3316 50.2858 50.2849 52.3325 50.2849 54.8572V61.799C50.2849 64.3237 52.3316 66.3704 54.8563 66.3704H61.7981C64.3228 66.3704 66.3695 64.3237 66.3695 61.799V54.8572C66.3695 52.3325 64.3228 50.2858 61.7981 50.2858H54.8563Z" fill="#0078D4"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M84.9441 55.3143V61.3418H90.9715V55.3143H84.9441ZM84.4869 50.2858C81.9622 50.2858 79.9155 52.3325 79.9155 54.8572V61.799C79.9155 64.3237 81.9622 66.3704 84.4869 66.3704H91.4287C93.9534 66.3704 96.0001 64.3237 96.0001 61.799V54.8572C96.0001 52.3325 93.9534 50.2858 91.4287 50.2858H84.4869Z" fill="#0078D4"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M84.9441 84.9437V90.9712H90.9715V84.9437H84.9441ZM84.4869 79.9152C81.9622 79.9152 79.9155 81.9619 79.9155 84.4866V91.4284C79.9155 93.9531 81.9622 95.9998 84.4869 95.9998H91.4287C93.9534 95.9998 96.0001 93.9531 96.0001 91.4284V84.4866C96.0001 81.9619 93.9534 79.9152 91.4287 79.9152H84.4869Z" fill="#0078D4"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M19.2105 69.2105V76.7895H26.7895V69.2105H19.2105ZM17.3158 64C15.4845 64 14 65.4845 14 67.3158V78.6842C14 80.5155 15.4845 82 17.3158 82H28.6842C30.5155 82 32 80.5155 32 78.6842V67.3158C32 65.4845 30.5155 64 28.6842 64H17.3158Z" fill="#0078D4"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M70.2105 19.2105V26.7895H77.7895V19.2105H70.2105ZM68.3158 14C66.4845 14 65 15.4845 65 17.3158V28.6842C65 30.5155 66.4845 32 68.3158 32H79.6842C81.5155 32 83 30.5155 83 28.6842V17.3158C83 15.4845 81.5155 14 79.6842 14H68.3158Z" fill="#0078D4"/>
</svg>

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

+72
View File
@@ -0,0 +1,72 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.svg" />
<link rel="mask-icon" href="%PUBLIC_URL%/favicon.svg" color="#0078d4">
<link rel="apple-touch-icon" href="%PUBLIC_URL%/apple-touch-icon.png">
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#ffffff" />
<meta name="description" content="Try out our QR authentication system!" />
<meta name="copyright" content="©2022 FoxDev Studio" />
<meta name="lanugage" content="en_us" />
<meta name="author" content="Eugene Fox, support@foxdev.studio" />
<meta property="og:title" content="EasyLogon interactive demo" />
<meta property="og:description" content="Try out our QR authentication system!" />
<meta property="og:type" content="website" />
<meta property="og:url" content="%PUBLIC_URL%" />
<meta property="og:image" content="%PUBLIC_URL%/banner.png" />
<meta property="og:image:type" content="image/png" />
<meta property="og:image:width" content="1024" />
<meta property="og:image:height" content="512" />
<meta property="og:image:alt"
content="QR-code-like logo with a lock and a fox-like logo inside it. Title: EasyLogon. Subtitle: Forget about passwords. This time for real" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<script>
if("serviceWorker" in navigator)
{
navigator.serviceWorker
.register("/sw.js");
}
</script>
<title>Interactive demo | EasyLogon</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>
+38
View File
@@ -0,0 +1,38 @@
{
"short_name": "EasyLogon demo",
"name": "EasyLogon Interactive demo",
"description": "Log in on any device with a few clicks",
"lang": "en",
"dir": "auto",
"categories": [ "productivity", "security", "utilities", "demo" ],
"icons":
[
{
"src": "favicon.svg",
"type": "image/svg+xml",
"purpose": "monochrome",
"sizes": "150x150"
},
{
"src": "favicon.svg",
"type": "image/svg+xml",
"purpose": "maskable",
"sizes": "150x150"
},
{
"src": "apple-touch-icon.png",
"type": "image/png",
"sizes": "180x180"
},
{
"src": "icon-512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": "/",
"scope": "/",
"display": "standalone",
"theme_color": "#ffffff",
"background_color": "#ffffff"
}
+31
View File
@@ -0,0 +1,31 @@
let filesToCache =
[
"/",
"/apple-touch-icon.png",
"/icon-512.png",
"/favicon.ico",
"/favicon.svg",
"/banner.png",
"/en",
"/ru"
];
self.addEventListener("install", e =>
{
e.waitUntil(
// Get React bundle files list to cache
fetch("/asset-manifest.json")
.then(response => response.json())
.then(assets => filesToCache.concat(Object.entries(assets.files).map(i => i[1])))
.then(() => caches.open("easylogon-demo"))
.then(cache => cache.addAll(filesToCache))
);
});
self.addEventListener("fetch", event =>
event.respondWith((async () =>
{
let response = await caches.match(event.request);
return response || await fetch(event.request);
})()));
+68
View File
@@ -0,0 +1,68 @@
@import "/node_modules/@fluentui/react/dist/sass/References";
ul, ol
{
margin: 0;
}
.containerItem
{
width: 100%
}
// Fix to make chart responsive
.recharts-wrapper
{
width: 100% !important;
}
svg.recharts-surface
{
width: 100%;
}
html
{
background-color: $ms-color-white;
}
// Dark theme adjustments for plugin
@media (prefers-color-scheme: dark)
{
.ezl-container button.ms-Button
{
background-color: rgb(51, 51, 51);
color: white;
}
.ezl-container button.ms-Button:hover
{
background-color: rgb(68, 68, 68);
color: rgb(244, 244, 244);
}
.ms-Tooltip-subtext
{
color: white !important;
}
.ms-Tooltip .ms-Callout-beak,
.ms-Tooltip .ms-Callout-beakCurtain,
.ms-Tooltip .ms-Callout-main
{
background-color: rgb(51, 51, 51) !important;
}
html
{
background-color: $ms-color-neutralPrimary;
}
}
@media screen and (max-width: 800px)
{
.container
{
flex-flow: column;
}
}
+9
View File
@@ -0,0 +1,9 @@
import { render, screen } from "@testing-library/react";
import App from "./App";
it("renders 'Welcome to Your Fluent UI App'", () =>
{
render(<App />);
const linkElement = screen.getByText(/Welcome to Your Fluent UI App/i);
expect(linkElement).toBeInTheDocument();
});
+120
View File
@@ -0,0 +1,120 @@
import React from "react";
import { ActionButton, CommandButton, Dialog, FontClassNames, Image, ITheme, Link, Stack, Text, ThemeProvider } from "@fluentui/react";
import "./App.scss";
import logo from "./logo.svg";
import Start from "./Views/Start";
import ScenarioOne from "./Views/Scenarios/ScenarioOne";
import ScenarioTwo from "./Views/Scenarios/ScenarioTwo";
import ScenarioThree from "./Views/Scenarios/ScenarioThree";
import Result from "./Views/Result";
import { GetCurrentTheme } from "./Services/ThemeService";
import { Strings } from "./Services/LocalizationService";
enum AvailableViews
{
Start = 0,
Secnario1 = 1,
Scenario2 = 2,
Scenario3 = 3,
Result = 4
}
interface IStates
{
currentView : AvailableViews;
records : number[];
haveInternet : boolean;
theme : ITheme;
}
export default class App extends React.Component<any, IStates>
{
constructor(props : any)
{
super(props);
this.state =
{
currentView: AvailableViews.Start,
records: [],
haveInternet: true,
theme: GetCurrentTheme()
};
document.querySelector("head meta[name=theme-color]").setAttribute("content", this.state.theme.palette.white);
window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", () =>
{
let theme = GetCurrentTheme();
this.setState({ ...this.state, theme });
document.querySelector("head meta[name=theme-color]").setAttribute("content", theme.palette.white);
});
}
async componentDidMount() : Promise<void>
{
window.addEventListener("load", () => this.setState({ ...this.state, haveInternet: navigator.onLine }));
window.addEventListener("online", () => this.setState({ ...this.state, haveInternet: navigator.onLine }));
window.addEventListener("offline", () => this.setState({ ...this.state, haveInternet: navigator.onLine }));
}
OnNext(elapsedTime : number) : void
{
this.setState({ ...this.state, currentView: this.state.currentView + 1, records: [...this.state.records, elapsedTime] });
}
public render() : JSX.Element
{
return (
<ThemeProvider theme={ this.state.theme }>
<Stack verticalFill tokens={ { padding: "10px 20px", childrenGap: 20 } } className={ FontClassNames.medium }>
<Stack grow>
{ this.state.currentView === AvailableViews.Start &&
<Start onNext={ () => this.setState({ ...this.state, currentView: AvailableViews.Secnario1 }) } />
}
{ this.state.currentView === AvailableViews.Secnario1 &&
<ScenarioOne onNext={ (e) => this.OnNext(e) } />
}
{ this.state.currentView === AvailableViews.Scenario2 &&
<ScenarioTwo onNext={ (e) => this.OnNext(e) } />
}
{ this.state.currentView === AvailableViews.Scenario3 &&
<ScenarioThree onNext={ (e) => this.OnNext(e) } />
}
{ this.state.currentView === AvailableViews.Result &&
<Result records={ this.state.records } />
}
</Stack>
<Dialog
hidden={ this.state.haveInternet }
dialogContentProps={
{
title: Strings.offlineTitle,
subText: Strings.offlineMessage
} } >
</Dialog>
<Stack horizontalAlign="center">
<CommandButton
text={ Strings.langLabel } iconProps={ { iconName: "LocaleLanguage" } }
menuProps={
{
items:
[
{ key: "en", text: Strings.langAlt, href: Strings.langHref }
]
}
} />
</Stack>
<Stack horizontalAlign="center" tokens={ { childrenGap: 5, padding: 10 } }>
<Image src={ logo } width={ 100 }/>
<ActionButton href="https://easylogon.foxdev.studio" iconProps={ { iconName: "Home" } } text={ Strings.homepageLink } />
<Text variant="small">©{ new Date().getFullYear() } <Link target="_blank" href="https://foxdev.studio">FoxDev Studio</Link>. { Strings.copyrightCaption }</Text>
</Stack>
</Stack>
</ThemeProvider>
);
}
}
+92
View File
@@ -0,0 +1,92 @@
import React from "react";
import { ActionButton, Depths, FontSizes, Icon, ITheme, MotionAnimations, PrimaryButton, Stack, Text } from "@fluentui/react";
import INavProps from "../Interfaces/INavProps";
import { GetCurrentTheme } from "../Services/ThemeService";
import { Strings } from "../Services/LocalizationService";
enum SandboxState
{
Initial,
Running,
Finished
}
interface IStates
{
state : SandboxState;
time : number;
}
export default class Sandbox extends React.Component<INavProps, IStates>
{
constructor(props : INavProps)
{
super(props);
this.state =
{
state: SandboxState.Initial,
time: 0
}
}
private OnStart() : void
{
this.setState({ state: SandboxState.Running, time: new Date().getTime() });
console.log("Sandbox started");
}
public OnFinished() : void
{
let elapsedTime : number = new Date().getTime() - this.state.time;
this.setState({ state: SandboxState.Finished, time: elapsedTime / 1000 });
console.log("Elapsed time (sec):", elapsedTime);
}
public render() : JSX.Element
{
let theme : ITheme = GetCurrentTheme();
return (
<Stack tokens={ { childrenGap: 10 } } >
<Text variant="xxLarge">{ Strings.sandbox }</Text>
<Stack
tokens={ { padding: 20 } }
styles={ { root: { boxShadow: Depths.depth8, borderRadius: 6 } } } >
{ this.state.state === SandboxState.Initial &&
<Stack
horizontalAlign="center"
tokens={ { childrenGap: 20 } }
styles={ { root: { animation: MotionAnimations.fadeIn } } } >
<Icon styles={ { root: { fontSize: FontSizes.size68 } } } iconName="Stopwatch" />
<Text variant="large" styles={ { root: { textAlign: "center" } } }>{ Strings.sandboxStartHeader }</Text>
<Text styles={ { root: { textAlign: "center" } } }>{ Strings.sandboxStartSubheader }</Text>
<PrimaryButton text={ Strings.sandboxStart } onClick={ () => this.OnStart() } />
</Stack>
}
{ this.state.state === SandboxState.Running &&
<Stack styles={ { root: { animation: MotionAnimations.fadeIn } } } >
{ this.props.children }
</Stack>
}
{ this.state.state === SandboxState.Finished &&
<Stack
horizontalAlign="center"
tokens={ { childrenGap: 20 } }
styles={ { root: { animation: MotionAnimations.fadeIn } } } >
<Icon styles={ { root: { fontSize: FontSizes.size68, color: theme.semanticColors.successIcon } } } iconName="Completed" />
<Text variant="xLarge" styles={ { root: { textAlign: "center", color: theme.semanticColors.successIcon } } }>{ Strings.sandboxFinishHeader }</Text>
{ this.props.onNext &&
<ActionButton text={ Strings.sandboxFinishNext } iconProps={ { iconName: "ChevronRight" } } onClick={ () => this.props.onNext(this.state.time) } />
}
</Stack>
}
</Stack>
</Stack>
);
}
}
+95
View File
@@ -0,0 +1,95 @@
import React from "react";
import { Callout, Link, PrimaryButton, Stack, Text, TextField } from "@fluentui/react";
import ISandboxProps from "../../Interfaces/ISandboxProps";
import { Strings } from "../../Services/LocalizationService";
interface IStates
{
login : string;
password : string;
loginError?: string;
passwordError?: string;
showHint : boolean;
}
export default class SandboxOne extends React.Component<ISandboxProps, IStates>
{
constructor(props : ISandboxProps)
{
super(props);
this.state =
{
login: "",
password: "",
loginError: null,
passwordError: null,
showHint: false
};
}
private OnSubmit() : void
{
let newState : IStates = this.state;
newState.loginError = null;
newState.passwordError = null;
if (newState.login.toLocaleLowerCase() !== "test-user@example.com")
newState.loginError = Strings.sandboxLoginError;
if (newState.password !== ".tSYRc:eYP_868p")
newState.passwordError = Strings.sandboxPasswordError;
if (newState.login === "")
newState.loginError = Strings.sandboxEmptyField;
if (newState.password === "")
newState.passwordError = Strings.sandboxEmptyField;
this.setState(newState);
// if (newState.loginError === null && newState.passwordError === null)
this.props.context.current.OnFinished();
}
private OnPaste(event : React.ClipboardEvent<HTMLInputElement>) : boolean
{
event.preventDefault();
return false;
}
public render() : JSX.Element
{
return (
<Stack tokens={ { childrenGap: 10 } } >
<Text variant="xLarge">{ Strings.sandboxLoginHeader }</Text>
<TextField
placeholder={ Strings.sandboxEmail } required autoComplete="chrome-off" onPaste={ this.OnPaste }
value={ this.state.login } onChange={ (_, e) => this.setState({ ...this.state, login: e }) }
errorMessage={ this.state.loginError } />
<TextField
placeholder={ Strings.sandboxPassword } required autoComplete="chrome-off" onPaste={ this.OnPaste }
type="password" canRevealPassword
value={ this.state.password } onChange={ (_, e) => this.setState({ ...this.state, password: e }) }
errorMessage={ this.state.passwordError } />
<Stack horizontalAlign="end" tokens={ { childrenGap: 10 } }>
<Link id="credentialHintLink" onClick={ () => this.setState({ ...this.state, showHint: true })}>{ Strings.sandbox1forgotPassword }</Link>
<PrimaryButton text={ Strings.sandboxLogin } onClick={ () => this.OnSubmit() } />
</Stack>
{ this.state.showHint &&
<Callout
onDismiss={ () => this.setState({ ...this.state, showHint: false }) }
target="#credentialHintLink" >
<Stack tokens={ { padding: 20 } } styles={ { root: { userSelect: "none" } } }>
<Text>{ Strings.startPrereqLogin }: test-user@example.com</Text>
<Text>{ Strings.startPrereqPassword }: .tSYRc:eYP_868p</Text>
</Stack>
</Callout>
}
</Stack>
);
}
}
+82
View File
@@ -0,0 +1,82 @@
import React from "react";
import { PrimaryButton, Stack, Text, TextField } from "@fluentui/react";
import ISandboxProps from "../../Interfaces/ISandboxProps";
import { Strings } from "../../Services/LocalizationService";
interface IStates
{
loginError?: string;
passwordError?: string;
}
export default class SandboxThree extends React.Component<ISandboxProps, IStates>
{
constructor(props : ISandboxProps)
{
super(props);
this.state =
{
loginError: null,
passwordError: null
};
}
componentDidMount() : void
{
var script = document.createElement("script");
script.src = "https://easylogon.foxdev.studio/ezlog.js";
script.defer = true;
script.async = true;
document.body.appendChild(script);
}
private OnSubmit() : void
{
let newState : IStates = this.state;
newState.loginError = null;
newState.passwordError = null;
let login : string = document.querySelector<HTMLInputElement>("input[name=field1]").value;
let password : string = document.querySelector<HTMLInputElement>("input[name=field2]").value;
if (login.toLocaleLowerCase() !== "test-user@example.com")
newState.loginError = Strings.sandboxLoginError;
if (password !== ".tSYRc:eYP_868p")
newState.passwordError = Strings.sandboxPasswordError;
if (login === "")
newState.loginError = Strings.sandboxEmptyField;
if (password === "")
newState.passwordError = Strings.sandboxEmptyField;
this.setState(newState);
// if (newState.loginError === null && newState.passwordError === null)
this.props.context.current.OnFinished();
}
public render() : JSX.Element
{
return (
<Stack tokens={ { childrenGap: 10 } } >
<Text variant="xLarge">{ Strings.sandboxLoginHeader }</Text>
<TextField
placeholder={ Strings.sandboxEmail } name="field1" required
errorMessage={ this.state.loginError } />
<TextField
placeholder={ Strings.sandboxPassword } name="field2" required
type="password" canRevealPassword
errorMessage={ this.state.passwordError } />
<Stack horizontalAlign="end" tokens={ { childrenGap: 10 } }>
<PrimaryButton text={ Strings.sandboxLogin } name="btn1" onClick={ () => this.OnSubmit() } />
</Stack>
<div className="ezl-container" data-widgettype="button" data-login="input[name=field1]" data-password="input[name=field2]" data-submit="button[name=btn1]" />
</Stack>
);
}
}
+92
View File
@@ -0,0 +1,92 @@
import React from "react";
import { ActionButton, PrimaryButton, Stack, Text, TextField } from "@fluentui/react";
import ISandboxProps from "../../Interfaces/ISandboxProps";
import { Strings } from "../../Services/LocalizationService";
interface IStates
{
login : string;
password : string;
loginError?: string;
passwordError?: string;
}
export default class SandboxTwo extends React.Component<ISandboxProps, IStates>
{
constructor(props : ISandboxProps)
{
super(props);
this.state =
{
login: "",
password: "",
loginError: null,
passwordError: null
};
}
private OnSubmit() : void
{
let newState : IStates = this.state;
newState.loginError = null;
newState.passwordError = null;
if (newState.login.toLocaleLowerCase() !== "test-user@example.com")
newState.loginError = Strings.sandboxLoginError;
if (newState.password !== ".tSYRc:eYP_868p")
newState.passwordError = Strings.sandboxPasswordError;
if (newState.login === "")
newState.loginError = Strings.sandboxEmptyField;
if (newState.password === "")
newState.passwordError = Strings.sandboxEmptyField;
this.setState(newState);
// if (newState.loginError === null && newState.passwordError === null)
this.props.context.current.OnFinished();
}
private OnTyping(event : React.KeyboardEvent<HTMLInputElement>) : boolean
{
console.log(event);
if (event.ctrlKey && (event.key === "v" || event.key === "a" || event.key === "x"))
return true;
if (event.key === "Backspace" || event.key === "Delete" || event.key === "Tab" ||
event.metaKey || event.altKey || event.ctrlKey || event.shiftKey)
return true;
event.preventDefault();
return false;
}
public render() : JSX.Element
{
return (
<Stack tokens={ { childrenGap: 10 } } >
<Text variant="xLarge">{ Strings.sandboxLoginHeader }</Text>
<TextField
placeholder={ Strings.sandboxEmail } required autoComplete="chrome-off" onKeyDown={ this.OnTyping }
value={ this.state.login } onChange={ (_, e) => this.setState({ ...this.state, login: e }) }
errorMessage={ this.state.loginError } />
<TextField
placeholder={ Strings.sandboxPassword } required autoComplete="chrome-off" onKeyDown={ this.OnTyping }
type="password" canRevealPassword
value={ this.state.password } onChange={ (_, e) => this.setState({ ...this.state, password: e }) }
errorMessage={ this.state.passwordError } />
<Stack horizontalAlign="end" tokens={ { childrenGap: 10 } }>
<PrimaryButton text={ Strings.sandboxLogin } onClick={ () => this.OnSubmit() } />
</Stack>
<Stack horizontalAlign="center">
<ActionButton iconProps={ { iconName: "Lightbulb" } } text={ Strings.sandbox2hint } />
</Stack>
</Stack>
);
}
}
+4
View File
@@ -0,0 +1,4 @@
export default interface INavProps
{
onNext?: (elapsedSeconds?: number) => void;
}
+7
View File
@@ -0,0 +1,7 @@
import React from "react";
import Sandbox from "../Components/Sandbox";
export default interface ISandboxProps
{
context : React.RefObject<Sandbox>;
}
+288
View File
@@ -0,0 +1,288 @@
import LocalizedStrings, { LocalizedStringsMethods } from "react-localization";
export let Strings = new LocalizedStrings(
{
en:
{
// App.tsx
// Offline dialog
offlineTitle: "Internet is required",
offlineMessage: "Please check your internet connection. It is required for the demo to work",
// Footer
homepageLink: "Go to homepage",
copyrightCaption: "All rights reserved",
// Language switch
langLabel: "Language: English",
langAlt: "Русский",
langHref: "/ru",
// Start.tsx
startTitle: "Welcome to EasyLogon demo!",
startHeader: "Try out our QR authentication system!",
startSubheader: "Throughout this demo you will go through three different scenarios of authentication on the the Internet",
startFactsHeader: "Did you know that:",
startFact1: "75% are frustrated trying to keep track of their passwords",
startFact2: "66% use the same password for more than one online account",
startFact3: "59% of users have incorporated a name or a birthday into their password to an online account",
startFact4: "24% have used common passwords, or some variation (abc123, qwerty, password, etc.)",
startFact5: "17% have tried to guess someones password and succeeded",
startFact6: "40% have reported their personal information to be compromised",
startFactOfThem: "Of them:",
startFact7: "Only 15% use a password manager",
startFact8: "63% don't use two-factor authentication",
startFact9: "66% don't change their passwords regularly",
startFactsSource: "Datasource",
startPrereqHeader: "Prerequisites",
startPrereq1TitleP1: "1. Download",
startPrereq1TitleP2: "EasyLogon app",
startPrereq1TitleP3: "to your phone",
startPrereq1CaptionP1: "You will need to have an",
startPrereq1CaptionP2: "smartphone with a camera",
startPrereq2Title: "2. Memorize these credentials",
startPrereq2Caption: "Take your time to remember these login and password. You will need them later",
startPrereqLogin: "Login",
startPrereqPassword: "Password",
startPrereqStart: "Let's start!",
// Sandbox.tsx
sandbox: "Sandbox",
sandboxStartHeader: "Try to complete the test as soon as you can",
sandboxStartSubheader: "Please read instructions before clicking \"Start\"",
sandboxStart: "Start",
// Sandbox common
sandboxLoginHeader: "Sign into AwesomeSite",
sandboxEmail: "Email",
sandboxPassword: "Password",
sandboxLogin: "Sign in",
sandboxLoginError: "Invalid login",
sandboxPasswordError: "Invalid password",
sandboxEmptyField: "This field is required",
sandboxFinishHeader: "Great job!",
sandboxFinishNext: "Next",
// Scenario common
scenarioObjective: "Objective",
scenarioSteps: "Steps",
scenarioStep1: "Click \"Start\" when you are ready",
// ScenarioOne.tsx
scenario1Header: "Scenario 1: Do as grandpa taught",
scenario1Objective: "Try to sign in as you usually do: by typing in login and password",
scenario1description1: "Usually, we have passwords saved in our browsers but from time to time we still come to a situation where our beloved autofill is missing (we're in computer lab, in conference room and need that presentation saved in the cloud ASAP). In this case we have to rely on our memory or just come up with a simple password which puts our priceless personal information under the risk",
scenario1description2: "Here we have recreated the situation: no autofill, just your memory or your notebook",
scenario1description3: "Password used here is considered to be invulnerable (15 characters, upper/lowercase letters, digits, special symbols). As all passwords should be",
scenario1step2: "Recall login and password shown at the start (click \"Forgot password?\" to get a hint)",
scenario1step3: "Type in credentials",
scenario1step4: "Click \"Sign in\"",
scenario1step5: "No copypasting ;)",
// SandboxOne.tsx
sandbox1forgotPassword: "Forgot password?",
// ScenarioTwo.tsx
scenario2Header: "Scenario 2: Anytime, anywhere",
scenario2Objective: "Try to sign in with QR code",
scenario2description1: "It wasn't that easy, was it? Now imagine that you have to remember 50 or 100 of such passwords. Holy smokes, right?",
scenario2description2: "But there's another way. You can use strong unique passwords without having to remember them any time on any device. Even there, where regular password managers aren't available",
scenario2description3: "EasyLogon offers you a brand new password manager that can deliver any of your credentials to any device within a few seconds. And it already works with all websites on the Internet. Let's try this out",
scenario2step2: "Open https://ezlog.app website in a new tab",
scenario2step3: "Scan the QR code with EasyLogon app",
scenario2step4: "Swipe right or left \"EasyLogon Demo\" credentials",
scenario2step5: "Copy/paste login and password values here",
scenario2step6: "Click \"Sign in\"",
// SandboxTwo.tsx
sandbox2hint: "Hint: open https://ezlog.app in a new tab",
// ScenarioThree.tsx
scenario3Header: "Scenario 3: Click, scan, done",
scenario3Objective: "Sign in with QR code again but faster",
scenario3description1: "See the difference? But that's not all! We've prepared a special tool that allows to add QR code sign in on any website within a few minutes! With this tool installed, you are able to sign on such websites within a few clicks!",
scenario3description2: "So if you are a web developer (or even a software developer), you can make your users' experience and security better in less than 30 minutes",
scenario3description3: "In a meantime, let's see how it works",
scenario3step2: "Click \"Sign in with QR code\" button",
scenario3step3: "Scan the QR code with EasyLogon app",
scenario3step4: "Swipe right or left \"EasyLogon Demo\" credentials",
// Results.tsx
results: "Results",
scenario: "Scenario",
tryAgain: "Try again",
learnMore: "Learn more about EasyLogon for developers",
resultsTitle: "Quite impressive, isn't it?",
resultsHeader: "Start using EasyLogon now!",
resultsSubheader: "And forget about passwords. This time for real!",
resultsUserHeader: "Advantages for users:",
resultsUser1: "No need to remember passwords anymore",
resultsUser2: "One app - countless services",
resultsUser3: "Password manager + Authenticator - maximum protection for your data",
resultsUser4: "You can now use complex passwords everywhere",
resultsDevHeader: "Advantages for developers:",
resultsDev1: "QR code authentication for your users",
resultsDev2: "Encourage users to use complex passwords",
resultsDev3: "10 minutes to get started",
resultsUserHeader2: "For users",
resultsDevHeader2: "For web developers",
userResultP1: "You can save up to",
userResultP2: "of your time",
userResultP3: "with EasyLogon when signing in on the Internet",
userResultAlt1: "Looks like you have a good skill at memorizing and typing in complex passwords! Great job!",
userResultAlt2: "Though imagine that you have to memorize 50 such passwords or 100. Will you able to handle this?",
devResultP1: "You can save up to",
devResultP2: "of your users' time",
devResultP3: "with EasyLogon when signing in on your websites",
},
ru:
{
// App.tsx
// Offline dialog
offlineTitle: "Отсутствует подключение к Интернету",
offlineMessage: "Пожалуйста, проверьте интернет-соединение. Оно необходимо для работы демонстрации",
// Footer
homepageLink: "Домашняя страница",
copyrightCaption: "Все права защищены",
// Language switch
langLabel: "Язык: Русский",
langAlt: "English",
langHref: "/en",
// Start.tsx
startTitle: "Добро пожаловать на демо EasyLogon!",
startHeader: "Испытайте нашу систему аутентификации через QR код!",
startSubheader: "В данной демонстрации вы пройдете через три разные сценария аутентификации в Интернете",
startFactsHeader: "Знаете ли вы, что",
startFact1: "75% испытывают проблемы с управлением паролями",
startFact2: "66% используют один и тот же пароль для нескольких онлайн-аккаунтов",
startFact3: "59% пользователей используют имя или день рождения как пароль для онлайн-аккаунтов",
startFact4: "24% используют простые пароли, или их вариации (abc123, qwerty, password и т.д.)",
startFact5: "17% пытались подобрать пароль другого пользователя и преуспели в этом",
startFact6: "40% пользователей сообщили об утечке своих личных данных",
startFactOfThem: "Из них:",
startFact7: "Только 15% используюут менеджер паролей",
startFact8: "63% не используют двухфакторную аутентификацию",
startFact9: "66% редко обновляют пароли",
startFactsSource: "Источник",
startPrereqHeader: "Подготовка",
startPrereq1TitleP1: "1. Скачайте",
startPrereq1TitleP2: "приложение EasyLogon",
startPrereq1TitleP3: "на ваш телефон",
startPrereq1CaptionP1: "Вам понадобится смартфон на",
startPrereq1CaptionP2: "с камерой",
startPrereq2Title: "2. Запомните эти данные",
startPrereq2Caption: "Пожалуйста, запомните эти логин и пароль. Они понадобятся вам в дальнейшем",
startPrereqLogin: "Логин",
startPrereqPassword: "Пароль",
startPrereqStart: "Начнем!",
// Sandbox.tsx
sandbox: "Песочница",
sandboxStartHeader: "Постарайтесь завершить задание как можно быстрее",
sandboxStartSubheader: "Пожалуйста, прочитайте инструкцию перед началом",
sandboxStart: "Начать",
// Sandbox common
sandboxLoginHeader: "Войдите в личный кабинет",
sandboxEmail: "Email",
sandboxPassword: "Пароль",
sandboxLogin: "Войти",
sandboxLoginError: "Неправильный логин",
sandboxPasswordError: "Неправильный пароль",
sandboxEmptyField: "Это поле не должно быть пустым",
sandboxFinishHeader: "Отличная работа!",
sandboxFinishNext: "Далее",
// Scenario common
scenarioObjective: "Задача",
scenarioSteps: "Шаги",
scenarioStep1: "Нажмите \"Начать\", когда будете готовы",
// ScenarioOne.tsx
scenario1Header: "Сценарий 1: Делай как отцы завещали",
scenario1Objective: "Попробуйте войти в аккаунт как обычно: вручную введя логин и пароль в нужные поля",
scenario1description1: "Обычно, мы храним наши пароли в браузере. Это удобно. Но время от времени мы все равно оказываемся в ситуации, когда наше любимое автозаполнение отсутствует (мы находимся в кабинете информатики, или в коференц-зале и нам очень срочно нужна та презентация, которую мы забыли в облаке). В таких случаях нам приходится полагаться на нашу память. Либо использовать слабый простой для запоминания пароль. Но тогда надо быть готовым ко вломам и утечкам данных",
scenario1description2: "Здесь мы воссоздали такую ситуацию: автозаполнения нет, только ваша память",
scenario1description3: "Пароль, который здесь используется, считается безопасным (15 символов, нижний/верхний регистр, цифры, специальные символы). Такой, какими все пароли должны быть",
scenario1step2: "Вспомните логин и пароль, показанные в начале (нажмите \"Забыли пароль?\", чтобы получить подсказку)",
scenario1step3: "Введите логин и пароль",
scenario1step4: "Нажмите \"Войти\"",
scenario1step5: "Без Ctrl-C/Ctrl-V ;)",
// SandboxOne.tsx
sandbox1forgotPassword: "Забыли пароль?",
// ScenarioTwo.tsx
scenario2Header: "Сценарий 2: Когда угодно, где угодно",
scenario2Objective: "Войдите в аккаунт через QR-код",
scenario2description1: "Не так уж и просто, да? А теперь представьте, что вам нужно запоминить 100 таких паролей для каждого сервиса. Неприятненько, да?",
scenario2description2: "Но есть другой способ. Вы можете использовать сложные уникальные пароли в любое время на любом устройстве, без необходимости их запоминать. Даже там, где обычные менеджеры паролей недоступны",
scenario2description3: "EasyLogon предлагает вам совершенно новый менеджер паролей, который может доставить ваши учетные данные для входа на любом устройстве за считанные секунды. И он уже работает со всеми вебсайтами в Интернете! Давайте взглянем",
scenario2step2: "Откройте вебсайт https://ezlog.app в новой вкладке",
scenario2step3: "Отсканируйте QR-код с помощью приложения EasyLogon",
scenario2step4: "Смахните вправо/влево учетные данные \"EasyLogon Demo\"",
scenario2step5: "Скопируйте и вставьте полученные логин и пароль сюда",
scenario2step6: "Нажмите \"Войти\"",
// SandboxTwo.tsx
sandbox2hint: "Подсказка: откройте https://ezlog.app в новой вкладке",
// ScenarioThree.tsx
scenario3Header: "Сценарий 3: Нажать, отсканировать, готово",
scenario3Objective: "Войдите в аккаунт через QR-код еще раз",
scenario3description1: "Чуствуете разницу? Но это еще не все! Мы подготовили специальный инструмент, который позволяет добавить вход через QR-код на любой сайт за считанные минуты! И на таких вебсайтах пользователи смогут входить в свой аккаунт всего в пару кликов!",
scenario3description2: "Так что если вы - веб-разработчик (или даже разработчик ПО), вы можете сделать пользовательскй опыт и безопасность лучше менее чем за 30 минут",
scenario3description3: "А между тем, давайте взглянем на то, как это работает",
scenario3step2: "Нажмите кнопку \"Sign in with QR code\"",
scenario3step3: "Отсканируйте QR-код с помощью приложения EasyLogon",
scenario3step4: "Смахните вправо/влево учетные данные \"EasyLogon Demo\"",
// Results.tsx
results: "Результаты",
scenario: "Сценарий",
tryAgain: "Попробовать еще раз",
learnMore: "Узнать больше об EasyLogon для разработчиков",
resultsTitle: "Впечатляет, не правда ли?",
resultsHeader: "Начните использовать EasyLogon уже сейчас!",
resultsSubheader: "И забудьте о паролях. На этот раз по настоящему!",
resultsUserHeader: "Плюсы для пользователей:",
resultsUser1: "Больше не надо запоминать пароли",
resultsUser2: "Одно приложение - множество сервисов",
resultsUser3: "Менеджер паролей + Аутентификатор - максимальная защита для ваших данных",
resultsUser4: "Теперь вы можете использовать сложные пароли повсюду",
resultsDevHeader: "Плюсы для разработчиков:",
resultsDev1: "Аутентификация через QR-код для ваших пользователей",
resultsDev2: "Побудите пользователей использовать сложные пароли",
resultsDev3: "10 минут для настройки",
resultsUserHeader2: "Для пользователей",
resultsDevHeader2: "Для веб-разработчиков",
userResultP1: "Вы можете сохранить до",
userResultP2: "вашего времени",
userResultP3: "с EasyLogon при входе в аккаунты в Интернете",
userResultAlt1: "Похоже, что у вас есть способности к запоминианию сложных паролей и быстрой печати! Это круто!",
userResultAlt2: "Но все же представьте, что вам нужно запомнить 50 таких паролей или 100. Сможете справиться?",
devResultP1: "Вы можете сохранить до",
devResultP2: "времени ваших пользователей",
devResultP3: "с EasyLogon при входе в аккаунт на ваших веб-сайтах",
}
}
);
+48
View File
@@ -0,0 +1,48 @@
import { createTheme, getTheme, ITheme, Theme } from "@fluentui/react";
export function GetCurrentTheme() : ITheme
{
return window.matchMedia("(prefers-color-scheme: dark)").matches ? DarkTheme : getTheme();
}
export const DarkTheme : Theme = createTheme(
{
palette:
{
themePrimary: "#69afe5",
themeLighterAlt: "#040709",
themeLighter: "#111c25",
themeLight: "#203545",
themeTertiary: "#3f698a",
themeSecondary: "#5d9bca",
themeDarkAlt: "#77b7e8",
themeDark: "#8bc2ec",
themeDarker: "#a8d1f1",
neutralLighterAlt: "#3c3c3c",
neutralLighter: "#444444",
neutralLight: "#515151",
neutralQuaternaryAlt: "#595959",
neutralQuaternary: "#5f5f5f",
neutralTertiaryAlt: "#7a7a7a",
neutralTertiary: "#c8c8c8",
neutralSecondary: "#d0d0d0",
neutralPrimaryAlt: "#dadada",
neutralPrimary: "#ffffff",
neutralDark: "#f4f4f4",
black: "#f8f8f8",
white: "#323130"
},
semanticColors:
{
successIcon: "#92c353",
successBackground: "#393d1b",
errorIcon: "#f1707b",
errorBackground: "#442726",
errorText: "#f1707b",
infoBackground: "#41403e",
infoIcon: "#c8c6c4",
messageText: "#f3f2f1",
messageLink: "#69afe5",
messageLinkHovered: "#a8d1f1"
}
});
+109
View File
@@ -0,0 +1,109 @@
import React from "react";
import { ActionButton, Depths, FontSizes, FontWeights, ITheme, MotionAnimations, PrimaryButton, Stack, Text } from "@fluentui/react";
import { Bar, BarChart, ResponsiveContainer, XAxis, YAxis } from "recharts";
import { GetCurrentTheme } from "../Services/ThemeService";
import { Strings } from "../Services/LocalizationService";
interface IResultRecord
{
Name : string;
Value : number;
}
export default class Result extends React.Component<{ records : number[] }>
{
public render() : JSX.Element
{
let theme : ITheme = GetCurrentTheme();
let data : IResultRecord[] = this.props.records.map((value, index) =>
({
Name: `${Strings.scenario} ${index + 1}`,
Value: Number.parseFloat(value.toFixed(2))
}));
return (
<Stack
verticalAlign="center" horizontalAlign="center"
verticalFill horizontal
className="container"
styles={ { root: { animation: MotionAnimations.fadeIn, gridGap: "60px" } } } >
<Stack className="containerItem" tokens={ { childrenGap: 20, maxWidth: 600 } }>
<Text variant="xxLarge">{ Strings.resultsTitle }</Text>
<Stack>
<Text variant="xLarge">{ Strings.resultsHeader }</Text>
<Text>{ Strings.resultsSubheader }</Text>
</Stack>
<Text variant="xLarge">{ Strings.resultsUserHeader }</Text>
<ul>
<li>{ Strings.resultsUser1 }</li>
<li>{ Strings.resultsUser2 }</li>
<li>{ Strings.resultsUser3 }</li>
<li>{ Strings.resultsUser4 }</li>
</ul>
<Text variant="xLarge">{ Strings.resultsDevHeader }</Text>
<ul>
<li>{ Strings.resultsDev1 }</li>
<li>{ Strings.resultsDev2 }</li>
<li>{ Strings.resultsDev3 }</li>
</ul>
<Stack horizontal wrap tokens={ { childrenGap: 10 } }>
<ActionButton
text={ Strings.tryAgain } iconProps={ { iconName: "Refresh" } }
styles={ { root: { height: 32 } } }
onClick={ () => window.location.reload() } />
<PrimaryButton text={ Strings.learnMore } href="https://easylogon.foxdev.studio" />
</Stack>
</Stack>
<Stack className="containerItem" tokens={ { childrenGap: 10, maxWidth: 600 } }>
<Text variant="xxLarge">{ Strings.results }</Text>
<Stack
tokens={ { childrenGap: 20, padding: 20 } }
styles={ { root: { boxShadow: Depths.depth8, borderRadius: 6 } } } >
<ResponsiveContainer height={ 80 } width="100%">
<BarChart
layout="vertical"
barCategoryGap={ 5 }
data={ data }
margin={ { right: 30, left: 25 } } >
<XAxis type="number" hide scale="linear" />
<YAxis
type="category" dataKey="Name" scale="auto"
axisLine={ false } tickLine={ false }
tick={ { fill: theme.palette.neutralPrimary, width: 100, fontWeight: FontWeights.semibold } } />
<Bar dataKey="Value" radius={ 4 } label={ { position: "right", fill: theme.palette.neutralPrimary } } fill={ theme.palette.themePrimary } />
</BarChart>
</ResponsiveContainer>
{ data[0].Value > data[1].Value ?
<Stack>
<Text variant="large">{ Strings.resultsUserHeader2 }</Text>
<Text>{ Strings.userResultP1 } <b style={ { fontSize: FontSizes.large, color: theme.semanticColors.successIcon } }>{ Math.round(100 - data[1].Value / data[0].Value * 100) }% { Strings.userResultP2 }</b> { Strings.userResultP3 }</Text>
</Stack>
:
<>
<Text>{ Strings.userResultAlt1 }</Text>
<Text>{ Strings.userResultAlt2 }</Text>
</>
}
{ data[0].Value > data[2].Value &&
<Stack>
<Text variant="large">{ Strings.resultsDevHeader2 }</Text>
<Text>{ Strings.devResultP1 } <b style={ { fontSize: FontSizes.large, color: theme.semanticColors.successIcon } }>{ Math.round(100 - data[2].Value / data[0].Value * 100) }% { Strings.devResultP2 }</b> { Strings.devResultP3 }</Text>
</Stack>
}
</Stack>
</Stack>
</Stack>
);
}
}
+48
View File
@@ -0,0 +1,48 @@
import React from "react";
import { MotionAnimations, Stack, Text } from "@fluentui/react";
import Sandbox from "../../Components/Sandbox";
import SandboxOne from "../../Components/Sandboxes/SandboxOne";
import INavProps from "../../Interfaces/INavProps";
import { Strings } from "../../Services/LocalizationService";
export default class ScenarioOne extends React.Component<INavProps>
{
private _sandboxRef : React.RefObject<Sandbox> = React.createRef();
public render() : JSX.Element
{
return (
<Stack
verticalAlign="center" horizontalAlign="center"
verticalFill horizontal
className="container"
styles={ { root: { animation: MotionAnimations.fadeIn, gridGap: "60px" } } } >
<Stack className="containerItem" tokens={ { childrenGap: 20, maxWidth: 600 } }>
<Text variant="xxLarge">{ Strings.scenario1Header }</Text>
<Stack>
<Text variant="xLarge">{ Strings.scenarioObjective }</Text>
<Text>{ Strings.scenario1Objective }</Text>
</Stack>
<Text>{ Strings.scenario1description1 }</Text>
<Text>{ Strings.scenario1description2 }</Text>
<Text>{ Strings.scenario1description3 }</Text>
<Text variant="xLarge">{ Strings.scenarioSteps }</Text>
<ol>
<li>{ Strings.scenarioStep1 }</li>
<li>{ Strings.scenario1step2 }</li>
<li>{ Strings.scenario1step3 }</li>
<li>{ Strings.scenario1step4 }</li>
<li>{ Strings.scenario1step5 }</li>
</ol>
</Stack>
<Stack className="containerItem" tokens={ { maxWidth: 600 } }>
<Sandbox ref={ this._sandboxRef } onNext={ (n) => this.props.onNext(n) }>
<SandboxOne context={ this._sandboxRef } />
</Sandbox>
</Stack>
</Stack>
);
}
}
+49
View File
@@ -0,0 +1,49 @@
import React from "react";
import { MotionAnimations, Stack, Text } from "@fluentui/react";
import Sandbox from "../../Components/Sandbox";
import INavProps from "../../Interfaces/INavProps";
import SandboxThree from "../../Components/Sandboxes/SandboxThree";
import { Strings } from "../../Services/LocalizationService";
export default class ScenarioThree extends React.Component<INavProps>
{
private _sandboxRef : React.RefObject<Sandbox> = React.createRef();
public render() : JSX.Element
{
return (
<Stack
verticalAlign="center" horizontalAlign="center"
verticalFill horizontal
className="container"
styles={ { root: { animation: MotionAnimations.fadeIn, gridGap: "60px" } } } >
<Stack className="containerItem" tokens={ { childrenGap: 20, maxWidth: 600 } }>
<Text variant="xxLarge">{ Strings.scenario3Header }</Text>
<Stack>
<Text variant="xLarge">{ Strings.scenarioObjective }</Text>
<Text>{ Strings.scenario3Objective }</Text>
</Stack>
<Text>{ Strings.scenario3description1 }</Text>
<Text>{ Strings.scenario3description2 }</Text>
<Text>{ Strings.scenario3description3 }</Text>
<Text variant="xLarge">{ Strings.scenarioSteps }</Text>
<ol>
<li>{ Strings.scenarioStep1 }</li>
<li>{ Strings.scenario3step2 }</li>
<li>{ Strings.scenario3step3 }</li>
<li>{ Strings.scenario3step4 }</li>
</ol>
</Stack>
<Stack className="containerItem" tokens={ { maxWidth: 600 } }>
<Sandbox ref={ this._sandboxRef } onNext={ (n) => this.props.onNext(n) }>
<SandboxThree context={ this._sandboxRef } />
</Sandbox>
</Stack>
</Stack>
);
}
}
+51
View File
@@ -0,0 +1,51 @@
import React from "react";
import { MotionAnimations, Stack, Text } from "@fluentui/react";
import Sandbox from "../../Components/Sandbox";
import INavProps from "../../Interfaces/INavProps";
import SandboxTwo from "../../Components/Sandboxes/SandboxTwo";
import { Strings } from "../../Services/LocalizationService";
export default class ScenarioTwo extends React.Component<INavProps>
{
private _sandboxRef : React.RefObject<Sandbox> = React.createRef();
public render() : JSX.Element
{
return (
<Stack
verticalAlign="center" horizontalAlign="center"
verticalFill horizontal
className="container"
styles={ { root: { animation: MotionAnimations.fadeIn, gridGap: "60px" } } } >
<Stack className="containerItem" tokens={ { childrenGap: 20, maxWidth: 600 } }>
<Text variant="xxLarge">{ Strings.scenario2Header }</Text>
<Stack>
<Text variant="xLarge">{ Strings.scenarioObjective }</Text>
<Text>{ Strings.scenario2Objective }</Text>
</Stack>
<Text>{ Strings.scenario2description1 }</Text>
<Text>{ Strings.scenario2description2 }</Text>
<Text>{ Strings.scenario2description3 }</Text>
<Text variant="xLarge">{ Strings.scenarioSteps }</Text>
<ol>
<li>{ Strings.scenarioStep1 }</li>
<li>{ Strings.scenario2step2 }</li>
<li>{ Strings.scenario2step3 }</li>
<li>{ Strings.scenario2step4 }</li>
<li>{ Strings.scenario2step5 }</li>
<li>{ Strings.scenario2step6 }</li>
</ol>
</Stack>
<Stack className="containerItem" tokens={ { maxWidth: 600 } }>
<Sandbox ref={ this._sandboxRef } onNext={ (n) => this.props.onNext(n) }>
<SandboxTwo context={ this._sandboxRef } />
</Sandbox>
</Stack>
</Stack>
);
}
}
+70
View File
@@ -0,0 +1,70 @@
import React from "react";
import { Depths, Link, MessageBar, MotionAnimations, PrimaryButton, Stack, Text } from "@fluentui/react";
import INavProps from "../Interfaces/INavProps";
import { Strings } from "../Services/LocalizationService";
export default class Start extends React.Component<INavProps>
{
public render() : JSX.Element
{
return (
<Stack
verticalAlign="center" horizontalAlign="center"
verticalFill horizontal
className="container"
styles={ { root: { animation: MotionAnimations.slideUpIn, gridGap: "60px" } } } >
<Stack className="containerItem" tokens={ { childrenGap: 20, maxWidth: 600 } }>
<Text variant="xxLarge">{ Strings.startTitle }</Text>
<Stack>
<Text variant="xLarge">{ Strings.startHeader }</Text>
<Text>{ Strings.startSubheader }</Text>
</Stack>
<Text variant="xLarge">{ Strings.startFactsHeader }:</Text>
<ul>
<li>{ Strings.startFact1 }</li>
<li>{ Strings.startFact2 }</li>
<li>{ Strings.startFact3 }</li>
<li>{ Strings.startFact4 }</li>
<li>{ Strings.startFact5 }</li>
<li>{ Strings.startFact6 }</li>
</ul>
<Text>{ Strings.startFactOfThem }</Text>
<ul>
<li>{ Strings.startFact7 }</li>
<li>{ Strings.startFact8 }</li>
<li>{ Strings.startFact9 }</li>
</ul>
<Text variant="tiny">{ Strings.startFactsSource }: <Link target="_blank" href="https://storage.googleapis.com/gweb-uniblog-publish-prod/documents/PasswordCheckup-HarrisPoll-InfographicFINAL.pdf">Google</Link></Text>
</Stack>
<Stack className="containerItem" tokens={ { childrenGap: 10, maxWidth: 600 } } >
<Text variant="xxLarge">{ Strings.startPrereqHeader }</Text>
<Stack
tokens={ { childrenGap: 20, padding: 20 } }
styles={ { root: { boxShadow: Depths.depth8, borderRadius: 6 } } } >
<Stack tokens={ { childrenGap: 10 } }>
<Text variant="large">{ Strings.startPrereq1TitleP1 } <Link target="_blank" href="https://ezlog.app/#download">{ Strings.startPrereq1TitleP2 }</Link> { Strings.startPrereq1TitleP3 }</Text>
<MessageBar>{ Strings.startPrereq1CaptionP1 } <b>Android</b> { Strings.startPrereq1CaptionP2 }</MessageBar>
</Stack>
<Stack>
<Text variant="large">{ Strings.startPrereq2Title }</Text>
<Text>{ Strings.startPrereq2Caption }</Text>
</Stack>
<Stack horizontalAlign="center">
<Text variant="mediumPlus">{ Strings.startPrereqLogin }: test-user@example.com</Text>
<Text variant="mediumPlus">{ Strings.startPrereqPassword }: .tSYRc:eYP_868p</Text>
</Stack>
<Stack horizontalAlign="center">
<PrimaryButton text={ Strings.startPrereqStart } onClick={ () => this.props.onNext() } />
</Stack>
</Stack>
</Stack>
</Stack>
);
}
}
+38
View File
@@ -0,0 +1,38 @@
import ReactDOM from "react-dom";
import App from "./App";
import { initializeIcons, mergeStyles } from "@fluentui/react";
import reportWebVitals from "./reportWebVitals";
import { Strings } from "./Services/LocalizationService";
initializeIcons();
UpdateLocale();
// Inject some global styles
mergeStyles({
":global(body, html, #root, #root > div)": {
margin: 0,
padding: 0,
height: "100vh",
userSelect: "none"
}
});
ReactDOM.render(<App />, document.getElementById("root"));
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
function UpdateLocale() : void
{
// CIS countries languages ()
let ruLanguages : string[] = [ "ru", "uk", "be", "kk", "ky", "ab", "mo", "my", "uz" ];
if (window.location.pathname.startsWith("/en"))
return;
if (ruLanguages.includes(navigator.language.substring(0, 2)) || window.location.pathname.startsWith("/ru"))
Strings.setLanguage("ru");
}
+52
View File
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 25 KiB

+1
View File
@@ -0,0 +1 @@
/// <reference types="react-scripts" />
+18
View File
@@ -0,0 +1,18 @@
import { ReportHandler } from "web-vitals";
const reportWebVitals = (onPerfEntry?: ReportHandler) =>
{
if (onPerfEntry && onPerfEntry instanceof Function)
{
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) =>
{
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);
getLCP(onPerfEntry);
getTTFB(onPerfEntry);
});
}
};
export default reportWebVitals;
+5
View File
@@ -0,0 +1,5 @@
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import "@testing-library/jest-dom";
+27
View File
@@ -0,0 +1,27 @@
{
"compilerOptions":
{
"target": "es5",
"lib":
[
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"strictNullChecks": false,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": [ "src" ]
}
+9293
View File
File diff suppressed because it is too large Load Diff