Skip to content

Commit d42a4bb

Browse files
authored
feat: add email subscription modal (#763)
* refactor(constants): use production environment dev portal * refactor(constants): add comments * refactor(constants): remove todo comment * refactor(auth): add promo mode * refactor(auth): open modal after wallet is connected * refactor(auth): set modal to open after app restart if user clicks import MM * refactor(auth): use promo mode from LD * feat: add email promo * refactor(email-subscription): use LD flag * chore: add .env.example
1 parent fe250cf commit d42a4bb

File tree

14 files changed

+146
-2
lines changed

14 files changed

+146
-2
lines changed

.env.example

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
VITE_LD_ENVIRONMENT_ID=<LAUNCHDARKLY_ENVIRONMENT_ID>

src/backend/api/misc.ts

+4
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,10 @@ export const openAuthModalIfAppReloads = () => {
216216
ipcRenderer.send('openAuthModalIfAppReloads')
217217
}
218218

219+
export const openEmailModalIfAppReloads = () => {
220+
ipcRenderer.send('openEmailModalIfAppReloads')
221+
}
222+
219223
export const handleLogOut = (
220224
cb: WrapRendererCallback<() => void>
221225
): (() => void) => {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { contextBridge, ipcRenderer, webFrame } from 'electron'
2+
3+
const removeBackground = `
4+
document.onreadystatechange = function(e)
5+
{
6+
if (document.readyState === 'interactive')
7+
{
8+
const styles = 'body, html { background: transparent !important } div.layout-root { padding: 0px !important; padding-top: 10px !important; }'
9+
const styleSheet = document.createElement('style')
10+
styleSheet.innerText = styles
11+
document.head.appendChild(styleSheet)
12+
}
13+
};
14+
`
15+
16+
contextBridge.exposeInMainWorld('authApi', {
17+
closeAuthModal: () => {
18+
ipcRenderer.sendToHost('closeAuthModal')
19+
}
20+
})
21+
22+
webFrame.executeJavaScript(removeBackground)

src/backend/main.ts

+5
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,11 @@ if (!gotTheLock) {
423423
path.join(__dirname, 'auth_provider_preload.js')
424424
])
425425

426+
const emailModalSession = session.fromPartition('persist:emailModal')
427+
emailModalSession.setPreloads([
428+
path.join(__dirname, 'email_modal_provider_preload.js')
429+
])
430+
426431
const hpStoreSession = session.fromPartition('persist:hyperplaystore')
427432
hpStoreSession.setPreloads([
428433
path.join(__dirname, 'hyperplay_store_preload.js'),

src/common/constants.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,4 @@ export const METAMASK_SNAPS_URL = 'https://snaps.metamask.io/'
88
export const METAMASK_SNAPS_EXTERNAL_URL = 'https://metamask.io/snaps/'
99
export const METAMASK_PORTFOLIO_URL = 'https://portfolio.metamask.io/'
1010

11-
export const DEV_PORTAL_URL =
12-
'https://hyperplay-dev-git-feature-unified-auth-ui-valist.vercel.app/'
11+
export const DEV_PORTAL_URL = 'https://developers.hyperplay.xyz/'

src/common/typedefs/ipcBridge.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ interface HyperPlaySyncIPCFunctions {
7979
setQaToken: (qaToken: string) => void
8080
removeFromLibrary: (appName: string) => void
8181
openAuthModalIfAppReloads: () => void
82+
openEmailModalIfAppReloads: () => void
8283
overlayReady: () => void
8384
updateOverlayWindow: (state: OverlayWindowState) => void
8485
toggleIsPopupOpen: () => void

src/common/types/electron_store.ts

+1
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ export interface StoreStructure {
9898
completedEarlyAccess: boolean
9999
completedDataPrivacy: boolean
100100
openAuthModalIfAppReloads: boolean
101+
openEmailModalIfAppReloads: boolean
101102
}
102103
hpLibraryStore: {
103104
[key: string]: GameInfo[]

src/frontend/App.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import AuthModal from './components/UI/AuthModal'
3636
import { WalletOnboardCloseReason } from 'common/types'
3737
import { DeviceStateController } from './state/DeviceState'
3838
import { useFlags } from 'launchdarkly-react-client-sdk'
39+
import EmailSubscriptionModal from './components/UI/EmailSubscriptionModal'
3940

4041
function App() {
4142
const { sidebarCollapsed, isSettingsModalOpen, connectivity } =
@@ -58,6 +59,7 @@ function App() {
5859
<DialogHandler />
5960
<ExternalLinkDialog />
6061
<AuthModal />
62+
<EmailSubscriptionModal />
6163
<StoreNavHandler />
6264
{isSettingsModalOpen.gameInfo && (
6365
<SettingsModal
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.webview {
2+
width: 620px;
3+
height: 490px;
4+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import React, { useEffect, useRef } from 'react'
2+
import styles from './index.module.scss'
3+
import { ModalAnimation } from '@hyperplay/ui'
4+
import { WebviewTag } from 'electron'
5+
import { observer } from 'mobx-react-lite'
6+
import { DEV_PORTAL_URL } from 'common/constants'
7+
import emailSubscriptionState from '../../../state/EmailSubscriptionState'
8+
import { useFlags } from 'launchdarkly-react-client-sdk'
9+
10+
const url = `${DEV_PORTAL_URL}/signin?isLauncher=true&promoMode=true`
11+
12+
const EmailSubscriptionModal = () => {
13+
const flags = useFlags()
14+
const webviewRef = useRef<WebviewTag>(null)
15+
const isEnabled = flags.emailSubscriptionModal
16+
17+
useEffect(() => {
18+
const webview = webviewRef.current
19+
if (!webview) return
20+
21+
const handleIpcMessage = async (event: Electron.IpcMessageEvent) => {
22+
switch (event.channel) {
23+
case 'closeAuthModal':
24+
emailSubscriptionState.closeEmailModal()
25+
break
26+
default:
27+
break
28+
}
29+
}
30+
31+
const handleDomReady = () => {
32+
webview.addEventListener('ipc-message', handleIpcMessage)
33+
}
34+
35+
webview.addEventListener('dom-ready', handleDomReady)
36+
37+
return () => {
38+
webview.removeEventListener('dom-ready', handleDomReady)
39+
webview.removeEventListener('ipc-message', handleIpcMessage)
40+
}
41+
}, [])
42+
43+
return (
44+
<ModalAnimation
45+
isOpen={isEnabled && emailSubscriptionState.isEmailModalOpen}
46+
onClose={() => emailSubscriptionState.closeEmailModal()}
47+
>
48+
<webview
49+
ref={webviewRef}
50+
src={url}
51+
className={styles.webview}
52+
partition="persist:emailModal"
53+
allowpopups={'true' as unknown as boolean | undefined}
54+
useragent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.2 Safari/605.1.15"
55+
/>
56+
</ModalAnimation>
57+
)
58+
}
59+
60+
export default observer(EmailSubscriptionModal)

src/frontend/screens/Onboarding/walletSelection/index.tsx

+3
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import StatusScreen, { CONNECTION_STATUS } from './screens/status'
3333
import { faXmark } from '@fortawesome/free-solid-svg-icons'
3434
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
3535
import { ONBOARDING_SCREEN } from '../types'
36+
import emailSubscriptionState from '../../../state/EmailSubscriptionState'
3637

3738
enum WALLET_SELECTION_DETAILS_SCREEN {
3839
INFO = 'INFO',
@@ -144,6 +145,7 @@ const WalletSelection: React.FC<WalletSelectionProps> = function (props) {
144145
dbPath?: string,
145146
browser?: ImportableBrowser
146147
) {
148+
emailSubscriptionState.enableOpenEmailModalOnAppReload()
147149
if (mmInitMethod === 'CREATE' || mmInitMethod === 'SECRET_PHRASE') {
148150
window.api.createNewMetaMaskWallet(mmInitMethod)
149151
} else {
@@ -165,6 +167,7 @@ const WalletSelection: React.FC<WalletSelectionProps> = function (props) {
165167
})
166168
wait(4000).then(() => {
167169
props.disableOnboarding('connected')
170+
emailSubscriptionState.openEmailModal()
168171
})
169172
}
170173

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { onboardingStore } from 'frontend/helpers/electronStores'
2+
import { makeAutoObservable } from 'mobx'
3+
4+
class EmailSubscriptionState {
5+
private modalOpen = false
6+
7+
constructor() {
8+
makeAutoObservable(this)
9+
this.modalOpen = onboardingStore.get('openEmailModalIfAppReloads', false)
10+
onboardingStore.set('openEmailModalIfAppReloads', false)
11+
}
12+
13+
get isEmailModalOpen() {
14+
return this.modalOpen
15+
}
16+
17+
openEmailModal() {
18+
this.modalOpen = true
19+
}
20+
21+
closeEmailModal() {
22+
this.modalOpen = false
23+
}
24+
25+
enableOpenEmailModalOnAppReload() {
26+
onboardingStore.set('openEmailModalIfAppReloads', true)
27+
}
28+
}
29+
30+
const emailSubscriptionState = new EmailSubscriptionState()
31+
32+
export default emailSubscriptionState

src/frontend/state/authState.ts

+4
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ class AuthState {
4444
setPendingSignatureRequest(pending: boolean) {
4545
this.pendingSignatureRequest = pending
4646
}
47+
48+
enableOpenAuthModalOnAppReload() {
49+
onboardingStore.set('openAuthModalIfAppReloads', true)
50+
}
4751
}
4852

4953
const authState = new AuthState()

vite.config.ts

+6
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,12 @@ export default defineConfig({
102102
__dirname + '/src/backend/auth_provider_preload.ts'
103103
),
104104
vite: electronViteConfig
105+
},
106+
{
107+
entry: path.resolve(
108+
__dirname + '/src/backend/email_modal_provider_preload.ts'
109+
),
110+
vite: electronViteConfig
105111
}
106112
]),
107113
svgr()

0 commit comments

Comments
 (0)