-
Notifications
You must be signed in to change notification settings - Fork 68
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add: extension invite code modal and top bar invite code option
- Loading branch information
1 parent
b4eae1a
commit 89219b7
Showing
13 changed files
with
742 additions
and
31 deletions.
There are no files selected for viewing
106 changes: 106 additions & 0 deletions
106
src/components/Modals/ExtensionInviteCodeModal/ExtensionInviteCodeModal.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
import { useCallback, useEffect, useState } from 'react' | ||
import cn from 'classnames' | ||
|
||
import { useModals } from 'hooks' | ||
|
||
import { ReactComponent as AmbireLogo } from 'resources/logo-new.svg' | ||
import { ReactComponent as CopyIcon } from 'resources/icons/copy-new.svg' | ||
import { ReactComponent as CloseIcon } from 'resources/icons/close.svg' | ||
import { ReactComponent as ChromeWebStore } from 'resources/chrome-web-store.svg' | ||
|
||
import { useToasts } from 'hooks/toasts' | ||
import styles from './ExtensionInviteCodeModal.module.scss' | ||
|
||
const CAN_CLOSE_AFTER_MS = 5000 | ||
|
||
const ExtensionInviteCodeModal = ({ | ||
inviteCode, | ||
setExtensionInviteCodeModalSeenBy, | ||
accountId, | ||
waitForClose = true | ||
}) => { | ||
const { onHideModal } = useModals() | ||
const { addToast } = useToasts() | ||
const [canClose, setCanClose] = useState(!waitForClose) | ||
|
||
const onCloseModal = useCallback(() => { | ||
onHideModal() | ||
setExtensionInviteCodeModalSeenBy((prev) => [...prev, accountId]) | ||
}, [accountId, onHideModal, setExtensionInviteCodeModalSeenBy]) | ||
|
||
useEffect(() => { | ||
const startingTime = Date.now() | ||
|
||
const timeout = setTimeout(() => { | ||
if (Date.now() - startingTime < CAN_CLOSE_AFTER_MS) return | ||
|
||
setCanClose(true) | ||
}, CAN_CLOSE_AFTER_MS) | ||
|
||
return () => { | ||
clearTimeout(timeout) | ||
} | ||
}) | ||
|
||
const handleCopy = useCallback(async () => { | ||
try { | ||
await navigator.clipboard.writeText(inviteCode) | ||
addToast('Invite code copied to clipboard') | ||
} catch { | ||
addToast('Failed to copy invite code to clipboard', { error: true }) | ||
} | ||
}, [addToast, inviteCode]) | ||
|
||
return ( | ||
<div className={styles.wrapper}> | ||
<div className={styles.header}> | ||
<div className={styles.headerPrimaryGradient} /> | ||
<div className={styles.headerSecondaryGradient} /> | ||
<AmbireLogo className={styles.headerLogo} width={92} height={96} /> | ||
<p className={styles.headerText}>Easy and secure self-custody for the Ethereum ecosystem</p> | ||
<CloseIcon | ||
className={cn(styles.closeIcon, { | ||
[styles.closeIconEnabled]: canClose | ||
})} | ||
onClick={onCloseModal} | ||
/> | ||
</div> | ||
<div className={styles.content}> | ||
<div className={styles.textWrapper}> | ||
<p className={styles.text}>Hey!</p> | ||
<p className={styles.text}> | ||
We are onboarding only selected Ambire Wallet users to our newest product - the Ambire | ||
browser extension. | ||
</p> | ||
<p className={styles.text}> | ||
Claim this exclusive invitation code to get early access and start collecting XP for our | ||
launch campaign before everyone else 🤫 | ||
</p> | ||
</div> | ||
<div className={styles.codeWrapper}> | ||
<span className={styles.codeTitle}>Invitation code</span> | ||
<span className={styles.code}>{inviteCode}</span> | ||
<button className={styles.copyButton} type="button" onClick={handleCopy}> | ||
<CopyIcon /> | ||
<span>Copy</span> | ||
</button> | ||
</div> | ||
<div className={styles.storeWrapper}> | ||
<p className={styles.storeText}> | ||
Go to Chrome Web Store, install the extension and use the invitation code to log in. | ||
</p> | ||
<a | ||
className={styles.storeLink} | ||
href="https://chromewebstore.google.com/detail/ambire-wallet/ehgjhhccekdedpbkifaojjaefeohnoea" | ||
target="_blank" | ||
rel="noreferrer" | ||
> | ||
<ChromeWebStore /> | ||
</a> | ||
</div> | ||
</div> | ||
</div> | ||
) | ||
} | ||
|
||
export default ExtensionInviteCodeModal |
241 changes: 241 additions & 0 deletions
241
src/components/Modals/ExtensionInviteCodeModal/ExtensionInviteCodeModal.module.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,241 @@ | ||
@import 'variables.scss'; | ||
|
||
$max-modal-height: 55.375rem; | ||
$min-modal-height: 34.375rem; | ||
|
||
.wrapper { | ||
z-index: 52; | ||
position: relative; | ||
max-height: $max-modal-height; | ||
min-height: $min-modal-height; | ||
max-width: 37.75rem; | ||
width: 100%; | ||
border-radius: 1.125rem; | ||
border: 1px #3e436b; | ||
background: #24263d; | ||
} | ||
|
||
// Reset | ||
.wrapper p, | ||
.wrapper span { | ||
margin: 0; | ||
} | ||
|
||
.header { | ||
overflow: hidden; | ||
position: relative; | ||
display: flex; | ||
flex-direction: column; | ||
align-items: center; | ||
padding: 2rem; | ||
background: linear-gradient(82deg, #6000ff, #353d6e); | ||
border-radius: 0.75rem 0.75rem 0 0; | ||
|
||
.headerPrimaryGradient { | ||
position: absolute; | ||
left: -20%; | ||
top: -35%; | ||
z-index: 2; | ||
width: 20rem; | ||
height: 13.75rem; | ||
background-color: #8b3dff; | ||
opacity: 0.66; | ||
filter: blur(4.375rem); | ||
} | ||
|
||
.headerSecondaryGradient { | ||
position: absolute; | ||
left: 60%; | ||
bottom: 30%; | ||
z-index: 2; | ||
width: 26.25rem; | ||
height: 18.5rem; | ||
background-color: #56f6c1; | ||
opacity: 0.48; | ||
filter: blur(6.25rem); | ||
} | ||
|
||
.closeIcon { | ||
position: absolute; | ||
right: 1rem; | ||
top: 1rem; | ||
z-index: 4; | ||
cursor: pointer; | ||
|
||
:global(#background) { | ||
display: none; | ||
} | ||
|
||
&:not(.closeIconEnabled) { | ||
opacity: .4; | ||
cursor: not-allowed; | ||
} | ||
} | ||
|
||
.headerLogo { | ||
position: relative; | ||
z-index: 3; | ||
margin-bottom: 1.5rem; | ||
} | ||
.headerText { | ||
position: relative; | ||
z-index: 3; | ||
font-size: 1rem; | ||
color: rgba(255, 255, 255, 0.6); | ||
text-align: center; | ||
} | ||
} | ||
|
||
.content { | ||
display: flex; | ||
flex-direction: column; | ||
padding: 2rem; | ||
gap: 2.5rem; | ||
} | ||
|
||
.textWrapper { | ||
.text { | ||
font-size: 1.125rem; | ||
line-height: 1.7; | ||
margin-bottom: 1rem; | ||
} | ||
.text:last-child { | ||
margin-bottom: 0; | ||
} | ||
} | ||
|
||
.codeWrapper { | ||
display: flex; | ||
flex-direction: column; | ||
align-items: center; | ||
.codeTitle { | ||
display: block; | ||
color: #bbbde4; | ||
font-size: 1rem; | ||
margin-bottom: 1rem; | ||
} | ||
.code { | ||
display: block; | ||
margin-bottom: 2rem; | ||
border: none; | ||
width: 18ch; | ||
background: repeating-linear-gradient( | ||
90deg, | ||
#6770b3 0, | ||
#6770b3 1ch, | ||
transparent 0, | ||
transparent 1.5ch | ||
) | ||
0 100%/98% 1px no-repeat; | ||
color: #27e8a7; // font: 36px monospace; | ||
font-size: 2.5rem; | ||
font-family: monospace; | ||
letter-spacing: 0.5ch; | ||
} | ||
.copyButton { | ||
display: flex; | ||
align-items: center; | ||
gap: 0.5rem; | ||
padding: 0.5rem 1.5rem; | ||
background: rgba(235, 236, 255, 0.08); | ||
border-radius: 1.5rem; | ||
cursor: pointer; | ||
|
||
svg, | ||
span { | ||
color: #bbbde4; | ||
transition: $basic-transition; | ||
} | ||
|
||
svg { | ||
width: 1.125rem; | ||
height: 1.125rem; | ||
} | ||
|
||
&:hover { | ||
background: rgba(235, 236, 255, 0.15); | ||
|
||
svg, | ||
span { | ||
color: #fff; | ||
} | ||
} | ||
} | ||
} | ||
|
||
.storeWrapper { | ||
.storeText { | ||
font-size: 1.125rem; | ||
margin-bottom: 2rem; | ||
} | ||
.storeLink { | ||
display: flex; | ||
justify-content: center; | ||
align-items: center; | ||
width: fit-content; | ||
gap: 0.5rem; | ||
padding: 1rem 3rem; | ||
margin: 0 auto; | ||
border-radius: 4rem; | ||
border: 1px solid #6770b3; | ||
background: rgba(0, 0, 0, 0.3); | ||
cursor: pointer; | ||
|
||
svg { | ||
width: 9.25rem; | ||
height: 2rem; | ||
} | ||
|
||
&:hover { | ||
background-color: #0e0e0e; | ||
} | ||
} | ||
} | ||
|
||
@mixin SmallScreen { | ||
.header { | ||
padding: 1.5rem; | ||
|
||
.headerLogo { | ||
margin-bottom: 1rem; | ||
} | ||
|
||
.headerText { | ||
font-size: .75rem; | ||
} | ||
} | ||
.content { | ||
gap: 1.5rem; | ||
} | ||
.storeWrapper .storeText, .textWrapper .text { | ||
font-size: 0.875rem; | ||
} | ||
.storeWrapper .storeText { | ||
margin-bottom: 1rem; | ||
} | ||
.codeWrapper { | ||
.codeTitle { | ||
font-size: 0.875rem; | ||
margin-bottom: .5rem; | ||
} | ||
.code { | ||
margin-bottom: 1.5rem; | ||
} | ||
} | ||
} | ||
|
||
@media screen and (max-height: calc(#{$max-modal-height} + 100px)) { | ||
@include SmallScreen; | ||
} | ||
|
||
@include sm-breakpoint { | ||
@include SmallScreen; | ||
.header, .content { | ||
padding: 1.5rem; | ||
} | ||
.codeWrapper { | ||
.code { | ||
font-size: 2rem; | ||
} | ||
} | ||
} |
Oops, something went wrong.