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 {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 {Divider} from '#/components/Divider'
|
||||
import {Shield_Stroke2_Corner0_Rounded as Shield} from '#/components/icons/Shield'
|
||||
import {Text} from '#/components/Typography'
|
||||
|
||||
export function AgeAssurancePrompt({style}: ViewStyleProp & {}) {
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
const {hasInitiated} = useAgeAssuranceContext()
|
||||
const control = useDialogControl()
|
||||
|
||||
return hasInitiated ? null : (
|
||||
<View style={style}>
|
||||
<View
|
||||
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>
|
||||
<>
|
||||
<AgeAssuranceInitDialog control={control} />
|
||||
|
||||
<Divider
|
||||
<View style={style}>
|
||||
<View
|
||||
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, {
|
||||
light: t.palette.primary_100,
|
||||
dark: 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
|
||||
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>
|
||||
<View style={[a.align_start, a.pb_md]}>
|
||||
<AgeAssuranceBadge />
|
||||
</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>
|
||||
|
||||
<Button
|
||||
label={_(msg`Verify now`)}
|
||||
size="small"
|
||||
variant="solid"
|
||||
color="primary">
|
||||
<ButtonText>
|
||||
<Trans>Verify now</Trans>
|
||||
</ButtonText>
|
||||
</Button>
|
||||
<Divider
|
||||
style={[
|
||||
a.mt_lg,
|
||||
{
|
||||
borderColor: select(t.name, {
|
||||
light: t.palette.primary_100,
|
||||
dark: 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
|
||||
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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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