feat: Purchase "Create account" & "Sign in" flows and Floating label input (#672)
This commit is contained in:
@@ -64,6 +64,7 @@ import { IconDirective } from './components/Icon';
|
||||
import { NoteTagsContainerDirective } from './components/NoteTagsContainer';
|
||||
import { PreferencesDirective } from './preferences';
|
||||
import { AppVersion, IsWebPlatform } from '@/version';
|
||||
import { PurchaseFlowDirective } from './purchaseFlow';
|
||||
import { QuickSettingsMenuDirective } from './components/QuickSettingsMenu';
|
||||
|
||||
function reloadHiddenFirefoxTab(): boolean {
|
||||
@@ -165,7 +166,8 @@ const startApplication: StartApplication = async function startApplication(
|
||||
.directive('notesOptionsPanel', NotesOptionsPanelDirective)
|
||||
.directive('icon', IconDirective)
|
||||
.directive('noteTagsContainer', NoteTagsContainerDirective)
|
||||
.directive('preferences', PreferencesDirective);
|
||||
.directive('preferences', PreferencesDirective)
|
||||
.directive('purchaseFlow', PurchaseFlowDirective);
|
||||
|
||||
// Filters
|
||||
angular.module('app').filter('trusted', ['$sce', trusted]);
|
||||
|
||||
75
app/assets/javascripts/components/FloatingLabelInput.tsx
Normal file
75
app/assets/javascripts/components/FloatingLabelInput.tsx
Normal file
@@ -0,0 +1,75 @@
|
||||
import { FunctionComponent, Ref } from 'preact';
|
||||
import { JSXInternal } from 'preact/src/jsx';
|
||||
import { forwardRef } from 'preact/compat';
|
||||
import { useState } from 'preact/hooks';
|
||||
|
||||
type Props = {
|
||||
id: string;
|
||||
type: 'text' | 'email' | 'password'; // Have no use cases for other types so far
|
||||
label: string;
|
||||
value: string;
|
||||
onChange: JSXInternal.GenericEventHandler<HTMLInputElement>;
|
||||
disabled?: boolean;
|
||||
className?: string;
|
||||
labelClassName?: string;
|
||||
inputClassName?: string;
|
||||
isInvalid?: boolean;
|
||||
};
|
||||
|
||||
export const FloatingLabelInput: FunctionComponent<Props> = forwardRef(
|
||||
(
|
||||
{
|
||||
id,
|
||||
type,
|
||||
label,
|
||||
disabled,
|
||||
value,
|
||||
isInvalid,
|
||||
onChange,
|
||||
className = '',
|
||||
labelClassName = '',
|
||||
inputClassName = '',
|
||||
},
|
||||
ref: Ref<HTMLInputElement>
|
||||
) => {
|
||||
const [focused, setFocused] = useState(false);
|
||||
|
||||
const BASE_CLASSNAME = `relative bg-default`;
|
||||
|
||||
const LABEL_CLASSNAME = `hidden absolute ${
|
||||
!focused ? 'color-neutral' : 'color-info'
|
||||
} ${focused || value ? 'flex top-0 left-2 pt-1.5 px-1' : ''} ${
|
||||
isInvalid ? 'color-dark-red' : ''
|
||||
} ${labelClassName}`;
|
||||
|
||||
const INPUT_CLASSNAME = `w-full h-full ${
|
||||
focused || value ? 'pt-6 pb-2' : 'py-2.5'
|
||||
} px-3 text-input border-1 border-solid border-gray-300 rounded placeholder-medium text-input focus:ring-info ${
|
||||
isInvalid ? 'border-dark-red placeholder-dark-red' : ''
|
||||
} ${inputClassName}`;
|
||||
|
||||
const handleFocus = () => setFocused(true);
|
||||
|
||||
const handleBlur = () => setFocused(false);
|
||||
|
||||
return (
|
||||
<div className={`${BASE_CLASSNAME} ${className}`}>
|
||||
<label htmlFor={id} className={LABEL_CLASSNAME}>
|
||||
{label}
|
||||
</label>
|
||||
<input
|
||||
id={id}
|
||||
className={INPUT_CLASSNAME}
|
||||
placeholder={!focused ? label : ''}
|
||||
type={type}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
onFocus={handleFocus}
|
||||
onBlur={handleBlur}
|
||||
ref={ref}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
68
app/assets/javascripts/purchaseFlow/PurchaseFlowView.tsx
Normal file
68
app/assets/javascripts/purchaseFlow/PurchaseFlowView.tsx
Normal file
@@ -0,0 +1,68 @@
|
||||
import { WebApplication } from '@/ui_models/application';
|
||||
import { AppState } from '@/ui_models/app_state';
|
||||
import { PurchaseFlowPane } from '@/ui_models/app_state/purchase_flow_state';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { FunctionComponent } from 'preact';
|
||||
import { CreateAccount } from './panes/CreateAccount';
|
||||
import { SignIn } from './panes/SignIn';
|
||||
import SNLogoFull from '../../svg/ic-sn-logo-full.svg';
|
||||
|
||||
type PaneSelectorProps = {
|
||||
currentPane: PurchaseFlowPane;
|
||||
} & PurchaseFlowViewProps;
|
||||
|
||||
type PurchaseFlowViewProps = {
|
||||
appState: AppState;
|
||||
application: WebApplication;
|
||||
};
|
||||
|
||||
const PurchaseFlowPaneSelector: FunctionComponent<PaneSelectorProps> = ({
|
||||
currentPane,
|
||||
appState,
|
||||
application,
|
||||
}) => {
|
||||
switch (currentPane) {
|
||||
case PurchaseFlowPane.CreateAccount:
|
||||
return <CreateAccount appState={appState} application={application} />;
|
||||
case PurchaseFlowPane.SignIn:
|
||||
return <SignIn appState={appState} application={application} />;
|
||||
}
|
||||
};
|
||||
|
||||
export const PurchaseFlowView: FunctionComponent<PurchaseFlowViewProps> =
|
||||
observer(({ appState, application }) => {
|
||||
const { currentPane } = appState.purchaseFlow;
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-center h-full w-full absolute top-left-0 z-index-purchase-flow bg-grey-2">
|
||||
<div className="relative fit-content">
|
||||
<div className="relative p-12 mb-4 bg-default border-1 border-solid border-gray-300 rounded">
|
||||
<SNLogoFull className="mb-5" />
|
||||
<PurchaseFlowPaneSelector
|
||||
currentPane={currentPane}
|
||||
appState={appState}
|
||||
application={application}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-end">
|
||||
<a
|
||||
className="mr-3 font-medium color-grey-1"
|
||||
href="https://standardnotes.com/privacy"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Privacy
|
||||
</a>
|
||||
<a
|
||||
className="font-medium color-grey-1"
|
||||
href="https://standardnotes.com/help"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Help
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
33
app/assets/javascripts/purchaseFlow/PurchaseFlowWrapper.tsx
Normal file
33
app/assets/javascripts/purchaseFlow/PurchaseFlowWrapper.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import { WebApplication } from '@/ui_models/application';
|
||||
import { AppState } from '@/ui_models/app_state';
|
||||
import { isDesktopApplication } from '@/utils';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { FunctionComponent } from 'preact';
|
||||
import { PurchaseFlowView } from './PurchaseFlowView';
|
||||
|
||||
export type PurchaseFlowWrapperProps = {
|
||||
appState: AppState;
|
||||
application: WebApplication;
|
||||
};
|
||||
|
||||
export const loadPurchaseFlowUrl = async (
|
||||
application: WebApplication
|
||||
): Promise<void> => {
|
||||
const url = await application.getPurchaseFlowUrl();
|
||||
if (url) {
|
||||
const currentUrl = window.location.href.split('/?')[0];
|
||||
const successUrl = isDesktopApplication()
|
||||
? `standardnotes://${currentUrl}`
|
||||
: currentUrl;
|
||||
window.location.assign(`${url}&success_url=${successUrl}`);
|
||||
}
|
||||
};
|
||||
|
||||
export const PurchaseFlowWrapper: FunctionComponent<PurchaseFlowWrapperProps> =
|
||||
observer(({ appState, application }) => {
|
||||
if (!appState.purchaseFlow.isOpen) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <PurchaseFlowView appState={appState} application={application} />;
|
||||
});
|
||||
8
app/assets/javascripts/purchaseFlow/index.ts
Normal file
8
app/assets/javascripts/purchaseFlow/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { toDirective } from '@/components/utils';
|
||||
import {
|
||||
PurchaseFlowWrapper,
|
||||
PurchaseFlowWrapperProps,
|
||||
} from './PurchaseFlowWrapper';
|
||||
|
||||
export const PurchaseFlowDirective =
|
||||
toDirective<PurchaseFlowWrapperProps>(PurchaseFlowWrapper);
|
||||
200
app/assets/javascripts/purchaseFlow/panes/CreateAccount.tsx
Normal file
200
app/assets/javascripts/purchaseFlow/panes/CreateAccount.tsx
Normal file
@@ -0,0 +1,200 @@
|
||||
import { Button } from '@/components/Button';
|
||||
import { WebApplication } from '@/ui_models/application';
|
||||
import { AppState } from '@/ui_models/app_state';
|
||||
import { PurchaseFlowPane } from '@/ui_models/app_state/purchase_flow_state';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { FunctionComponent } from 'preact';
|
||||
import { useEffect, useRef, useState } from 'preact/hooks';
|
||||
import Illustration from '../../../svg/create-account-illustration.svg';
|
||||
import Circle from '../../../svg/circle-55.svg';
|
||||
import BlueDot from '../../../svg/blue-dot.svg';
|
||||
import Diamond from '../../../svg/diamond-with-horizontal-lines.svg';
|
||||
import { FloatingLabelInput } from '@/components/FloatingLabelInput';
|
||||
import { isEmailValid } from '@/utils';
|
||||
import { loadPurchaseFlowUrl } from '../PurchaseFlowWrapper';
|
||||
|
||||
type Props = {
|
||||
appState: AppState;
|
||||
application: WebApplication;
|
||||
};
|
||||
|
||||
export const CreateAccount: FunctionComponent<Props> = observer(
|
||||
({ appState, application }) => {
|
||||
const { setCurrentPane } = appState.purchaseFlow;
|
||||
const [email, setEmail] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [confirmPassword, setConfirmPassword] = useState('');
|
||||
const [isCreatingAccount, setIsCreatingAccount] = useState(false);
|
||||
const [isEmailInvalid, setIsEmailInvalid] = useState(false);
|
||||
const [isPasswordNotMatching, setIsPasswordNotMatching] = useState(false);
|
||||
|
||||
const emailInputRef = useRef<HTMLInputElement>();
|
||||
const passwordInputRef = useRef<HTMLInputElement>();
|
||||
const confirmPasswordInputRef = useRef<HTMLInputElement>();
|
||||
|
||||
useEffect(() => {
|
||||
if (emailInputRef.current) emailInputRef.current.focus();
|
||||
}, []);
|
||||
|
||||
const handleEmailChange = (e: Event) => {
|
||||
if (e.target instanceof HTMLInputElement) {
|
||||
setEmail(e.target.value);
|
||||
setIsEmailInvalid(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePasswordChange = (e: Event) => {
|
||||
if (e.target instanceof HTMLInputElement) {
|
||||
setPassword(e.target.value);
|
||||
}
|
||||
};
|
||||
|
||||
const handleConfirmPasswordChange = (e: Event) => {
|
||||
if (e.target instanceof HTMLInputElement) {
|
||||
setConfirmPassword(e.target.value);
|
||||
setIsPasswordNotMatching(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSignInInstead = () => {
|
||||
setCurrentPane(PurchaseFlowPane.SignIn);
|
||||
};
|
||||
|
||||
const handleCreateAccount = async () => {
|
||||
if (!email) {
|
||||
emailInputRef?.current.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isEmailValid(email)) {
|
||||
setIsEmailInvalid(true);
|
||||
emailInputRef?.current.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!password) {
|
||||
passwordInputRef?.current.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!confirmPassword) {
|
||||
confirmPasswordInputRef?.current.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
if (password !== confirmPassword) {
|
||||
setConfirmPassword('');
|
||||
setIsPasswordNotMatching(true);
|
||||
confirmPasswordInputRef?.current.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
setIsCreatingAccount(true);
|
||||
|
||||
try {
|
||||
const response = await application.register(email, password);
|
||||
if (response.error || response.data?.error) {
|
||||
throw new Error(
|
||||
response.error?.message || response.data?.error?.message
|
||||
);
|
||||
} else {
|
||||
loadPurchaseFlowUrl(application).catch((err) => {
|
||||
console.error(err);
|
||||
application.alertService.alert(err);
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
application.alertService.alert(err as string);
|
||||
} finally {
|
||||
setIsCreatingAccount(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
<Circle className="absolute w-8 h-8 top-40% -left-28" />
|
||||
<BlueDot className="absolute w-4 h-4 top-35% -left-10" />
|
||||
<Diamond className="absolute w-26 h-26 -bottom-5 left-0 -translate-x-1/2 -z-index-1" />
|
||||
|
||||
<Circle className="absolute w-8 h-8 bottom-35% -right-20" />
|
||||
<BlueDot className="absolute w-4 h-4 bottom-25% -right-10" />
|
||||
<Diamond className="absolute w-18 h-18 top-0 -right-2 translate-x-1/2 -z-index-1" />
|
||||
|
||||
<div className="mr-12">
|
||||
<h1 className="mt-0 mb-2 text-2xl">Create your free account</h1>
|
||||
<div className="mb-4 font-medium text-sm">
|
||||
to continue to Standard Notes.
|
||||
</div>
|
||||
<form onSubmit={handleCreateAccount}>
|
||||
<div className="flex flex-col">
|
||||
<FloatingLabelInput
|
||||
className={`min-w-90 ${isEmailInvalid ? 'mb-2' : 'mb-4'}`}
|
||||
id="purchase-sign-in-email"
|
||||
type="email"
|
||||
label="Email"
|
||||
value={email}
|
||||
onChange={handleEmailChange}
|
||||
ref={emailInputRef}
|
||||
disabled={isCreatingAccount}
|
||||
isInvalid={isEmailInvalid}
|
||||
/>
|
||||
{isEmailInvalid ? (
|
||||
<div className="color-dark-red mb-4">
|
||||
Please provide a valid email.
|
||||
</div>
|
||||
) : null}
|
||||
<FloatingLabelInput
|
||||
className="min-w-90 mb-4"
|
||||
id="purchase-create-account-password"
|
||||
type="password"
|
||||
label="Password"
|
||||
value={password}
|
||||
onChange={handlePasswordChange}
|
||||
ref={passwordInputRef}
|
||||
disabled={isCreatingAccount}
|
||||
/>
|
||||
<FloatingLabelInput
|
||||
className={`min-w-90 ${
|
||||
isPasswordNotMatching ? 'mb-2' : 'mb-4'
|
||||
}`}
|
||||
id="create-account-confirm"
|
||||
type="password"
|
||||
label="Repeat password"
|
||||
value={confirmPassword}
|
||||
onChange={handleConfirmPasswordChange}
|
||||
ref={confirmPasswordInputRef}
|
||||
disabled={isCreatingAccount}
|
||||
isInvalid={isPasswordNotMatching}
|
||||
/>
|
||||
{isPasswordNotMatching ? (
|
||||
<div className="color-dark-red mb-4">
|
||||
Passwords don't match. Please try again.
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</form>
|
||||
<div className="flex justify-between">
|
||||
<button
|
||||
onClick={handleSignInInstead}
|
||||
disabled={isCreatingAccount}
|
||||
className="p-0 bg-default border-0 font-medium color-info cursor-pointer hover:underline"
|
||||
>
|
||||
Sign in instead
|
||||
</button>
|
||||
<Button
|
||||
className="py-2.5"
|
||||
type="primary"
|
||||
label={
|
||||
isCreatingAccount ? 'Creating account...' : 'Create account'
|
||||
}
|
||||
onClick={handleCreateAccount}
|
||||
disabled={isCreatingAccount}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Illustration />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
175
app/assets/javascripts/purchaseFlow/panes/SignIn.tsx
Normal file
175
app/assets/javascripts/purchaseFlow/panes/SignIn.tsx
Normal file
@@ -0,0 +1,175 @@
|
||||
import { Button } from '@/components/Button';
|
||||
import { WebApplication } from '@/ui_models/application';
|
||||
import { AppState } from '@/ui_models/app_state';
|
||||
import { PurchaseFlowPane } from '@/ui_models/app_state/purchase_flow_state';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { FunctionComponent } from 'preact';
|
||||
import { useEffect, useRef, useState } from 'preact/hooks';
|
||||
import Circle from '../../../svg/circle-55.svg';
|
||||
import BlueDot from '../../../svg/blue-dot.svg';
|
||||
import Diamond from '../../../svg/diamond-with-horizontal-lines.svg';
|
||||
import { FloatingLabelInput } from '@/components/FloatingLabelInput';
|
||||
import { isEmailValid } from '@/utils';
|
||||
import { loadPurchaseFlowUrl } from '../PurchaseFlowWrapper';
|
||||
|
||||
type Props = {
|
||||
appState: AppState;
|
||||
application: WebApplication;
|
||||
};
|
||||
|
||||
export const SignIn: FunctionComponent<Props> = observer(
|
||||
({ appState, application }) => {
|
||||
const { setCurrentPane } = appState.purchaseFlow;
|
||||
const [email, setEmail] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [isSigningIn, setIsSigningIn] = useState(false);
|
||||
const [isEmailInvalid, setIsEmailInvalid] = useState(false);
|
||||
const [isPasswordInvalid, setIsPasswordInvalid] = useState(false);
|
||||
const [otherErrorMessage, setOtherErrorMessage] = useState('');
|
||||
|
||||
const emailInputRef = useRef<HTMLInputElement>();
|
||||
const passwordInputRef = useRef<HTMLInputElement>();
|
||||
|
||||
useEffect(() => {
|
||||
if (emailInputRef.current) emailInputRef.current.focus();
|
||||
}, []);
|
||||
|
||||
const handleEmailChange = (e: Event) => {
|
||||
if (e.target instanceof HTMLInputElement) {
|
||||
setEmail(e.target.value);
|
||||
setIsEmailInvalid(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePasswordChange = (e: Event) => {
|
||||
if (e.target instanceof HTMLInputElement) {
|
||||
setPassword(e.target.value);
|
||||
setIsPasswordInvalid(false);
|
||||
setOtherErrorMessage('');
|
||||
}
|
||||
};
|
||||
|
||||
const handleCreateAccountInstead = () => {
|
||||
if (isSigningIn) return;
|
||||
setCurrentPane(PurchaseFlowPane.CreateAccount);
|
||||
};
|
||||
|
||||
const handleSignIn = async () => {
|
||||
if (!email) {
|
||||
emailInputRef?.current.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isEmailValid(email)) {
|
||||
setIsEmailInvalid(true);
|
||||
emailInputRef?.current.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!password) {
|
||||
passwordInputRef?.current.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
setIsSigningIn(true);
|
||||
|
||||
try {
|
||||
const response = await application.signIn(email, password);
|
||||
if (response.error || response.data?.error) {
|
||||
throw new Error(
|
||||
response.error?.message || response.data?.error?.message
|
||||
);
|
||||
} else {
|
||||
loadPurchaseFlowUrl(application).catch((err) => {
|
||||
console.error(err);
|
||||
application.alertService.alert(err);
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
if ((err as Error).toString().includes('Invalid email or password')) {
|
||||
setIsSigningIn(false);
|
||||
setIsEmailInvalid(true);
|
||||
setIsPasswordInvalid(true);
|
||||
setOtherErrorMessage('Invalid email or password.');
|
||||
setPassword('');
|
||||
} else {
|
||||
application.alertService.alert(err as string);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
<Circle className="absolute w-8 h-8 top-35% -left-56" />
|
||||
<BlueDot className="absolute w-4 h-4 top-30% -left-40" />
|
||||
<Diamond className="absolute w-26 h-26 -bottom-5 left-0 -translate-x-1/2 -z-index-1" />
|
||||
|
||||
<Circle className="absolute w-8 h-8 bottom-30% -right-56" />
|
||||
<BlueDot className="absolute w-4 h-4 bottom-20% -right-44" />
|
||||
<Diamond className="absolute w-18 h-18 top-0 -right-2 translate-x-1/2 -z-index-1" />
|
||||
|
||||
<div>
|
||||
<h1 className="mt-0 mb-2 text-2xl">Sign in</h1>
|
||||
<div className="mb-4 font-medium text-sm">
|
||||
to continue to Standard Notes.
|
||||
</div>
|
||||
<form onSubmit={handleSignIn}>
|
||||
<div className="flex flex-col">
|
||||
<FloatingLabelInput
|
||||
className={`min-w-90 ${
|
||||
isEmailInvalid && !otherErrorMessage ? 'mb-2' : 'mb-4'
|
||||
}`}
|
||||
id="purchase-sign-in-email"
|
||||
type="email"
|
||||
label="Email"
|
||||
value={email}
|
||||
onChange={handleEmailChange}
|
||||
ref={emailInputRef}
|
||||
disabled={isSigningIn}
|
||||
isInvalid={isEmailInvalid}
|
||||
/>
|
||||
{isEmailInvalid && !otherErrorMessage ? (
|
||||
<div className="color-dark-red mb-4">
|
||||
Please provide a valid email.
|
||||
</div>
|
||||
) : null}
|
||||
<FloatingLabelInput
|
||||
className={`min-w-90 ${otherErrorMessage ? 'mb-2' : 'mb-4'}`}
|
||||
id="purchase-sign-in-password"
|
||||
type="password"
|
||||
label="Password"
|
||||
value={password}
|
||||
onChange={handlePasswordChange}
|
||||
ref={passwordInputRef}
|
||||
disabled={isSigningIn}
|
||||
isInvalid={isPasswordInvalid}
|
||||
/>
|
||||
{otherErrorMessage ? (
|
||||
<div className="color-dark-red mb-4">{otherErrorMessage}</div>
|
||||
) : null}
|
||||
</div>
|
||||
<Button
|
||||
className={`${isSigningIn ? 'min-w-30' : 'min-w-24'} py-2.5 mb-5`}
|
||||
type="primary"
|
||||
label={isSigningIn ? 'Signing in...' : 'Sign in'}
|
||||
onClick={handleSignIn}
|
||||
disabled={isSigningIn}
|
||||
/>
|
||||
</form>
|
||||
<div className="text-sm font-medium color-grey-1">
|
||||
Don’t have an account yet?{' '}
|
||||
<a
|
||||
className={`color-info ${
|
||||
isSigningIn ? 'cursor-not-allowed' : 'cursor-pointer '
|
||||
}`}
|
||||
onClick={handleCreateAccountInstead}
|
||||
>
|
||||
Create account
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
@@ -23,6 +23,7 @@ import { NotesState } from './notes_state';
|
||||
import { TagsState } from './tags_state';
|
||||
import { AccountMenuState } from '@/ui_models/app_state/account_menu_state';
|
||||
import { PreferencesState } from './preferences_state';
|
||||
import { PurchaseFlowState } from './purchase_flow_state';
|
||||
import { QuickSettingsState } from './quick_settings_state';
|
||||
|
||||
export enum AppStateEvent {
|
||||
@@ -67,6 +68,7 @@ export class AppState {
|
||||
readonly accountMenu: AccountMenuState;
|
||||
readonly actionsMenu = new ActionsMenuState();
|
||||
readonly preferences = new PreferencesState();
|
||||
readonly purchaseFlow: PurchaseFlowState;
|
||||
readonly noAccountWarning: NoAccountWarningState;
|
||||
readonly noteTags: NoteTagsState;
|
||||
readonly sync = new SyncState();
|
||||
@@ -113,6 +115,7 @@ export class AppState {
|
||||
application,
|
||||
this.appEventObserverRemovers
|
||||
);
|
||||
this.purchaseFlow = new PurchaseFlowState(application);
|
||||
this.addAppEventObserver();
|
||||
this.streamNotesAndTags();
|
||||
this.onVisibilityChange = () => {
|
||||
@@ -284,6 +287,8 @@ export class AppState {
|
||||
break;
|
||||
case ApplicationEvent.Launched:
|
||||
this.locked = false;
|
||||
if (window.location.search.includes('purchase=true'))
|
||||
this.purchaseFlow.openPurchaseFlow();
|
||||
break;
|
||||
case ApplicationEvent.SyncStatusChanged:
|
||||
this.sync.update(this.application.getSyncStatus());
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import { action, makeObservable, observable } from 'mobx';
|
||||
import { WebApplication } from '../application';
|
||||
|
||||
export enum PurchaseFlowPane {
|
||||
SignIn,
|
||||
CreateAccount,
|
||||
}
|
||||
|
||||
export class PurchaseFlowState {
|
||||
isOpen = false;
|
||||
currentPane = PurchaseFlowPane.CreateAccount;
|
||||
|
||||
constructor(private application: WebApplication) {
|
||||
makeObservable(this, {
|
||||
isOpen: observable,
|
||||
currentPane: observable,
|
||||
|
||||
setCurrentPane: action,
|
||||
openPurchaseFlow: action,
|
||||
closePurchaseFlow: action,
|
||||
});
|
||||
}
|
||||
|
||||
setCurrentPane = (currentPane: PurchaseFlowPane): void => {
|
||||
this.currentPane = currentPane;
|
||||
};
|
||||
|
||||
openPurchaseFlow = (): void => {
|
||||
const user = this.application.getUser();
|
||||
if (!user) {
|
||||
this.isOpen = true;
|
||||
}
|
||||
};
|
||||
|
||||
closePurchaseFlow = (): void => {
|
||||
this.isOpen = false;
|
||||
};
|
||||
}
|
||||
@@ -41,4 +41,7 @@
|
||||
application='self.application'
|
||||
app-state='self.appState'
|
||||
)
|
||||
|
||||
purchase-flow(
|
||||
application='self.application'
|
||||
app-state='self.appState'
|
||||
)
|
||||
|
||||
@@ -14,6 +14,8 @@ $z-index-footer-bar-item-panel: 2000;
|
||||
|
||||
$z-index-preferences: 3000;
|
||||
|
||||
$z-index-purchase-flow: 4000;
|
||||
|
||||
$z-index-lock-screen: 10000;
|
||||
$z-index-modal: 10000;
|
||||
|
||||
@@ -244,3 +246,7 @@ $footer-height: 2rem;
|
||||
.z-index-preferences {
|
||||
z-index: $z-index-preferences;
|
||||
}
|
||||
|
||||
.z-index-purchase-flow {
|
||||
z-index: $z-index-purchase-flow;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,21 @@
|
||||
/* Components and utilities that are good candidates for extraction to StyleKit. */
|
||||
|
||||
:root {
|
||||
--sn-stylekit-grey-2: #f8f9fc;
|
||||
}
|
||||
|
||||
.bg-grey-2 {
|
||||
background-color: var(--sn-stylekit-grey-2);
|
||||
}
|
||||
|
||||
.h-90vh {
|
||||
height: 90vh;
|
||||
}
|
||||
|
||||
.h-26 {
|
||||
width: 6.5rem;
|
||||
}
|
||||
|
||||
.h-33 {
|
||||
height: 8.25rem;
|
||||
}
|
||||
@@ -219,6 +231,10 @@
|
||||
margin-right: 0.75rem;
|
||||
}
|
||||
|
||||
.mr-12 {
|
||||
margin-right: 3rem;
|
||||
}
|
||||
|
||||
.my-0\.5 {
|
||||
margin-top: 0.125rem;
|
||||
margin-bottom: 0.125rem;
|
||||
@@ -234,14 +250,30 @@
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.mt-0 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.mb-2 {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.mb-4 {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.mb-5 {
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
.max-w-89 {
|
||||
max-width: 22.25rem;
|
||||
}
|
||||
|
||||
.w-26 {
|
||||
width: 6.5rem;
|
||||
}
|
||||
|
||||
.w-92 {
|
||||
width: 23rem;
|
||||
}
|
||||
@@ -274,6 +306,18 @@
|
||||
min-width: 3.75rem;
|
||||
}
|
||||
|
||||
.min-w-24 {
|
||||
min-width: 6rem;
|
||||
}
|
||||
|
||||
.min-w-30 {
|
||||
min-width: 7.5rem;
|
||||
}
|
||||
|
||||
.min-w-90 {
|
||||
min-width: 22.5rem;
|
||||
}
|
||||
|
||||
.min-h-1px {
|
||||
min-height: 1px;
|
||||
}
|
||||
@@ -314,6 +358,18 @@
|
||||
color: var(--sn-stylekit-background-color);
|
||||
}
|
||||
|
||||
.p-1 {
|
||||
padding: 0.25rem;
|
||||
}
|
||||
|
||||
.p-8 {
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.p-12 {
|
||||
padding: 3rem;
|
||||
}
|
||||
|
||||
.pt-1 {
|
||||
padding-top: 0.25rem;
|
||||
}
|
||||
@@ -326,10 +382,26 @@
|
||||
padding-top: 0.5rem;
|
||||
}
|
||||
|
||||
.pt-3 {
|
||||
padding-top: 0.75rem;
|
||||
}
|
||||
|
||||
.pt-6 {
|
||||
padding-top: 1.5rem;
|
||||
}
|
||||
|
||||
.pb-1 {
|
||||
padding-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.pb-2 {
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.pb-2\.5 {
|
||||
padding-bottom: 0.625rem;
|
||||
}
|
||||
|
||||
.px-9 {
|
||||
padding-left: 2.25rem;
|
||||
padding-right: 2.25rem;
|
||||
@@ -340,6 +412,11 @@
|
||||
padding-right: 3rem;
|
||||
}
|
||||
|
||||
.sn-component .py-2\.5 {
|
||||
padding-top: 0.625rem;
|
||||
padding-bottom: 0.625rem;
|
||||
}
|
||||
|
||||
.py-9 {
|
||||
padding-top: 2.25rem;
|
||||
padding-bottom: 2.25rem;
|
||||
@@ -349,6 +426,118 @@
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.placeholder-dark-red::placeholder {
|
||||
@extend .color-dark-red;
|
||||
}
|
||||
|
||||
.placeholder-medium::placeholder {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.top-30\% {
|
||||
top: 30%;
|
||||
}
|
||||
|
||||
.top-35\% {
|
||||
top: 35%;
|
||||
}
|
||||
|
||||
.top-40\% {
|
||||
top: 40%;
|
||||
}
|
||||
|
||||
.-top-0\.25 {
|
||||
top: -0.0625rem;
|
||||
}
|
||||
|
||||
.bottom-20\% {
|
||||
bottom: 20%;
|
||||
}
|
||||
|
||||
.bottom-25\% {
|
||||
bottom: 25%;
|
||||
}
|
||||
|
||||
.bottom-30\% {
|
||||
bottom: 30%;
|
||||
}
|
||||
|
||||
.bottom-35\% {
|
||||
bottom: 35%;
|
||||
}
|
||||
|
||||
.bottom-40\% {
|
||||
bottom: 40%;
|
||||
}
|
||||
|
||||
.left-2 {
|
||||
left: 0.5rem;
|
||||
}
|
||||
|
||||
.-left-10 {
|
||||
left: -2.5rem;
|
||||
}
|
||||
|
||||
.-left-28 {
|
||||
left: -7rem;
|
||||
}
|
||||
|
||||
.-left-16 {
|
||||
left: -4rem;
|
||||
}
|
||||
|
||||
.-left-40 {
|
||||
left: -10rem;
|
||||
}
|
||||
|
||||
.-left-56 {
|
||||
left: -14rem;
|
||||
}
|
||||
|
||||
.-right-2 {
|
||||
right: -0.5rem;
|
||||
}
|
||||
|
||||
.-right-10 {
|
||||
right: -2.5rem;
|
||||
}
|
||||
|
||||
.-right-20 {
|
||||
right: -5rem;
|
||||
}
|
||||
|
||||
.-right-24 {
|
||||
right: -6rem;
|
||||
}
|
||||
|
||||
.-right-44 {
|
||||
right: -11rem;
|
||||
}
|
||||
|
||||
.-right-56 {
|
||||
right: -14rem;
|
||||
}
|
||||
|
||||
.-translate-x-1\/2 {
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.-translate-y-1\/2 {
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.translate-x-1\/2 {
|
||||
transform: translateX(50%);
|
||||
}
|
||||
|
||||
.-bottom-5 {
|
||||
bottom: -1.25rem;
|
||||
}
|
||||
|
||||
.-z-index-1 {
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.sn-component .btn-w-full {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
3
app/assets/svg/blue-dot.svg
Normal file
3
app/assets/svg/blue-dot.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9.37156 12.7469C12.5261 11.4593 14.0402 7.85996 12.7534 4.70768C11.4666 1.5554 7.86624 0.0438474 4.71174 1.33153C1.55723 2.61921 0.0431373 6.21851 1.32991 9.37079C2.61669 12.5231 6.21706 14.0346 9.37156 12.7469Z" fill="#BED7FE"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 324 B |
11
app/assets/svg/circle-55.svg
Normal file
11
app/assets/svg/circle-55.svg
Normal file
@@ -0,0 +1,11 @@
|
||||
<svg viewBox="0 0 55 55" fill="none" xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<rect opacity="0.36" width="54.5639" height="54.5639" rx="27.282" fill="url(#pattern0)"/>
|
||||
<defs>
|
||||
<pattern id="pattern0" patternContentUnits="objectBoundingBox" width="0.0824721" height="0.0824721">
|
||||
<use xlink:href="#image0" transform="scale(0.00916356)"/>
|
||||
</pattern>
|
||||
<image id="image0" width="9" height="9"
|
||||
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAtSURBVHgB1dChDQAACAPBHwn23wVWAYMkxdKkVacKkF1jj80oyDfoFxgD1UdRPHMLOJmKMAEAAAAASUVORK5CYII="/>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 748 B |
245
app/assets/svg/create-account-illustration.svg
Normal file
245
app/assets/svg/create-account-illustration.svg
Normal file
@@ -0,0 +1,245 @@
|
||||
<svg
|
||||
width="320"
|
||||
height="292"
|
||||
viewBox="0 0 320 292"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
>
|
||||
<path
|
||||
opacity="0.36"
|
||||
d="M28.293 250.445C28.293 250.445 147.618 182.328 291.708 250.445H28.293Z"
|
||||
fill="url(#pattern0)"
|
||||
/>
|
||||
<path
|
||||
d="M91.8025 33.6013L28.6904 47.8847C26.1046 48.4699 24.4827 51.0406 25.0679 53.6264L46.768 149.51C47.3532 152.096 49.9239 153.718 52.5098 153.132L115.622 138.849C118.208 138.264 119.83 135.693 119.244 133.107L97.5443 37.2238C96.959 34.6379 94.3884 33.0161 91.8025 33.6013Z"
|
||||
fill="#3F3D56"
|
||||
/>
|
||||
<path
|
||||
d="M31.2991 65.229C32.4831 65.229 33.4429 64.2692 33.4429 63.0852C33.4429 61.9012 32.4831 60.9414 31.2991 60.9414C30.1151 60.9414 29.1553 61.9012 29.1553 63.0852C29.1553 64.2692 30.1151 65.229 31.2991 65.229Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
d="M33.4426 73.0904C34.6266 73.0904 35.5865 72.1306 35.5865 70.9466C35.5865 69.7626 34.6266 68.8027 33.4426 68.8027C32.2586 68.8027 31.2988 69.7626 31.2988 70.9466C31.2988 72.1306 32.2586 73.0904 33.4426 73.0904Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
d="M47.3772 130.616C48.5612 130.616 49.521 129.656 49.521 128.472C49.521 127.288 48.5612 126.328 47.3772 126.328C46.1932 126.328 45.2334 127.288 45.2334 128.472C45.2334 129.656 46.1932 130.616 47.3772 130.616Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
d="M49.5213 138.476C50.7053 138.476 51.6651 137.516 51.6651 136.332C51.6651 135.148 50.7053 134.188 49.5213 134.188C48.3373 134.188 47.3774 135.148 47.3774 136.332C47.3774 137.516 48.3373 138.476 49.5213 138.476Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
d="M91.5362 73.4773L48.0181 83.3262L49.6596 90.5792L93.1777 80.7303L91.5362 73.4773Z"
|
||||
fill="#F5F5F5"
|
||||
/>
|
||||
<path
|
||||
d="M149.289 70.2477L76.813 86.6504L100.632 191.898L173.109 175.495L149.289 70.2477Z"
|
||||
fill="#2B6FCF"
|
||||
/>
|
||||
<path
|
||||
d="M225.719 52.9509L153.243 69.3535L177.062 174.601L249.538 158.198L225.719 52.9509Z"
|
||||
fill="#3F3D56"
|
||||
/>
|
||||
<path
|
||||
d="M135.842 85.2683L92.3237 95.1172L93.9652 102.37L137.483 92.5213L135.842 85.2683Z"
|
||||
fill="#F5F5F5"
|
||||
/>
|
||||
<path
|
||||
d="M147.062 101.154L89.0376 114.286L89.448 116.099L147.472 102.968L147.062 101.154Z"
|
||||
fill="#F5F5F5"
|
||||
/>
|
||||
<path
|
||||
d="M148.019 105.386L89.9951 118.518L90.4055 120.331L148.43 107.199L148.019 105.386Z"
|
||||
fill="#F5F5F5"
|
||||
/>
|
||||
<path
|
||||
d="M148.977 109.616L90.9526 122.748L91.363 124.561L149.387 111.429L148.977 109.616Z"
|
||||
fill="#F5F5F5"
|
||||
/>
|
||||
<path
|
||||
d="M119.713 120.687L91.9102 126.979L92.3205 128.793L120.124 122.5L119.713 120.687Z"
|
||||
fill="#F5F5F5"
|
||||
/>
|
||||
<path
|
||||
d="M124.902 123.96L92.8682 131.21L93.2785 133.023L125.313 125.773L124.902 123.96Z"
|
||||
fill="#F5F5F5"
|
||||
/>
|
||||
<path
|
||||
d="M151.849 122.31L93.8252 135.441L94.2356 137.255L152.26 124.123L151.849 122.31Z"
|
||||
fill="#F5F5F5"
|
||||
/>
|
||||
<path
|
||||
d="M152.807 126.541L94.7832 139.673L95.1936 141.486L153.218 128.354L152.807 126.541Z"
|
||||
fill="#F5F5F5"
|
||||
/>
|
||||
<path
|
||||
d="M149.533 131.729L95.7402 143.903L96.1506 145.717L149.944 133.542L149.533 131.729Z"
|
||||
fill="#F5F5F5"
|
||||
/>
|
||||
<path
|
||||
d="M154.722 135.003L96.6982 148.135L97.1086 149.948L155.133 136.816L154.722 135.003Z"
|
||||
fill="#F5F5F5"
|
||||
/>
|
||||
<path
|
||||
d="M127.876 145.526L97.6553 152.365L98.0656 154.178L128.287 147.339L127.876 145.526Z"
|
||||
fill="#F5F5F5"
|
||||
/>
|
||||
<path
|
||||
opacity="0.3"
|
||||
d="M209.358 132.168L177.324 139.418L177.735 141.231L209.769 133.981L209.358 132.168Z"
|
||||
fill="#2B6FCF"
|
||||
/>
|
||||
<path
|
||||
opacity="0.3"
|
||||
d="M236.306 130.517L178.282 143.648L178.692 145.462L236.716 132.33L236.306 130.517Z"
|
||||
fill="#2B6FCF"
|
||||
/>
|
||||
<path
|
||||
opacity="0.3"
|
||||
d="M237.263 134.747L179.239 147.879L179.65 149.692L237.674 136.56L237.263 134.747Z"
|
||||
fill="#2B6FCF"
|
||||
/>
|
||||
<path
|
||||
opacity="0.3"
|
||||
d="M233.99 139.936L180.197 152.11L180.607 153.924L234.4 141.749L233.99 139.936Z"
|
||||
fill="#2B6FCF"
|
||||
/>
|
||||
<path
|
||||
opacity="0.3"
|
||||
d="M239.178 143.209L181.154 156.341L181.565 158.154L239.589 145.022L239.178 143.209Z"
|
||||
fill="#2B6FCF"
|
||||
/>
|
||||
<path
|
||||
d="M156.112 146.76L121.056 154.694L126.527 178.871L161.584 170.937L156.112 146.76Z"
|
||||
fill="#F5F5F5"
|
||||
/>
|
||||
<path
|
||||
opacity="0.3"
|
||||
d="M197.607 66.2068L162.551 74.1406L168.022 98.3174L203.079 90.3835L197.607 66.2068Z"
|
||||
fill="#2B6FCF"
|
||||
/>
|
||||
<path
|
||||
opacity="0.3"
|
||||
d="M231.919 91.4812L196.863 99.415L202.334 123.592L237.391 115.658L231.919 91.4812Z"
|
||||
fill="#2B6FCF"
|
||||
/>
|
||||
<g opacity="0.5">
|
||||
<path
|
||||
opacity="0.5"
|
||||
d="M67.3864 214.327H66.3145V220.401H67.3864V214.327Z"
|
||||
fill="#47E6B1"
|
||||
/>
|
||||
<path
|
||||
opacity="0.5"
|
||||
d="M69.8877 217.9V216.828H63.8135V217.9H69.8877Z"
|
||||
fill="#47E6B1"
|
||||
/>
|
||||
</g>
|
||||
<g opacity="0.5">
|
||||
<path
|
||||
opacity="0.5"
|
||||
d="M184.224 32H183.152V38.0742H184.224V32Z"
|
||||
fill="#47E6B1"
|
||||
/>
|
||||
<path
|
||||
opacity="0.5"
|
||||
d="M186.726 35.573V34.5011H180.651V35.573H186.726Z"
|
||||
fill="#47E6B1"
|
||||
/>
|
||||
</g>
|
||||
<g opacity="0.5">
|
||||
<path
|
||||
opacity="0.5"
|
||||
d="M317.499 76.6631H316.427V82.7372H317.499V76.6631Z"
|
||||
fill="#47E6B1"
|
||||
/>
|
||||
<path
|
||||
opacity="0.5"
|
||||
d="M320 80.2361V79.1642H313.926V80.2361H320Z"
|
||||
fill="#47E6B1"
|
||||
/>
|
||||
</g>
|
||||
<path
|
||||
opacity="0.5"
|
||||
d="M128.733 213.722C128.437 213.593 128.2 213.36 128.065 213.068C127.93 212.775 127.907 212.443 128.001 212.135C128.016 212.088 128.026 212.039 128.03 211.989C128.038 211.848 128.001 211.708 127.924 211.59C127.847 211.471 127.733 211.381 127.6 211.332C127.468 211.283 127.323 211.278 127.187 211.318C127.051 211.358 126.932 211.44 126.847 211.553C126.818 211.593 126.793 211.637 126.774 211.682C126.645 211.978 126.413 212.215 126.12 212.35C125.827 212.485 125.495 212.508 125.187 212.414C125.14 212.399 125.091 212.389 125.042 212.385C124.9 212.377 124.76 212.414 124.642 212.491C124.523 212.569 124.433 212.682 124.384 212.815C124.335 212.947 124.33 213.092 124.37 213.228C124.41 213.364 124.492 213.483 124.605 213.568C124.645 213.597 124.689 213.622 124.735 213.641C125.03 213.77 125.268 214.002 125.403 214.295C125.538 214.588 125.56 214.92 125.466 215.228C125.451 215.275 125.441 215.324 125.438 215.373C125.429 215.515 125.466 215.655 125.543 215.773C125.621 215.892 125.734 215.982 125.867 216.031C126 216.08 126.145 216.085 126.28 216.045C126.416 216.005 126.535 215.923 126.62 215.81C126.65 215.77 126.674 215.726 126.693 215.681C126.822 215.385 127.055 215.147 127.347 215.012C127.64 214.878 127.972 214.855 128.28 214.949C128.327 214.964 128.376 214.974 128.426 214.977C128.567 214.986 128.707 214.949 128.826 214.872C128.944 214.794 129.034 214.681 129.083 214.548C129.132 214.415 129.137 214.271 129.097 214.135C129.057 213.999 128.975 213.88 128.862 213.795C128.822 213.765 128.779 213.741 128.733 213.722Z"
|
||||
fill="#4D8AF0"
|
||||
/>
|
||||
<path
|
||||
opacity="0.5"
|
||||
d="M180.97 258.26C180.674 258.131 180.436 257.898 180.301 257.606C180.167 257.313 180.144 256.981 180.238 256.673C180.253 256.626 180.263 256.577 180.267 256.527C180.275 256.386 180.238 256.246 180.161 256.128C180.083 256.009 179.97 255.919 179.837 255.87C179.705 255.821 179.56 255.816 179.424 255.856C179.288 255.896 179.169 255.978 179.084 256.091C179.054 256.131 179.03 256.175 179.011 256.22C178.882 256.516 178.649 256.754 178.357 256.888C178.064 257.023 177.732 257.046 177.424 256.952C177.377 256.937 177.328 256.927 177.278 256.923C177.137 256.915 176.997 256.952 176.879 257.029C176.76 257.107 176.67 257.22 176.621 257.353C176.572 257.485 176.567 257.63 176.607 257.766C176.647 257.902 176.729 258.021 176.842 258.106C176.882 258.136 176.926 258.16 176.971 258.179C177.267 258.308 177.505 258.541 177.639 258.833C177.774 259.126 177.797 259.458 177.703 259.766C177.688 259.813 177.678 259.862 177.674 259.911C177.666 260.053 177.703 260.193 177.78 260.311C177.858 260.43 177.971 260.52 178.104 260.569C178.236 260.618 178.381 260.623 178.517 260.583C178.653 260.543 178.772 260.461 178.857 260.348C178.887 260.308 178.911 260.264 178.93 260.219C179.059 259.923 179.292 259.685 179.584 259.55C179.877 259.416 180.209 259.393 180.517 259.487C180.564 259.502 180.613 259.512 180.662 259.516C180.804 259.524 180.944 259.487 181.062 259.41C181.181 259.332 181.271 259.219 181.32 259.086C181.369 258.954 181.374 258.809 181.334 258.673C181.294 258.537 181.212 258.418 181.099 258.333C181.059 258.303 181.015 258.279 180.97 258.26Z"
|
||||
fill="#4D8AF0"
|
||||
/>
|
||||
<path
|
||||
opacity="0.5"
|
||||
d="M4.39143 107.5C4.09603 107.371 3.85826 107.139 3.72336 106.846C3.58846 106.553 3.56585 106.221 3.65985 105.913C3.67506 105.866 3.68465 105.817 3.6884 105.768C3.697 105.626 3.65989 105.486 3.58257 105.368C3.50525 105.249 3.39183 105.159 3.25912 105.11C3.1264 105.061 2.98145 105.056 2.84572 105.096C2.71 105.136 2.59072 105.218 2.50557 105.331C2.47628 105.371 2.45183 105.415 2.43271 105.461C2.30406 105.756 2.07128 105.994 1.77869 106.129C1.48609 106.264 1.15411 106.286 0.845929 106.192C0.798682 106.177 0.749803 106.167 0.700311 106.164C0.559115 106.155 0.41891 106.192 0.300455 106.269C0.181999 106.347 0.0915968 106.46 0.0426396 106.593C-0.00631764 106.726 -0.0112273 106.871 0.0286388 107.006C0.0685049 107.142 0.151028 107.261 0.263981 107.346C0.304058 107.376 0.347448 107.4 0.393253 107.419C0.688651 107.548 0.926428 107.781 1.06133 108.073C1.19623 108.366 1.21883 108.698 1.12483 109.006C1.10962 109.053 1.10003 109.102 1.09627 109.152C1.08768 109.293 1.12479 109.433 1.20211 109.552C1.27943 109.67 1.39285 109.76 1.52557 109.809C1.65828 109.858 1.80323 109.863 1.93896 109.823C2.07468 109.784 2.19395 109.701 2.27911 109.588C2.30839 109.548 2.33284 109.505 2.35196 109.459C2.48061 109.163 2.71339 108.926 3.00599 108.791C3.29858 108.656 3.63057 108.633 3.93875 108.727C3.986 108.742 4.03487 108.752 4.08436 108.756C4.22556 108.764 4.36576 108.727 4.48422 108.65C4.60267 108.573 4.69307 108.459 4.74203 108.327C4.79098 108.194 4.7959 108.049 4.75603 107.913C4.71617 107.777 4.63365 107.658 4.5207 107.573C4.48062 107.544 4.43723 107.519 4.39143 107.5Z"
|
||||
fill="#4D8AF0"
|
||||
/>
|
||||
<path
|
||||
opacity="0.5"
|
||||
d="M313.529 161.788C313.234 161.66 312.996 161.427 312.861 161.134C312.726 160.842 312.704 160.51 312.798 160.201C312.813 160.154 312.822 160.105 312.826 160.056C312.835 159.915 312.798 159.774 312.72 159.656C312.643 159.537 312.53 159.447 312.397 159.398C312.264 159.349 312.119 159.344 311.983 159.384C311.848 159.424 311.728 159.506 311.643 159.619C311.614 159.66 311.59 159.703 311.57 159.749C311.442 160.044 311.209 160.282 310.916 160.417C310.624 160.552 310.292 160.574 309.984 160.48C309.936 160.465 309.887 160.455 309.838 160.452C309.697 160.443 309.557 160.48 309.438 160.558C309.32 160.635 309.229 160.748 309.18 160.881C309.131 161.014 309.126 161.159 309.166 161.294C309.206 161.43 309.289 161.549 309.402 161.635C309.442 161.664 309.485 161.688 309.531 161.707C309.826 161.836 310.064 162.069 310.199 162.361C310.334 162.654 310.357 162.986 310.263 163.294C310.247 163.341 310.238 163.39 310.234 163.44C310.225 163.581 310.262 163.721 310.34 163.84C310.417 163.958 310.531 164.049 310.663 164.097C310.796 164.146 310.941 164.151 311.077 164.111C311.212 164.072 311.332 163.989 311.417 163.876C311.446 163.836 311.471 163.793 311.49 163.747C311.618 163.451 311.851 163.214 312.144 163.079C312.436 162.944 312.768 162.921 313.076 163.015C313.124 163.031 313.173 163.04 313.222 163.044C313.363 163.052 313.503 163.015 313.622 162.938C313.74 162.861 313.831 162.747 313.88 162.615C313.929 162.482 313.934 162.337 313.894 162.201C313.854 162.065 313.771 161.946 313.658 161.861C313.618 161.832 313.575 161.807 313.529 161.788Z"
|
||||
fill="#4D8AF0"
|
||||
/>
|
||||
<path
|
||||
opacity="0.5"
|
||||
d="M38.4446 196.819C39.6286 196.819 40.5884 195.859 40.5884 194.675C40.5884 193.491 39.6286 192.531 38.4446 192.531C37.2606 192.531 36.3008 193.491 36.3008 194.675C36.3008 195.859 37.2606 196.819 38.4446 196.819Z"
|
||||
fill="#F55F44"
|
||||
/>
|
||||
<path
|
||||
opacity="0.5"
|
||||
d="M114.907 36.2876C116.091 36.2876 117.051 35.3278 117.051 34.1438C117.051 32.9598 116.091 32 114.907 32C113.723 32 112.764 32.9598 112.764 34.1438C112.764 35.3278 113.723 36.2876 114.907 36.2876Z"
|
||||
fill="#4D8AF0"
|
||||
/>
|
||||
<path
|
||||
opacity="0.5"
|
||||
d="M165.716 197.766C166.9 197.766 167.86 196.806 167.86 195.622C167.86 194.438 166.9 193.479 165.716 193.479C164.532 193.479 163.572 194.438 163.572 195.622C163.572 196.806 164.532 197.766 165.716 197.766Z"
|
||||
fill="#47E6B1"
|
||||
/>
|
||||
<path
|
||||
opacity="0.5"
|
||||
d="M289.343 251.362C290.527 251.362 291.487 250.402 291.487 249.218C291.487 248.034 290.527 247.074 289.343 247.074C288.159 247.074 287.199 248.034 287.199 249.218C287.199 250.402 288.159 251.362 289.343 251.362Z"
|
||||
fill="#F55F44"
|
||||
/>
|
||||
<path
|
||||
d="M248.302 78.9783L185.189 93.2616C182.604 93.8469 180.982 96.4175 181.567 99.0034L203.267 194.887C203.852 197.473 206.423 199.094 209.009 198.509L272.121 184.226C274.707 183.641 276.329 181.07 275.743 178.484L254.043 82.6007C253.458 80.0149 250.887 78.393 248.302 78.9783Z"
|
||||
fill="#2B6FCF"
|
||||
/>
|
||||
<path
|
||||
d="M187.798 110.607C188.982 110.607 189.941 109.647 189.941 108.463C189.941 107.279 188.982 106.319 187.798 106.319C186.614 106.319 185.654 107.279 185.654 108.463C185.654 109.647 186.614 110.607 187.798 110.607Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
d="M189.942 118.467C191.126 118.467 192.085 117.507 192.085 116.324C192.085 115.14 191.126 114.18 189.942 114.18C188.758 114.18 187.798 115.14 187.798 116.324C187.798 117.507 188.758 118.467 189.942 118.467Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
d="M203.876 175.994C205.06 175.994 206.02 175.034 206.02 173.85C206.02 172.666 205.06 171.706 203.876 171.706C202.692 171.706 201.732 172.666 201.732 173.85C201.732 175.034 202.692 175.994 203.876 175.994Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
d="M206.02 183.854C207.204 183.854 208.164 182.894 208.164 181.71C208.164 180.526 207.204 179.566 206.02 179.566C204.836 179.566 203.876 180.526 203.876 181.71C203.876 182.894 204.836 183.854 206.02 183.854Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
d="M248.035 118.855L204.517 128.704L206.159 135.957L249.677 126.108L248.035 118.855Z"
|
||||
fill="#F5F5F5"
|
||||
/>
|
||||
<defs>
|
||||
<pattern
|
||||
id="pattern0"
|
||||
patternContentUnits="objectBoundingBox"
|
||||
width="0.0170833"
|
||||
height="0.148641"
|
||||
>
|
||||
<use xlink:href="#image0" transform="scale(0.00189815 0.0165157)" />
|
||||
</pattern>
|
||||
<image
|
||||
id="image0"
|
||||
width="9"
|
||||
height="9"
|
||||
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAtSURBVHgB1dChDQAACAPBHwn23wVWAYMkxdKkVacKkF1jj80oyDfoFxgD1UdRPHMLOJmKMAEAAAAASUVORK5CYII="
|
||||
/>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 14 KiB |
12
app/assets/svg/diamond-with-horizontal-lines.svg
Normal file
12
app/assets/svg/diamond-with-horizontal-lines.svg
Normal file
@@ -0,0 +1,12 @@
|
||||
<svg viewBox="0 0 104 104" fill="none" xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<rect opacity="0.36" y="52.0004" width="73.5394" height="73.5394" rx="8" transform="rotate(-45 0 52.0004)"
|
||||
fill="url(#pattern0)"/>
|
||||
<defs>
|
||||
<pattern id="pattern0" patternContentUnits="objectBoundingBox" width="0.0611917" height="0.0611917">
|
||||
<use xlink:href="#image0" transform="scale(0.00679907)"/>
|
||||
</pattern>
|
||||
<image id="image0" width="9" height="9"
|
||||
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAtSURBVHgB1dChDQAACAPBHwn23wVWAYMkxdKkVacKkF1jj80oyDfoFxgD1UdRPHMLOJmKMAEAAAAASUVORK5CYII="/>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 801 B |
4
app/assets/svg/ic-sn-logo-full.svg
Normal file
4
app/assets/svg/ic-sn-logo-full.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 15 KiB |
Reference in New Issue
Block a user