;
+
+export const Primary: Story = {
+ args: {
+ children: (
+
+
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore
+ et dolore magna aliqua. Tincidunt nunc pulvinar sapien et. Mattis aliquam faucibus purus in.
+ Tristique sollicitudin nibh sit amet commodo nulla facilisi. Eget nullam non nisi est sit amet. At
+ lectus urna duis convallis. Consequat semper viverra nam libero. Gravida rutrum quisque non tellus
+ orci ac auctor. Mattis aliquam faucibus purus in massa tempor nec feugiat. Consectetur adipiscing
+ elit duis tristique sollicitudin. Sit amet est placerat in egestas erat imperdiet sed euismod.
+ Ornare massa eget egestas purus viverra. Viverra maecenas accumsan lacus vel facilisis. Malesuada
+ fames ac turpis egestas integer eget aliquet nibh. Non diam phasellus vestibulum lorem sed risus.
+ Tincidunt vitae semper quis lectus nulla. Cursus euismod quis viverra nibh cras pulvinar mattis nunc
+ sed.
+
+
+
+
+
+ ),
+ open: true,
+ },
+};
+
+export const WallOfText: Story = {
+ args: {
+ children: (
+ <>
+ Lorem ipsum!
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore
+ et dolore magna aliqua. Tincidunt nunc pulvinar sapien et. Mattis aliquam faucibus purus in.
+ Tristique sollicitudin nibh sit amet commodo nulla facilisi. Eget nullam non nisi est sit amet. At
+ lectus urna duis convallis. Consequat semper viverra nam libero. Gravida rutrum quisque non tellus
+ orci ac auctor. Mattis aliquam faucibus purus in massa tempor nec feugiat. Consectetur adipiscing
+ elit duis tristique sollicitudin. Sit amet est placerat in egestas erat imperdiet sed euismod.
+ Ornare massa eget egestas purus viverra. Viverra maecenas accumsan lacus vel facilisis. Malesuada
+ fames ac turpis egestas integer eget aliquet nibh. Non diam phasellus vestibulum lorem sed risus.
+ Tincidunt vitae semper quis lectus nulla. Cursus euismod quis viverra nibh cras pulvinar mattis nunc
+ sed.
+
+
+ Commodo quis imperdiet massa tincidunt nunc pulvinar sapien et ligula. Vitae congue mauris rhoncus
+ aenean vel elit scelerisque mauris pellentesque. Nec feugiat in fermentum posuere urna. Lobortis
+ scelerisque fermentum dui faucibus in ornare quam. Sit amet est placerat in egestas erat imperdiet
+ sed euismod. Nulla aliquet porttitor lacus luctus accumsan tortor posuere. Arcu risus quis varius
+ quam. Ullamcorper a lacus vestibulum sed arcu non odio. Eu mi bibendum neque egestas. Duis at
+ consectetur lorem donec.
+
+
+ Hendrerit dolor magna eget est lorem ipsum dolor sit amet. Dignissim sodales ut eu sem. Tellus
+ molestie nunc non blandit massa enim nec. Amet nulla facilisi morbi tempus iaculis urna. Metus
+ vulputate eu scelerisque felis imperdiet proin. Mauris ultrices eros in cursus turpis massa
+ tincidunt dui ut. Praesent tristique magna sit amet purus gravida quis blandit turpis. Turpis nunc
+ eget lorem dolor sed viverra ipsum. Et egestas quis ipsum suspendisse ultrices gravida. Egestas diam
+ in arcu cursus euismod quis. Egestas purus viverra accumsan in. Convallis convallis tellus id
+ interdum. Donec pretium vulputate sapien nec sagittis. Consectetur adipiscing elit ut aliquam purus
+ sit amet luctus venenatis.
+
+
+ Eget gravida cum sociis natoque penatibus et. Tincidunt eget nullam non nisi est. Amet mattis
+ vulputate enim nulla. Eget mi proin sed libero enim sed faucibus turpis in. Mattis ullamcorper velit
+ sed ullamcorper. Nunc vel risus commodo viverra. At tellus at urna condimentum mattis. At elementum
+ eu facilisis sed odio. Egestas dui id ornare arcu. Proin libero nunc consequat interdum varius.
+ Scelerisque eleifend donec pretium vulputate sapien nec.
+
+
+ Eget velit aliquet sagittis id consectetur purus ut faucibus pulvinar. Sapien eget mi proin sed
+ libero enim. Eget velit aliquet sagittis id consectetur. Est placerat in egestas erat. Diam maecenas
+ ultricies mi eget mauris pharetra et ultrices neque. Ut sem nulla pharetra diam sit amet nisl
+ suscipit adipiscing. Curabitur vitae nunc sed velit dignissim sodales ut eu sem. Adipiscing at in
+ tellus integer feugiat scelerisque varius. Ultrices mi tempus imperdiet nulla malesuada pellentesque
+ elit eget. Quisque sagittis purus sit amet volutpat. Dignissim diam quis enim lobortis scelerisque
+ fermentum dui. At lectus urna duis convallis convallis tellus id. Ultrices dui sapien eget mi proin
+ sed.
+
+ >
+ ),
+ open: true,
+ },
+};
diff --git a/packages/browser-wallet/src/popup/popupX/shared/FullscreenNotice/FullscreenNotice.tsx b/packages/browser-wallet/src/popup/popupX/shared/FullscreenNotice/FullscreenNotice.tsx
new file mode 100644
index 00000000..e5e9fca5
--- /dev/null
+++ b/packages/browser-wallet/src/popup/popupX/shared/FullscreenNotice/FullscreenNotice.tsx
@@ -0,0 +1,93 @@
+import React, { PropsWithChildren, useCallback, useEffect, useMemo } from 'react';
+import clsx from 'clsx';
+import { Portal, noOp } from 'wallet-common-helpers';
+import Back from '@assets/svgX/arrow-left.svg';
+import { Connection, Fullscreen } from '@popup/popupX/page-layouts/MainLayout/Header/components';
+import Button from '../Button';
+
+type HeaderProps = {
+ isScrolling: boolean;
+ onBack(): void;
+};
+
+function Header({ isScrolling, onBack }: HeaderProps) {
+ return (
+
+
+
+
+
+
+ } onClick={() => onBack()} />
+
+
+ );
+}
+
+const htmlElement = document.getElementsByTagName('html')[0]!;
+const bodyElement = document.getElementsByTagName('body')[0]!;
+
+type Props = {
+ /** Control whether notice is shown or not */
+ open: boolean;
+ /** Invoked when the notice is closed */
+ onClose(): void;
+};
+
+/**
+ * @description
+ * Opens content in a modal overlay on top of the current wallet window.
+ *
+ * @example
+ * setIsOpen(false)}>
+ *
+ *
+ * This is the body
+ *
+ * Some action
+ *
+ *
+ * This content is shown in a modal!
+ *
+ */
+export default function FullscreenNotice({ open, onClose, children }: PropsWithChildren): JSX.Element | null {
+ const [scroll, setScroll] = React.useState(0);
+ const isScrolling = useMemo(() => scroll > 0, [!!scroll]);
+ const close = useCallback(() => {
+ onClose();
+ }, [onClose]);
+
+ useEffect(() => {
+ if (open) {
+ htmlElement.classList.add('modal-open');
+
+ // Prevent modal from stretching window height
+ htmlElement.style.height = bodyElement.style.height;
+
+ return () => {
+ htmlElement.classList.remove('modal-open');
+ // Reset to initial value
+ htmlElement.style.height = '100%';
+ };
+ }
+ return noOp;
+ }, [open]);
+
+ if (!open) {
+ return null;
+ }
+
+ return (
+
+
+ {
+ setScroll(e.currentTarget.scrollTop);
+ }}
+ >
+ {children}
+
+
+ );
+}
diff --git a/packages/browser-wallet/src/popup/popupX/shared/FullscreenNotice/index.ts b/packages/browser-wallet/src/popup/popupX/shared/FullscreenNotice/index.ts
new file mode 100644
index 00000000..6301de42
--- /dev/null
+++ b/packages/browser-wallet/src/popup/popupX/shared/FullscreenNotice/index.ts
@@ -0,0 +1 @@
+export { default } from './FullscreenNotice';
diff --git a/packages/browser-wallet/src/popup/popupX/shared/PasswordProtect/PasswordProtect.scss b/packages/browser-wallet/src/popup/popupX/shared/PasswordProtect/PasswordProtect.scss
new file mode 100644
index 00000000..9a8fb103
--- /dev/null
+++ b/packages/browser-wallet/src/popup/popupX/shared/PasswordProtect/PasswordProtect.scss
@@ -0,0 +1,5 @@
+.confirm-password-x {
+ #confirm-password-form {
+ margin-top: rem(30px);
+ }
+}
diff --git a/packages/browser-wallet/src/popup/popupX/shared/PasswordProtect/PasswordProtect.tsx b/packages/browser-wallet/src/popup/popupX/shared/PasswordProtect/PasswordProtect.tsx
new file mode 100644
index 00000000..2df9ed1e
--- /dev/null
+++ b/packages/browser-wallet/src/popup/popupX/shared/PasswordProtect/PasswordProtect.tsx
@@ -0,0 +1,79 @@
+import React from 'react';
+import { Validate } from 'react-hook-form';
+import { useAtomValue } from 'jotai';
+import { useTranslation } from 'react-i18next';
+import Page from '@popup/popupX/shared/Page';
+import Text from '@popup/popupX/shared/Text';
+import Button from '@popup/popupX/shared/Button';
+import FormPassword from '@popup/popupX/shared/Form/Password';
+import Form from '@popup/popupX/shared/Form/Form';
+import { sessionPasscodeAtom } from '@popup/store/settings';
+import { useForm } from '@popup/shared/Form';
+import { TranslationKeyX } from '@popup/shell/i18n/i18n';
+
+type FormValues = {
+ currentPasscode: string;
+};
+
+export type PasswordProtectConfigType = {
+ headingKey: TranslationKeyX;
+ pageInfoKey: TranslationKeyX;
+ submitKey: TranslationKeyX;
+};
+
+type PasswordProtectProps = {
+ setPasswordConfirmed: (passwordConfirmed: boolean) => void;
+ config: PasswordProtectConfigType;
+};
+
+export default function PasswordProtect({
+ setPasswordConfirmed,
+ config: { headingKey, pageInfoKey, submitKey },
+}: PasswordProtectProps) {
+ const { t: tUse } = useTranslation('x');
+ const t = (key: TranslationKeyX) => tUse(key) as unknown as string;
+ const { t: tPasscode } = useTranslation('x', { keyPrefix: 'sharedX.form.password' });
+ const passcode = useAtomValue(sessionPasscodeAtom);
+ const form = useForm();
+
+ const handleSubmit = () => {
+ setPasswordConfirmed(true);
+ };
+
+ function validateCurrentPasscode(): Validate {
+ return (currentPasscode) => (currentPasscode !== passcode.value ? tPasscode('incorrectPasscode') : undefined);
+ }
+
+ return (
+
+
+
+ {t(pageInfoKey)}
+
+
+
+
+
+
+ );
+}
diff --git a/packages/browser-wallet/src/popup/popupX/shared/PasswordProtect/index.ts b/packages/browser-wallet/src/popup/popupX/shared/PasswordProtect/index.ts
new file mode 100644
index 00000000..d64c6e98
--- /dev/null
+++ b/packages/browser-wallet/src/popup/popupX/shared/PasswordProtect/index.ts
@@ -0,0 +1,2 @@
+export { default } from './PasswordProtect';
+export type { PasswordProtectConfigType } from './PasswordProtect';
diff --git a/packages/browser-wallet/src/popup/popupX/shared/i18n/en.ts b/packages/browser-wallet/src/popup/popupX/shared/i18n/en.ts
index 558045b2..49b2586a 100644
--- a/packages/browser-wallet/src/popup/popupX/shared/i18n/en.ts
+++ b/packages/browser-wallet/src/popup/popupX/shared/i18n/en.ts
@@ -5,6 +5,9 @@ const t = {
weak: 'Weak',
medium: 'Medium',
strong: 'Strong',
+ incorrectPasscode: 'Incorrect passcode',
+ currentPasscode: 'Enter current passcode',
+ passcodeRequired: 'A passcode must be entered',
},
tokenAmount: {
token: {
diff --git a/packages/browser-wallet/src/popup/popupX/shared/utils/hoc.tsx b/packages/browser-wallet/src/popup/popupX/shared/utils/hoc.tsx
index d8c54b5d..91961cdb 100644
--- a/packages/browser-wallet/src/popup/popupX/shared/utils/hoc.tsx
+++ b/packages/browser-wallet/src/popup/popupX/shared/utils/hoc.tsx
@@ -1,6 +1,7 @@
-import React from 'react';
+import React, { useState } from 'react';
import { useSelectedCredential } from '@popup/shared/utils/account-helpers';
import Loader from '@popup/popupX/shared/Loader';
+import PasswordProtect, { PasswordProtectConfigType } from '@popup/popupX/shared/PasswordProtect';
export function withSelectedCredential(
Component: React.ComponentType
@@ -16,3 +17,16 @@ export function withSelectedCredential
(
}
return NewComponent;
}
+
+export function withPasswordProtected(Component: React.ComponentType, config: PasswordProtectConfigType) {
+ function NewComponent() {
+ const [passwordConfirmed, setPasswordConfirmed] = useState(false);
+
+ if (!passwordConfirmed) {
+ return ;
+ }
+
+ return ;
+ }
+ return NewComponent;
+}
diff --git a/packages/browser-wallet/src/popup/popupX/shared/utils/typescriptHelpers.ts b/packages/browser-wallet/src/popup/popupX/shared/utils/typescriptHelpers.ts
new file mode 100644
index 00000000..be18801b
--- /dev/null
+++ b/packages/browser-wallet/src/popup/popupX/shared/utils/typescriptHelpers.ts
@@ -0,0 +1,4 @@
+// Generic type for iterating through nested object keys
+export type ObjectPath = {
+ [K in keyof T]: `${D}${Exclude}${'' | (T[K] extends object ? ObjectPath : '')}`;
+}[keyof T];
diff --git a/packages/browser-wallet/src/popup/popupX/styles/_elements.scss b/packages/browser-wallet/src/popup/popupX/styles/_elements.scss
index 8636c339..1d11dc03 100644
--- a/packages/browser-wallet/src/popup/popupX/styles/_elements.scss
+++ b/packages/browser-wallet/src/popup/popupX/styles/_elements.scss
@@ -37,10 +37,12 @@
@import '../shared/Text/Text';
@import '../shared/Loader/Loader';
@import '../shared/IdCard/IdCard';
+@import '../shared/PasswordProtect/PasswordProtect';
@import '../shared/Web3IdCard/Web3IdCard';
@import '../shared/Button/Button';
@import '../shared/ExternalLink/ExternalLink';
@import '../shared/Carousel/Carousel';
@import '../shared/Form/Form';
@import '../shared/Form/TokenAmount/TokenAmount';
+@import '../shared/FullscreenNotice/FullscreenNotice';
@import '../shared/EditableValue/EditableValue';
diff --git a/packages/browser-wallet/src/popup/shared/Modal/Modal.stories.tsx b/packages/browser-wallet/src/popup/shared/Modal/Modal.stories.tsx
index 6cc9a200..b5a97619 100644
--- a/packages/browser-wallet/src/popup/shared/Modal/Modal.stories.tsx
+++ b/packages/browser-wallet/src/popup/shared/Modal/Modal.stories.tsx
@@ -6,6 +6,7 @@ import Modal from './Modal';
export default {
title: 'Shared/Modal',
component: Modal,
+ tags: ['!autodocs'],
} as Meta;
type Story = StoryObj;
diff --git a/packages/browser-wallet/src/popup/shell/Root.tsx b/packages/browser-wallet/src/popup/shell/Root.tsx
index 8135cd16..788ecbb3 100644
--- a/packages/browser-wallet/src/popup/shell/Root.tsx
+++ b/packages/browser-wallet/src/popup/shell/Root.tsx
@@ -136,9 +136,7 @@ export default function Root() {
return (
-
+
diff --git a/packages/browser-wallet/src/popup/shell/i18n/i18n.ts b/packages/browser-wallet/src/popup/shell/i18n/i18n.ts
index ffeaca0e..2dfb128d 100644
--- a/packages/browser-wallet/src/popup/shell/i18n/i18n.ts
+++ b/packages/browser-wallet/src/popup/shell/i18n/i18n.ts
@@ -3,6 +3,7 @@ import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import countries from 'i18n-iso-countries';
+import { ObjectPath } from '@popup/popupX/shared/utils/typescriptHelpers';
import en from './locales/en';
import da from './locales/da';
@@ -10,6 +11,7 @@ import da from './locales/da';
countries.registerLocale(require('i18n-iso-countries/langs/en.json'));
countries.registerLocale(require('i18n-iso-countries/langs/da.json'));
+export type TranslationKeyX = ObjectPath;
export const defaultNS: keyof typeof en = 'shared';
const ns: Array = Object.keys(en) as Array;