chore: move all components into Components dir with pascal case (#934)

This commit is contained in:
Mo
2022-03-17 11:38:45 -05:00
committed by GitHub
parent 42b84ef9b1
commit c29e45795d
89 changed files with 370 additions and 259 deletions

View File

@@ -0,0 +1,44 @@
import { FunctionalComponent } from 'preact';
import {
PreferencesGroup,
PreferencesSegment,
} from '@/components/Preferences/components';
import { OfflineSubscription } from '@/components/Preferences/panes/account/offlineSubscription';
import { WebApplication } from '@/ui_models/application';
import { observer } from 'mobx-react-lite';
import { AppState } from '@/ui_models/app_state';
import { Extensions } from '@/components/Preferences/panes/Extensions';
import { ExtensionsLatestVersions } from '@/components/Preferences/panes/extensions-segments';
import { AccordionItem } from '@/components/Shared/AccordionItem';
interface IProps {
application: WebApplication;
appState: AppState;
extensionsLatestVersions: ExtensionsLatestVersions;
}
export const Advanced: FunctionalComponent<IProps> = observer(
({ application, appState, extensionsLatestVersions }) => {
return (
<PreferencesGroup>
<PreferencesSegment>
<AccordionItem title={'Advanced Settings'}>
<div className="flex flex-row items-center">
<div className="flex-grow flex flex-col">
<OfflineSubscription
application={application}
appState={appState}
/>
<Extensions
className={'mt-3'}
application={application}
extensionsLatestVersions={extensionsLatestVersions}
/>
</div>
</div>
</AccordionItem>
</PreferencesSegment>
</PreferencesGroup>
);
}
);

View File

@@ -0,0 +1,60 @@
import { AccountMenuPane } from '@/components/AccountMenu';
import { Button } from '@/components/Button';
import {
PreferencesGroup,
PreferencesSegment,
Text,
Title,
} from '@/components/Preferences/components';
import { WebApplication } from '@/ui_models/application';
import { AppState } from '@/ui_models/app_state';
import { observer } from 'mobx-react-lite';
import { FunctionComponent } from 'preact';
import { AccountIllustration } from '@standardnotes/stylekit';
export const Authentication: FunctionComponent<{
application: WebApplication;
appState: AppState;
}> = observer(({ appState }) => {
const clickSignIn = () => {
appState.preferences.closePreferences();
appState.accountMenu.setCurrentPane(AccountMenuPane.SignIn);
appState.accountMenu.setShow(true);
};
const clickRegister = () => {
appState.preferences.closePreferences();
appState.accountMenu.setCurrentPane(AccountMenuPane.Register);
appState.accountMenu.setShow(true);
};
return (
<PreferencesGroup>
<PreferencesSegment>
<div className="flex flex-col items-center px-12">
<AccountIllustration className="mb-3" />
<Title>You're not signed in</Title>
<Text className="text-center mb-3">
Sign in to sync your notes and preferences across all your devices
and enable end-to-end encryption.
</Text>
<Button
type="primary"
label="Create free account"
onClick={clickRegister}
className="mb-3"
/>
<div className="text-input">
Already have an account?{' '}
<button
className="border-0 p-0 bg-default color-info underline cursor-pointer"
onClick={clickSignIn}
>
Sign in
</button>
</div>
</div>
</PreferencesSegment>
</PreferencesGroup>
);
});

View File

@@ -0,0 +1,80 @@
import {
PreferencesGroup,
PreferencesSegment,
Subtitle,
Text,
Title,
} from '@/components/Preferences/components';
import { Button } from '@/components/Button';
import { WebApplication } from '@/ui_models/application';
import { observer } from '@node_modules/mobx-react-lite';
import { HorizontalSeparator } from '@/components/Shared/HorizontalSeparator';
import { dateToLocalizedString } from '@standardnotes/snjs';
import { useCallback, useState } from 'preact/hooks';
import { ChangeEmail } from '@/components/Preferences/panes/account/changeEmail';
import { FunctionComponent, render } from 'preact';
import { AppState } from '@/ui_models/app_state';
import { PasswordWizard } from '@/components/PasswordWizard';
type Props = {
application: WebApplication;
appState: AppState;
};
export const Credentials: FunctionComponent<Props> = observer(
({ application }: Props) => {
const [isChangeEmailDialogOpen, setIsChangeEmailDialogOpen] =
useState(false);
const user = application.getUser();
const passwordCreatedAtTimestamp =
application.getUserPasswordCreationDate() as Date;
const passwordCreatedOn = dateToLocalizedString(passwordCreatedAtTimestamp);
const presentPasswordWizard = useCallback(() => {
render(
<PasswordWizard application={application} />,
document.body.appendChild(document.createElement('div'))
);
}, [application]);
return (
<PreferencesGroup>
<PreferencesSegment>
<Title>Credentials</Title>
<Subtitle>Email</Subtitle>
<Text>
You're signed in as <span className="font-bold">{user?.email}</span>
</Text>
<Button
className="min-w-20 mt-3"
type="normal"
label="Change email"
onClick={() => {
setIsChangeEmailDialogOpen(true);
}}
/>
<HorizontalSeparator classes="mt-5 mb-3" />
<Subtitle>Password</Subtitle>
<Text>
Current password was set on{' '}
<span className="font-bold">{passwordCreatedOn}</span>
</Text>
<Button
className="min-w-20 mt-3"
type="normal"
label="Change password"
onClick={presentPasswordWizard}
/>
{isChangeEmailDialogOpen && (
<ChangeEmail
onCloseDialog={() => setIsChangeEmailDialogOpen(false)}
application={application}
/>
)}
</PreferencesSegment>
</PreferencesGroup>
);
}
);

View File

@@ -0,0 +1,93 @@
import { Button } from '@/components/Button';
import { OtherSessionsSignOutContainer } from '@/components/OtherSessionsSignOut';
import {
PreferencesGroup,
PreferencesSegment,
Subtitle,
Text,
Title,
} from '@/components/Preferences/components';
import { WebApplication } from '@/ui_models/application';
import { AppState } from '@/ui_models/app_state';
import { observer } from 'mobx-react-lite';
import { FunctionComponent } from 'preact';
const SignOutView: FunctionComponent<{
application: WebApplication;
appState: AppState;
}> = observer(({ application, appState }) => {
return (
<>
<PreferencesGroup>
<PreferencesSegment>
<Title>Sign out</Title>
<Subtitle>Other devices</Subtitle>
<Text>Want to sign out on all devices except this one?</Text>
<div className="min-h-3" />
<div className="flex flex-row">
<Button
className="mr-3"
type="normal"
label="Sign out other sessions"
onClick={() => {
appState.accountMenu.setOtherSessionsSignOut(true);
}}
/>
<Button
type="normal"
label="Manage sessions"
onClick={() => appState.openSessionsModal()}
/>
</div>
</PreferencesSegment>
<PreferencesSegment>
<Subtitle>This device</Subtitle>
<Text>This will delete all local items and preferences.</Text>
<div className="min-h-3" />
<Button
type="danger"
label="Sign out and clear local data"
onClick={() => {
appState.accountMenu.setSigningOut(true);
}}
/>
</PreferencesSegment>
</PreferencesGroup>
<OtherSessionsSignOutContainer
appState={appState}
application={application}
/>
</>
);
});
const ClearSessionDataView: FunctionComponent<{
appState: AppState;
}> = observer(({ appState }) => {
return (
<PreferencesGroup>
<PreferencesSegment>
<Title>Clear session data</Title>
<Text>This will delete all local items and preferences.</Text>
<div className="min-h-3" />
<Button
type="danger"
label="Clear Session Data"
onClick={() => {
appState.accountMenu.setSigningOut(true);
}}
/>
</PreferencesSegment>
</PreferencesGroup>
);
});
export const SignOutWrapper: FunctionComponent<{
application: WebApplication;
appState: AppState;
}> = observer(({ application, appState }) => {
if (!application.hasAccount()) {
return <ClearSessionDataView appState={appState} />;
}
return <SignOutView appState={appState} application={application} />;
});

View File

@@ -0,0 +1,69 @@
import {
PreferencesGroup,
PreferencesSegment,
Text,
Title,
} from '@/components/Preferences/components';
import { Button } from '@/components/Button';
import { SyncQueueStrategy, dateToLocalizedString } from '@standardnotes/snjs';
import { STRING_GENERIC_SYNC_ERROR } from '@/strings';
import { useState } from '@node_modules/preact/hooks';
import { observer } from 'mobx-react-lite';
import { WebApplication } from '@/ui_models/application';
import { FunctionComponent } from 'preact';
type Props = {
application: WebApplication;
};
export const formatLastSyncDate = (lastUpdatedDate: Date) => {
return dateToLocalizedString(lastUpdatedDate);
};
export const Sync: FunctionComponent<Props> = observer(
({ application }: Props) => {
const [isSyncingInProgress, setIsSyncingInProgress] = useState(false);
const [lastSyncDate, setLastSyncDate] = useState(
formatLastSyncDate(application.sync.getLastSyncDate() as Date)
);
const doSynchronization = async () => {
setIsSyncingInProgress(true);
const response = await application.sync.sync({
queueStrategy: SyncQueueStrategy.ForceSpawnNew,
checkIntegrity: true,
});
setIsSyncingInProgress(false);
if (response && (response as any).error) {
application.alertService.alert(STRING_GENERIC_SYNC_ERROR);
} else {
setLastSyncDate(
formatLastSyncDate(application.sync.getLastSyncDate() as Date)
);
}
};
return (
<PreferencesGroup>
<PreferencesSegment>
<div className="flex flex-row items-center">
<div className="flex-grow flex flex-col">
<Title>Sync</Title>
<Text>
Last synced <span className="font-bold">on {lastSyncDate}</span>
</Text>
<Button
className="min-w-20 mt-3"
type="normal"
label="Sync now"
disabled={isSyncingInProgress}
onClick={doSynchronization}
/>
</div>
</div>
</PreferencesSegment>
</PreferencesGroup>
);
}
);

View File

@@ -0,0 +1,47 @@
import { StateUpdater } from 'preact/hooks';
import { FunctionalComponent } from 'preact';
type Props = {
setNewEmail: StateUpdater<string>;
setCurrentPassword: StateUpdater<string>;
};
const labelClassName = `block mb-1`;
const inputClassName = 'sk-input contrast';
export const ChangeEmailForm: FunctionalComponent<Props> = ({
setNewEmail,
setCurrentPassword,
}) => {
return (
<div className="w-full flex flex-col">
<div className="mt-2 mb-3">
<label className={labelClassName} htmlFor="change-email-email-input">
New Email:
</label>
<input
id="change-email-email-input"
className={inputClassName}
type="email"
onChange={({ target }) => {
setNewEmail((target as HTMLInputElement).value);
}}
/>
</div>
<div className="mb-2">
<label className={labelClassName} htmlFor="change-email-password-input">
Current Password:
</label>
<input
id="change-email-password-input"
className={inputClassName}
type="password"
onChange={({ target }) => {
setCurrentPassword((target as HTMLInputElement).value);
}}
/>
</div>
</div>
);
};

View File

@@ -0,0 +1,15 @@
import { FunctionalComponent } from 'preact';
export const ChangeEmailSuccess: FunctionalComponent = () => {
return (
<div>
<div className={'sk-label sk-bold info mt-2'}>
Your email has been successfully changed.
</div>
<p className={'sk-p'}>
Please ensure you are running the latest version of Standard Notes on
all platforms to ensure maximum compatibility.
</p>
</div>
);
};

View File

@@ -0,0 +1,180 @@
import { useState } from '@node_modules/preact/hooks';
import {
ModalDialog,
ModalDialogButtons,
ModalDialogDescription,
ModalDialogLabel,
} from '@/components/Shared/ModalDialog';
import { Button } from '@/components/Button';
import { FunctionalComponent } from 'preact';
import { WebApplication } from '@/ui_models/application';
import { useBeforeUnload } from '@/hooks/useBeforeUnload';
import { ChangeEmailForm } from './ChangeEmailForm';
import { ChangeEmailSuccess } from './ChangeEmailSuccess';
import { isEmailValid } from '@/utils';
enum SubmitButtonTitles {
Default = 'Continue',
GeneratingKeys = 'Generating Keys...',
Finish = 'Finish',
}
enum Steps {
InitialStep,
FinishStep,
}
type Props = {
onCloseDialog: () => void;
application: WebApplication;
};
export const ChangeEmail: FunctionalComponent<Props> = ({
onCloseDialog,
application,
}) => {
const [currentPassword, setCurrentPassword] = useState('');
const [newEmail, setNewEmail] = useState('');
const [isContinuing, setIsContinuing] = useState(false);
const [lockContinue, setLockContinue] = useState(false);
const [submitButtonTitle, setSubmitButtonTitle] = useState(
SubmitButtonTitles.Default
);
const [currentStep, setCurrentStep] = useState(Steps.InitialStep);
useBeforeUnload();
const applicationAlertService = application.alertService;
const validateCurrentPassword = async () => {
if (!currentPassword || currentPassword.length === 0) {
applicationAlertService.alert('Please enter your current password.');
return false;
}
const success = await application.validateAccountPassword(currentPassword);
if (!success) {
applicationAlertService.alert(
'The current password you entered is not correct. Please try again.'
);
return false;
}
return success;
};
const validateNewEmail = async () => {
if (!isEmailValid(newEmail)) {
applicationAlertService.alert(
'The email you entered has an invalid format. Please review your input and try again.'
);
return false;
}
return true;
};
const resetProgressState = () => {
setSubmitButtonTitle(SubmitButtonTitles.Default);
setIsContinuing(false);
};
const processEmailChange = async () => {
await application.downloadBackup();
setLockContinue(true);
const response = await application.changeEmail(newEmail, currentPassword);
const success = !response.error;
setLockContinue(false);
return success;
};
const dismiss = () => {
if (lockContinue) {
applicationAlertService.alert(
'Cannot close window until pending tasks are complete.'
);
} else {
onCloseDialog();
}
};
const handleSubmit = async () => {
if (lockContinue || isContinuing) {
return;
}
if (currentStep === Steps.FinishStep) {
dismiss();
return;
}
setIsContinuing(true);
setSubmitButtonTitle(SubmitButtonTitles.GeneratingKeys);
const valid =
(await validateCurrentPassword()) && (await validateNewEmail());
if (!valid) {
resetProgressState();
return;
}
const success = await processEmailChange();
if (!success) {
resetProgressState();
return;
}
setIsContinuing(false);
setSubmitButtonTitle(SubmitButtonTitles.Finish);
setCurrentStep(Steps.FinishStep);
};
const handleDialogClose = () => {
if (lockContinue) {
applicationAlertService.alert(
'Cannot close window until pending tasks are complete.'
);
} else {
onCloseDialog();
}
};
return (
<div>
<ModalDialog>
<ModalDialogLabel closeDialog={handleDialogClose}>
Change Email
</ModalDialogLabel>
<ModalDialogDescription className="px-4.5">
{currentStep === Steps.InitialStep && (
<ChangeEmailForm
setNewEmail={setNewEmail}
setCurrentPassword={setCurrentPassword}
/>
)}
{currentStep === Steps.FinishStep && <ChangeEmailSuccess />}
</ModalDialogDescription>
<ModalDialogButtons className="px-4.5">
<Button
className="min-w-20"
type="primary"
label={submitButtonTitle}
onClick={handleSubmit}
/>
</ModalDialogButtons>
</ModalDialog>
</div>
);
};

View File

@@ -0,0 +1,6 @@
export { Subscription } from './subscription/Subscription';
export { Sync } from './Sync';
export { Credentials } from './Credentials';
export { SignOutWrapper } from './SignOutView';
export { Authentication } from './Authentication';
export { Advanced } from './Advanced';

View File

@@ -0,0 +1,146 @@
import { FunctionalComponent } from 'preact';
import { Subtitle } from '@/components/Preferences/components';
import { DecoratedInput } from '@/components/DecoratedInput';
import { Button } from '@/components/Button';
import { JSXInternal } from '@node_modules/preact/src/jsx';
import TargetedEvent = JSXInternal.TargetedEvent;
import { useEffect, useState } from 'preact/hooks';
import { WebApplication } from '@/ui_models/application';
import { AppState } from '@/ui_models/app_state';
import { observer } from 'mobx-react-lite';
import { STRING_REMOVE_OFFLINE_KEY_CONFIRMATION } from '@/strings';
import { ButtonType, ClientDisplayableError } from '@standardnotes/snjs';
import { HorizontalSeparator } from '@/components/Shared/HorizontalSeparator';
interface IProps {
application: WebApplication;
appState: AppState;
}
export const OfflineSubscription: FunctionalComponent<IProps> = observer(
({ application }) => {
const [activationCode, setActivationCode] = useState('');
const [isSuccessfullyActivated, setIsSuccessfullyActivated] =
useState(false);
const [isSuccessfullyRemoved, setIsSuccessfullyRemoved] = useState(false);
const [hasUserPreviouslyStoredCode, setHasUserPreviouslyStoredCode] =
useState(false);
useEffect(() => {
if (application.features.hasOfflineRepo()) {
setHasUserPreviouslyStoredCode(true);
}
}, [application]);
const shouldShowOfflineSubscription = () => {
return (
!application.hasAccount() ||
application.isThirdPartyHostUsed() ||
hasUserPreviouslyStoredCode
);
};
const handleSubscriptionCodeSubmit = async (
event: TargetedEvent<HTMLFormElement, Event>
) => {
event.preventDefault();
const result = await application.features.setOfflineFeaturesCode(
activationCode
);
if (result instanceof ClientDisplayableError) {
await application.alertService.alert(result.text);
} else {
setIsSuccessfullyActivated(true);
setHasUserPreviouslyStoredCode(true);
setIsSuccessfullyRemoved(false);
}
};
const handleRemoveOfflineKey = async () => {
await application.features.deleteOfflineFeatureRepo();
setIsSuccessfullyActivated(false);
setHasUserPreviouslyStoredCode(false);
setActivationCode('');
setIsSuccessfullyRemoved(true);
};
const handleRemoveClick = async () => {
application.alertService
.confirm(
STRING_REMOVE_OFFLINE_KEY_CONFIRMATION,
'Remove offline key?',
'Remove Offline Key',
ButtonType.Danger,
'Cancel'
)
.then(async (shouldRemove: boolean) => {
if (shouldRemove) {
await handleRemoveOfflineKey();
}
})
.catch((err: string) => {
application.alertService.alert(err);
});
};
if (!shouldShowOfflineSubscription()) {
return null;
}
return (
<>
<div className="flex items-center justify-between">
<div className="flex flex-col mt-3 w-full">
<Subtitle>
{!hasUserPreviouslyStoredCode && 'Activate'} Offline Subscription
</Subtitle>
<form onSubmit={handleSubscriptionCodeSubmit}>
<div className={'mt-2'}>
{!hasUserPreviouslyStoredCode && (
<DecoratedInput
onChange={(code) => setActivationCode(code)}
placeholder={'Offline Subscription Code'}
text={activationCode}
disabled={isSuccessfullyActivated}
className={'mb-3'}
/>
)}
</div>
{(isSuccessfullyActivated || isSuccessfullyRemoved) && (
<div className={'mt-3 mb-3 info'}>
Your offline subscription code has been successfully{' '}
{isSuccessfullyActivated ? 'activated' : 'removed'}.
</div>
)}
{hasUserPreviouslyStoredCode && (
<Button
type="danger"
label="Remove offline key"
onClick={() => {
handleRemoveClick();
}}
/>
)}
{!hasUserPreviouslyStoredCode && !isSuccessfullyActivated && (
<Button
label={'Submit'}
type="primary"
disabled={activationCode === ''}
onClick={(event) =>
handleSubscriptionCodeSubmit(
event as TargetedEvent<HTMLFormElement>
)
}
/>
)}
</form>
</div>
</div>
<HorizontalSeparator classes="mt-8 mb-5" />
</>
);
}
);

View File

@@ -0,0 +1,57 @@
import { FunctionalComponent } from 'preact';
import { LinkButton, Text } from '@/components/Preferences/components';
import { Button } from '@/components/Button';
import { WebApplication } from '@/ui_models/application';
import { useState } from 'preact/hooks';
import { loadPurchaseFlowUrl } from '@/components/PurchaseFlow/PurchaseFlowWrapper';
export const NoSubscription: FunctionalComponent<{
application: WebApplication;
}> = ({ application }) => {
const [isLoadingPurchaseFlow, setIsLoadingPurchaseFlow] = useState(false);
const [purchaseFlowError, setPurchaseFlowError] = useState<
string | undefined
>(undefined);
const onPurchaseClick = async () => {
const errorMessage =
'There was an error when attempting to redirect you to the subscription page.';
setIsLoadingPurchaseFlow(true);
try {
if (!(await loadPurchaseFlowUrl(application))) {
setPurchaseFlowError(errorMessage);
}
} catch (e) {
setPurchaseFlowError(errorMessage);
} finally {
setIsLoadingPurchaseFlow(false);
}
};
return (
<>
<Text>You don't have a Standard Notes subscription yet.</Text>
{isLoadingPurchaseFlow && (
<Text>Redirecting you to the subscription page...</Text>
)}
{purchaseFlowError && (
<Text className="color-danger">{purchaseFlowError}</Text>
)}
<div className="flex">
<LinkButton
className="min-w-20 mt-3 mr-3"
label="Learn More"
link={window.plansUrl as string}
/>
{application.hasAccount() && (
<Button
className="min-w-20 mt-3"
type="primary"
label="Subscribe"
onClick={onPurchaseClick}
/>
)}
</div>
</>
);
};

View File

@@ -0,0 +1,45 @@
import {
PreferencesGroup,
PreferencesSegment,
Title,
} from '@/components/Preferences/components';
import { WebApplication } from '@/ui_models/application';
import { SubscriptionInformation } from './SubscriptionInformation';
import { NoSubscription } from './NoSubscription';
import { observer } from 'mobx-react-lite';
import { FunctionComponent } from 'preact';
import { AppState } from '@/ui_models/app_state';
type Props = {
application: WebApplication;
appState: AppState;
};
export const Subscription: FunctionComponent<Props> = observer(
({ application, appState }: Props) => {
const subscriptionState = appState.subscription;
const { userSubscription } = subscriptionState;
const now = new Date().getTime();
return (
<PreferencesGroup>
<PreferencesSegment>
<div className="flex flex-row items-center">
<div className="flex-grow flex flex-col">
<Title>Subscription</Title>
{userSubscription && userSubscription.endsAt > now ? (
<SubscriptionInformation
subscriptionState={subscriptionState}
application={application}
/>
) : (
<NoSubscription application={application} />
)}
</div>
</div>
</PreferencesSegment>
</PreferencesGroup>
);
}
);

View File

@@ -0,0 +1,91 @@
import { observer } from 'mobx-react-lite';
import { SubscriptionState } from '../../../../../ui_models/app_state/subscription_state';
import { Text } from '@/components/Preferences/components';
import { Button } from '@/components/Button';
import { WebApplication } from '@/ui_models/application';
import { openSubscriptionDashboard } from '@/hooks/manageSubscription';
type Props = {
subscriptionState: SubscriptionState;
application?: WebApplication;
};
const StatusText = observer(({ subscriptionState }: Props) => {
const {
userSubscriptionName,
userSubscriptionExpirationDate,
isUserSubscriptionExpired,
isUserSubscriptionCanceled,
} = subscriptionState;
const expirationDateString = userSubscriptionExpirationDate?.toLocaleString();
if (isUserSubscriptionCanceled) {
return (
<Text className="mt-1">
Your{' '}
<span className="font-bold">
Standard Notes{userSubscriptionName ? ' ' : ''}
{userSubscriptionName}
</span>{' '}
subscription has been canceled{' '}
{isUserSubscriptionExpired ? (
<span className="font-bold">
and expired on {expirationDateString}
</span>
) : (
<span className="font-bold">
but will remain valid until {expirationDateString}
</span>
)}
. You may resubscribe below if you wish.
</Text>
);
}
if (isUserSubscriptionExpired) {
return (
<Text className="mt-1">
Your{' '}
<span className="font-bold">
Standard Notes{userSubscriptionName ? ' ' : ''}
{userSubscriptionName}
</span>{' '}
subscription{' '}
<span className="font-bold">expired on {expirationDateString}</span>.
You may resubscribe below if you wish.
</Text>
);
}
return (
<Text className="mt-1">
Your{' '}
<span className="font-bold">
Standard Notes{userSubscriptionName ? ' ' : ''}
{userSubscriptionName}
</span>{' '}
subscription will be{' '}
<span className="font-bold">renewed on {expirationDateString}</span>.
</Text>
);
});
export const SubscriptionInformation = observer(
({ subscriptionState, application }: Props) => {
const manageSubscription = async () => {
openSubscriptionDashboard(application!);
};
return (
<>
<StatusText subscriptionState={subscriptionState} />
<Button
className="min-w-20 mt-3 mr-3"
type="normal"
label="Manage subscription"
onClick={manageSubscription}
/>
</>
);
}
);