Add send email dialog
This commit is contained in:
@@ -0,0 +1,48 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import {msg} from '@lingui/macro'
|
||||||
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
|
import {sanitizeAppLanguageSetting} from '#/locale/helpers'
|
||||||
|
import {APP_LANGUAGES} from '#/locale/languages'
|
||||||
|
import * as Select from '#/components/Select'
|
||||||
|
|
||||||
|
export function LanguageSelect({
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
}: {
|
||||||
|
value: string
|
||||||
|
onChange: (value: string) => void
|
||||||
|
}) {
|
||||||
|
const {_} = useLingui()
|
||||||
|
|
||||||
|
const handleOnChange = React.useCallback(
|
||||||
|
(value: string) => {
|
||||||
|
if (!value) return
|
||||||
|
onChange(sanitizeAppLanguageSetting(value))
|
||||||
|
},
|
||||||
|
[onChange],
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select.Root
|
||||||
|
value={sanitizeAppLanguageSetting(value)}
|
||||||
|
onValueChange={handleOnChange}>
|
||||||
|
<Select.Trigger label={_(msg`Select language`)}>
|
||||||
|
<Select.ValueText />
|
||||||
|
<Select.Icon />
|
||||||
|
</Select.Trigger>
|
||||||
|
<Select.Content
|
||||||
|
renderItem={({label, value}) => (
|
||||||
|
<Select.Item value={value} label={label}>
|
||||||
|
<Select.ItemIndicator />
|
||||||
|
<Select.ItemText>{label}</Select.ItemText>
|
||||||
|
</Select.Item>
|
||||||
|
)}
|
||||||
|
items={APP_LANGUAGES.map(l => ({
|
||||||
|
label: l.name,
|
||||||
|
value: l.code2,
|
||||||
|
}))}
|
||||||
|
/>
|
||||||
|
</Select.Root>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import {View} from 'react-native'
|
||||||
|
import {Trans} from '@lingui/macro'
|
||||||
|
|
||||||
|
import {atoms as a, select, useTheme} from '#/alf'
|
||||||
|
import {Shield_Stroke2_Corner0_Rounded as Shield} from '#/components/icons/Shield'
|
||||||
|
import {Text} from '#/components/Typography'
|
||||||
|
|
||||||
|
export function AgeAssuranceBadge() {
|
||||||
|
const t = useTheme()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.flex_row,
|
||||||
|
a.align_center,
|
||||||
|
a.gap_xs,
|
||||||
|
a.px_sm,
|
||||||
|
a.py_xs,
|
||||||
|
a.pr_sm,
|
||||||
|
a.rounded_full,
|
||||||
|
{
|
||||||
|
backgroundColor: select(t.name, {
|
||||||
|
light: t.palette.primary_100,
|
||||||
|
dark: t.palette.primary_100,
|
||||||
|
dim: t.palette.primary_100,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
]}>
|
||||||
|
<Shield size="sm" />
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
a.font_bold,
|
||||||
|
a.leading_snug,
|
||||||
|
{
|
||||||
|
color: select(t.name, {
|
||||||
|
light: t.palette.primary_800,
|
||||||
|
dark: t.palette.primary_800,
|
||||||
|
dim: t.palette.primary_800,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
]}>
|
||||||
|
<Trans>Age Assurance</Trans>
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,179 @@
|
|||||||
|
import {useState} from 'react'
|
||||||
|
import {View} from 'react-native'
|
||||||
|
import {msg, Trans} from '@lingui/macro'
|
||||||
|
import {useLingui} from '@lingui/react'
|
||||||
|
import {validate as validateEmail} from 'email-validator'
|
||||||
|
|
||||||
|
import {useLanguagePrefs} from '#/state/preferences'
|
||||||
|
import {useSession} from '#/state/session'
|
||||||
|
import {atoms as a, useTheme, web} from '#/alf'
|
||||||
|
import {AgeAssuranceBadge} from '#/components/ageAssurance/AgeAssuranceBadge'
|
||||||
|
import {urls} from '#/components/ageAssurance/const'
|
||||||
|
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||||
|
import * as Dialog from '#/components/Dialog'
|
||||||
|
import {Divider} from '#/components/Divider'
|
||||||
|
import * as TextField from '#/components/forms/TextField'
|
||||||
|
import {Envelope_Stroke2_Corner0_Rounded as Envelope} from '#/components/icons/Envelope'
|
||||||
|
import {LanguageSelect} from '#/components/LanguageSelect'
|
||||||
|
import {InlineLinkText} from '#/components/Link'
|
||||||
|
import {Text} from '#/components/Typography'
|
||||||
|
|
||||||
|
export {useDialogControl} from '#/components/Dialog/context'
|
||||||
|
|
||||||
|
export function AgeAssuranceInitDialog({
|
||||||
|
control,
|
||||||
|
}: {
|
||||||
|
control: Dialog.DialogControlProps
|
||||||
|
}) {
|
||||||
|
const {_} = useLingui()
|
||||||
|
return (
|
||||||
|
<Dialog.Outer control={control}>
|
||||||
|
<Dialog.Handle />
|
||||||
|
|
||||||
|
<Dialog.ScrollableInner
|
||||||
|
label={_(
|
||||||
|
'Begin the age assurance process by completing the fields below.',
|
||||||
|
)}
|
||||||
|
style={[
|
||||||
|
web({
|
||||||
|
maxWidth: 400,
|
||||||
|
}),
|
||||||
|
]}>
|
||||||
|
<Inner />
|
||||||
|
<Dialog.Close />
|
||||||
|
</Dialog.ScrollableInner>
|
||||||
|
</Dialog.Outer>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function Inner() {
|
||||||
|
const t = useTheme()
|
||||||
|
const {_} = useLingui()
|
||||||
|
const {currentAccount} = useSession()
|
||||||
|
const langPrefs = useLanguagePrefs()
|
||||||
|
|
||||||
|
const [email, setEmail] = useState(currentAccount?.email || '')
|
||||||
|
const [emailValid, setEmailValid] = useState(validateEmail(email))
|
||||||
|
const [language, setLanguage] = useState(langPrefs.appLanguage)
|
||||||
|
|
||||||
|
const onSubmit = async () => {
|
||||||
|
try {
|
||||||
|
if (!validateEmail(email)) {
|
||||||
|
setEmailValid(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View>
|
||||||
|
<View style={[a.align_start]}>
|
||||||
|
<AgeAssuranceBadge />
|
||||||
|
|
||||||
|
<Text style={[a.text_xl, a.font_heavy, a.pt_xl, a.pb_md]}>
|
||||||
|
<Trans>Verify your age</Trans>
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<View style={[a.pb_xl, a.gap_sm]}>
|
||||||
|
<Text style={[a.text_sm, a.leading_snug]}>
|
||||||
|
<Trans>
|
||||||
|
We use{' '}
|
||||||
|
<InlineLinkText
|
||||||
|
label={urls.kwsHome}
|
||||||
|
to={urls.kwsHome}
|
||||||
|
style={[a.text_sm, a.leading_snug]}>
|
||||||
|
KWS
|
||||||
|
</InlineLinkText>{' '}
|
||||||
|
to verify that you’re an adult. When you click "Get an email"
|
||||||
|
below, Bluesky will share your email address with KWS.
|
||||||
|
</Trans>
|
||||||
|
</Text>
|
||||||
|
<Text style={[a.text_sm, a.leading_snug]}>
|
||||||
|
<Trans>
|
||||||
|
If KWS cannot use your email address to confirm that you
|
||||||
|
previously verified your age, you’ll be taken to the KWS website
|
||||||
|
to provide more details.
|
||||||
|
</Trans>
|
||||||
|
</Text>
|
||||||
|
<Text style={[a.text_sm, a.leading_snug]}>
|
||||||
|
<Trans>This should only take a few minutes.</Trans>
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<View style={[a.w_full, a.pt_xl, a.gap_md]}>
|
||||||
|
<View>
|
||||||
|
<TextField.LabelText>
|
||||||
|
<Trans>Your email</Trans>
|
||||||
|
</TextField.LabelText>
|
||||||
|
<TextField.Root isInvalid={!emailValid}>
|
||||||
|
<TextField.Input
|
||||||
|
label={_(msg`Your email`)}
|
||||||
|
placeholder={_(msg`Your email`)}
|
||||||
|
value={email}
|
||||||
|
onChangeText={setEmail}
|
||||||
|
onFocus={() => setEmailValid(true)}
|
||||||
|
onBlur={() => {
|
||||||
|
setEmailValid(validateEmail(email))
|
||||||
|
}}
|
||||||
|
returnKeyType="done"
|
||||||
|
autoCapitalize="none"
|
||||||
|
autoComplete="off"
|
||||||
|
autoCorrect={false}
|
||||||
|
onSubmitEditing={onSubmit}
|
||||||
|
/>
|
||||||
|
</TextField.Root>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View>
|
||||||
|
<TextField.LabelText>
|
||||||
|
<Trans>Your preferred language</Trans>
|
||||||
|
</TextField.LabelText>
|
||||||
|
<LanguageSelect value={language} onChange={setLanguage} />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
label={_(msg`Get an email`)}
|
||||||
|
size="large"
|
||||||
|
variant="solid"
|
||||||
|
color="primary">
|
||||||
|
<ButtonText>
|
||||||
|
<Trans>Get an email</Trans>
|
||||||
|
</ButtonText>
|
||||||
|
<ButtonIcon icon={Envelope} position="right" />
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
a.text_xs,
|
||||||
|
a.leading_snug,
|
||||||
|
a.pt_lg,
|
||||||
|
t.atoms.text_contrast_medium,
|
||||||
|
]}>
|
||||||
|
<Trans>
|
||||||
|
By continuing, you agree to the{' '}
|
||||||
|
<InlineLinkText
|
||||||
|
label={urls.kwsTermsOfUse}
|
||||||
|
to={urls.kwsTermsOfUse}
|
||||||
|
style={[a.text_xs, a.leading_snug]}>
|
||||||
|
KWS Terms of Use
|
||||||
|
</InlineLinkText>{' '}
|
||||||
|
and acknowledge that KWS will store your verified status alongside
|
||||||
|
your hashed email address in accordance with the{' '}
|
||||||
|
<InlineLinkText
|
||||||
|
label={urls.kwsPrivacyPolicy}
|
||||||
|
to={urls.kwsPrivacyPolicy}
|
||||||
|
style={[a.text_xs, a.leading_snug]}>
|
||||||
|
KWS Privacy Policy
|
||||||
|
</InlineLinkText>
|
||||||
|
. This means you won’t need to verify again the next time you use
|
||||||
|
this email for other apps, games, and services powered by KWS
|
||||||
|
technology.
|
||||||
|
</Trans>
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -4,120 +4,98 @@ import {useLingui} from '@lingui/react'
|
|||||||
|
|
||||||
import {useAgeAssuranceContext} from '#/state/ageAssurance'
|
import {useAgeAssuranceContext} from '#/state/ageAssurance'
|
||||||
import {atoms as a, select, useTheme, type ViewStyleProp} from '#/alf'
|
import {atoms as a, select, useTheme, type ViewStyleProp} from '#/alf'
|
||||||
|
import {AgeAssuranceBadge} from '#/components/ageAssurance/AgeAssuranceBadge'
|
||||||
|
import {
|
||||||
|
AgeAssuranceInitDialog,
|
||||||
|
useDialogControl,
|
||||||
|
} from '#/components/ageAssurance/AgeAssuranceInitDialog'
|
||||||
import {Button, ButtonText} from '#/components/Button'
|
import {Button, ButtonText} from '#/components/Button'
|
||||||
import {Divider} from '#/components/Divider'
|
import {Divider} from '#/components/Divider'
|
||||||
import {Shield_Stroke2_Corner0_Rounded as Shield} from '#/components/icons/Shield'
|
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
|
|
||||||
export function AgeAssurancePrompt({style}: ViewStyleProp & {}) {
|
export function AgeAssurancePrompt({style}: ViewStyleProp & {}) {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const {hasInitiated} = useAgeAssuranceContext()
|
const {hasInitiated} = useAgeAssuranceContext()
|
||||||
|
const control = useDialogControl()
|
||||||
|
|
||||||
return hasInitiated ? null : (
|
return hasInitiated ? null : (
|
||||||
<View style={style}>
|
<>
|
||||||
<View
|
<AgeAssuranceInitDialog control={control} />
|
||||||
style={[
|
|
||||||
a.p_lg,
|
|
||||||
a.rounded_md,
|
|
||||||
a.border,
|
|
||||||
{
|
|
||||||
backgroundColor: select(t.name, {
|
|
||||||
light: t.palette.primary_25,
|
|
||||||
dark: t.palette.primary_25,
|
|
||||||
dim: t.palette.primary_25,
|
|
||||||
}),
|
|
||||||
borderColor: select(t.name, {
|
|
||||||
light: t.palette.primary_100,
|
|
||||||
dark: t.palette.primary_100,
|
|
||||||
dim: t.palette.primary_100,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
]}>
|
|
||||||
<View style={[a.align_start, a.pb_md]}>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.flex_row,
|
|
||||||
a.align_center,
|
|
||||||
a.gap_xs,
|
|
||||||
a.px_sm,
|
|
||||||
a.py_xs,
|
|
||||||
a.pr_sm,
|
|
||||||
a.rounded_full,
|
|
||||||
{
|
|
||||||
backgroundColor: select(t.name, {
|
|
||||||
light: t.palette.primary_100,
|
|
||||||
dark: t.palette.primary_100,
|
|
||||||
dim: t.palette.primary_100,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
]}>
|
|
||||||
<Shield size="sm" />
|
|
||||||
<Text
|
|
||||||
style={[
|
|
||||||
a.font_bold,
|
|
||||||
a.leading_snug,
|
|
||||||
{
|
|
||||||
color: select(t.name, {
|
|
||||||
light: t.palette.primary_800,
|
|
||||||
dark: t.palette.primary_800,
|
|
||||||
dim: t.palette.primary_800,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
]}>
|
|
||||||
<Trans>Age Assurance</Trans>
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
<Text style={[a.text_md, a.leading_snug]}>
|
|
||||||
You're currently accessing Bluesky from a location that by law
|
|
||||||
requires you to verify your age prior to accessing certain content and
|
|
||||||
features.
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<Divider
|
<View style={style}>
|
||||||
|
<View
|
||||||
style={[
|
style={[
|
||||||
a.mt_lg,
|
a.p_lg,
|
||||||
|
a.rounded_md,
|
||||||
|
a.border,
|
||||||
{
|
{
|
||||||
|
backgroundColor: select(t.name, {
|
||||||
|
light: t.palette.primary_25,
|
||||||
|
dark: t.palette.primary_25,
|
||||||
|
dim: t.palette.primary_25,
|
||||||
|
}),
|
||||||
borderColor: select(t.name, {
|
borderColor: select(t.name, {
|
||||||
light: t.palette.primary_100,
|
light: t.palette.primary_100,
|
||||||
dark: t.palette.primary_100,
|
dark: t.palette.primary_100,
|
||||||
dim: t.palette.primary_100,
|
dim: t.palette.primary_100,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
]}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
a.flex_row,
|
|
||||||
a.justify_between,
|
|
||||||
a.align_center,
|
|
||||||
a.pt_md,
|
|
||||||
a.gap_lg,
|
|
||||||
]}>
|
]}>
|
||||||
<Text
|
<View style={[a.align_start, a.pb_md]}>
|
||||||
style={{
|
<AgeAssuranceBadge />
|
||||||
color: select(t.name, {
|
</View>
|
||||||
light: t.palette.primary_800,
|
<Text style={[a.text_md, a.leading_snug]}>
|
||||||
dark: t.palette.primary_800,
|
You're currently accessing Bluesky from a location that by law
|
||||||
dim: t.palette.primary_800,
|
requires you to verify your age prior to accessing certain content
|
||||||
}),
|
and features.
|
||||||
}}>
|
|
||||||
<Trans>Verification takes only a few minutes</Trans>
|
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<Button
|
<Divider
|
||||||
label={_(msg`Verify now`)}
|
style={[
|
||||||
size="small"
|
a.mt_lg,
|
||||||
variant="solid"
|
{
|
||||||
color="primary">
|
borderColor: select(t.name, {
|
||||||
<ButtonText>
|
light: t.palette.primary_100,
|
||||||
<Trans>Verify now</Trans>
|
dark: t.palette.primary_100,
|
||||||
</ButtonText>
|
dim: t.palette.primary_100,
|
||||||
</Button>
|
}),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.flex_row,
|
||||||
|
a.justify_between,
|
||||||
|
a.align_center,
|
||||||
|
a.pt_md,
|
||||||
|
a.gap_lg,
|
||||||
|
]}>
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
color: select(t.name, {
|
||||||
|
light: t.palette.primary_800,
|
||||||
|
dark: t.palette.primary_800,
|
||||||
|
dim: t.palette.primary_800,
|
||||||
|
}),
|
||||||
|
}}>
|
||||||
|
<Trans>Verification takes only a few minutes</Trans>
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
label={_(msg`Verify now`)}
|
||||||
|
size="small"
|
||||||
|
variant="solid"
|
||||||
|
color="primary"
|
||||||
|
onPress={() => control.open()}>
|
||||||
|
<ButtonText>
|
||||||
|
<Trans>Verify now</Trans>
|
||||||
|
</ButtonText>
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export const urls = {
|
||||||
|
kwsHome: 'https://dev.epicgames.com/docs/kids-web-services',
|
||||||
|
kwsTermsOfUse: 'https://www.kidswebservices.com/en-US/terms-of-use',
|
||||||
|
kwsPrivacyPolicy: 'https://www.kidswebservices.com/en-US/privacy-policy',
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user