541 lines
16 KiB
TypeScript
541 lines
16 KiB
TypeScript
|
|
import { FC, useCallback, useEffect, useRef, useState } from "react"
|
|||
|
|
import { TextStyle, View, ViewStyle } from "react-native"
|
|||
|
|
import { KeyboardAwareScrollView } from "react-native-keyboard-controller"
|
|||
|
|
|
|||
|
|
import { Button } from "@/components/Button"
|
|||
|
|
import { Dialog } from "@/components/Dialog"
|
|||
|
|
import { Screen } from "@/components/Screen"
|
|||
|
|
import { Text } from "@/components/Text"
|
|||
|
|
import { TextField } from "@/components/TextField"
|
|||
|
|
import { useAuth } from "@/context/AuthContext"
|
|||
|
|
import { translate } from "@/i18n/translate"
|
|||
|
|
import { AppStackScreenProps } from "@/navigators/navigationTypes"
|
|||
|
|
import { useAppTheme } from "@/theme/context"
|
|||
|
|
import { $styles } from "@/theme/styles"
|
|||
|
|
import type { ThemedStyle } from "@/theme/types"
|
|||
|
|
import { s } from "@/utils/responsive"
|
|||
|
|
import { useHeader } from "@/utils/useHeader"
|
|||
|
|
|
|||
|
|
const COUNTDOWN_SECONDS = 60
|
|||
|
|
|
|||
|
|
export const ChangeEmailScreen: FC<AppStackScreenProps<"ChangeEmail">> =
|
|||
|
|
function ChangeEmailScreen({ navigation }) {
|
|||
|
|
const {
|
|||
|
|
user,
|
|||
|
|
emailChangeStep,
|
|||
|
|
sendEmailCode,
|
|||
|
|
verifyCurrentEmail,
|
|||
|
|
bindNewEmail,
|
|||
|
|
resetEmailChangeStep,
|
|||
|
|
isLoading,
|
|||
|
|
error,
|
|||
|
|
clearError,
|
|||
|
|
} = useAuth()
|
|||
|
|
const { themed } = useAppTheme()
|
|||
|
|
|
|||
|
|
// Step 1: Verify current email
|
|||
|
|
const [currentEmailCode, setCurrentEmailCode] = useState("")
|
|||
|
|
|
|||
|
|
// Step 2: Bind new email
|
|||
|
|
const [newEmail, setNewEmail] = useState("")
|
|||
|
|
const [newEmailCode, setNewEmailCode] = useState("")
|
|||
|
|
const [newEmailCodeSent, setNewEmailCodeSent] = useState(false)
|
|||
|
|
|
|||
|
|
const [localError, setLocalError] = useState("")
|
|||
|
|
const [successDialogVisible, setSuccessDialogVisible] = useState(false)
|
|||
|
|
|
|||
|
|
// Countdown timers
|
|||
|
|
const [currentEmailCountdown, setCurrentEmailCountdown] = useState(0)
|
|||
|
|
const [newEmailCountdown, setNewEmailCountdown] = useState(0)
|
|||
|
|
const currentEmailTimerRef = useRef<ReturnType<typeof setInterval> | null>(null)
|
|||
|
|
const newEmailTimerRef = useRef<ReturnType<typeof setInterval> | null>(null)
|
|||
|
|
|
|||
|
|
// Start countdown for current email
|
|||
|
|
const startCurrentEmailCountdown = useCallback(() => {
|
|||
|
|
setCurrentEmailCountdown(COUNTDOWN_SECONDS)
|
|||
|
|
if (currentEmailTimerRef.current) {
|
|||
|
|
clearInterval(currentEmailTimerRef.current)
|
|||
|
|
}
|
|||
|
|
currentEmailTimerRef.current = setInterval(() => {
|
|||
|
|
setCurrentEmailCountdown((prev) => {
|
|||
|
|
if (prev <= 1) {
|
|||
|
|
if (currentEmailTimerRef.current) {
|
|||
|
|
clearInterval(currentEmailTimerRef.current)
|
|||
|
|
currentEmailTimerRef.current = null
|
|||
|
|
}
|
|||
|
|
return 0
|
|||
|
|
}
|
|||
|
|
return prev - 1
|
|||
|
|
})
|
|||
|
|
}, 1000)
|
|||
|
|
}, [])
|
|||
|
|
|
|||
|
|
// Start countdown for new email
|
|||
|
|
const startNewEmailCountdown = useCallback(() => {
|
|||
|
|
setNewEmailCountdown(COUNTDOWN_SECONDS)
|
|||
|
|
if (newEmailTimerRef.current) {
|
|||
|
|
clearInterval(newEmailTimerRef.current)
|
|||
|
|
}
|
|||
|
|
newEmailTimerRef.current = setInterval(() => {
|
|||
|
|
setNewEmailCountdown((prev) => {
|
|||
|
|
if (prev <= 1) {
|
|||
|
|
if (newEmailTimerRef.current) {
|
|||
|
|
clearInterval(newEmailTimerRef.current)
|
|||
|
|
newEmailTimerRef.current = null
|
|||
|
|
}
|
|||
|
|
return 0
|
|||
|
|
}
|
|||
|
|
return prev - 1
|
|||
|
|
})
|
|||
|
|
}, 1000)
|
|||
|
|
}, [])
|
|||
|
|
|
|||
|
|
// Reset state when unmounting
|
|||
|
|
useEffect(() => {
|
|||
|
|
return () => {
|
|||
|
|
resetEmailChangeStep()
|
|||
|
|
if (currentEmailTimerRef.current) {
|
|||
|
|
clearInterval(currentEmailTimerRef.current)
|
|||
|
|
}
|
|||
|
|
if (newEmailTimerRef.current) {
|
|||
|
|
clearInterval(newEmailTimerRef.current)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}, [resetEmailChangeStep])
|
|||
|
|
|
|||
|
|
// Step 1: Send verification code to current email
|
|||
|
|
const handleSendCurrentEmailCode = useCallback(async () => {
|
|||
|
|
clearError()
|
|||
|
|
setLocalError("")
|
|||
|
|
if (user?.email) {
|
|||
|
|
const success = await sendEmailCode(user.email)
|
|||
|
|
if (success) {
|
|||
|
|
startCurrentEmailCountdown()
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}, [sendEmailCode, clearError, user?.email, startCurrentEmailCountdown])
|
|||
|
|
|
|||
|
|
// Step 1: Verify current email code
|
|||
|
|
const handleVerifyCurrentEmail = useCallback(async () => {
|
|||
|
|
clearError()
|
|||
|
|
setLocalError("")
|
|||
|
|
|
|||
|
|
if (!currentEmailCode) {
|
|||
|
|
setLocalError(translate("changeEmailScreen:codeRequired"))
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (currentEmailCode.length !== 6) {
|
|||
|
|
setLocalError(translate("changeEmailScreen:codeInvalid"))
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
await verifyCurrentEmail(currentEmailCode)
|
|||
|
|
}, [currentEmailCode, verifyCurrentEmail, clearError])
|
|||
|
|
|
|||
|
|
// Step 2: Send verification code to new email
|
|||
|
|
const handleSendNewEmailCode = useCallback(async () => {
|
|||
|
|
clearError()
|
|||
|
|
setLocalError("")
|
|||
|
|
|
|||
|
|
if (!newEmail) {
|
|||
|
|
setLocalError(translate("changeEmailScreen:newEmailRequired"))
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Basic email validation
|
|||
|
|
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(newEmail)) {
|
|||
|
|
setLocalError(translate("changeEmailScreen:emailInvalid"))
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (newEmail.toLowerCase() === user?.email?.toLowerCase()) {
|
|||
|
|
setLocalError(translate("changeEmailScreen:sameEmail"))
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const success = await sendEmailCode(newEmail)
|
|||
|
|
if (success) {
|
|||
|
|
setNewEmailCodeSent(true)
|
|||
|
|
startNewEmailCountdown()
|
|||
|
|
}
|
|||
|
|
}, [newEmail, user?.email, sendEmailCode, clearError, startNewEmailCountdown])
|
|||
|
|
|
|||
|
|
// Step 2: Bind new email
|
|||
|
|
const handleBindNewEmail = useCallback(async () => {
|
|||
|
|
clearError()
|
|||
|
|
setLocalError("")
|
|||
|
|
|
|||
|
|
if (!newEmailCode) {
|
|||
|
|
setLocalError(translate("changeEmailScreen:codeRequired"))
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (newEmailCode.length !== 6) {
|
|||
|
|
setLocalError(translate("changeEmailScreen:codeInvalid"))
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const success = await bindNewEmail(newEmail, newEmailCode)
|
|||
|
|
if (success) {
|
|||
|
|
setSuccessDialogVisible(true)
|
|||
|
|
}
|
|||
|
|
}, [newEmail, newEmailCode, bindNewEmail, clearError])
|
|||
|
|
|
|||
|
|
const displayError = localError || error
|
|||
|
|
|
|||
|
|
// Render Step 1 content (without buttons)
|
|||
|
|
const renderStep1Content = () => (
|
|||
|
|
<View style={themed($content)}>
|
|||
|
|
<Text tx="changeEmailScreen:step1Title" preset="subheading" style={themed($stepTitle)} />
|
|||
|
|
<Text tx="changeEmailScreen:step1Description" style={themed($description)} />
|
|||
|
|
|
|||
|
|
<View style={themed($currentEmailContainer)}>
|
|||
|
|
<Text size="xs" style={themed($currentEmailLabel)}>
|
|||
|
|
{translate("changeEmailScreen:currentEmail")}
|
|||
|
|
</Text>
|
|||
|
|
<Text preset="bold" style={themed($currentEmailText)}>
|
|||
|
|
{user?.email || ""}
|
|||
|
|
</Text>
|
|||
|
|
</View>
|
|||
|
|
|
|||
|
|
{emailChangeStep !== "idle" && (
|
|||
|
|
<>
|
|||
|
|
<TextField
|
|||
|
|
labelTx="changeEmailScreen:verificationCode"
|
|||
|
|
value={currentEmailCode}
|
|||
|
|
onChangeText={setCurrentEmailCode}
|
|||
|
|
containerStyle={themed($inputContainer)}
|
|||
|
|
keyboardType="number-pad"
|
|||
|
|
maxLength={6}
|
|||
|
|
placeholder="000000"
|
|||
|
|
/>
|
|||
|
|
|
|||
|
|
{displayError ? (
|
|||
|
|
<Text size="sm" style={themed($errorText)}>
|
|||
|
|
{displayError}
|
|||
|
|
</Text>
|
|||
|
|
) : null}
|
|||
|
|
</>
|
|||
|
|
)}
|
|||
|
|
</View>
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// Render Step 2 content (without buttons)
|
|||
|
|
const renderStep2Content = () => (
|
|||
|
|
<View style={themed($content)}>
|
|||
|
|
<Text tx="changeEmailScreen:step2Title" preset="subheading" style={themed($stepTitle)} />
|
|||
|
|
<Text tx="changeEmailScreen:step2Description" style={themed($description)} />
|
|||
|
|
|
|||
|
|
<TextField
|
|||
|
|
labelTx="changeEmailScreen:newEmail"
|
|||
|
|
value={newEmail}
|
|||
|
|
onChangeText={setNewEmail}
|
|||
|
|
containerStyle={themed($inputContainer)}
|
|||
|
|
keyboardType="email-address"
|
|||
|
|
autoCapitalize="none"
|
|||
|
|
autoCorrect={false}
|
|||
|
|
editable={!newEmailCodeSent}
|
|||
|
|
/>
|
|||
|
|
|
|||
|
|
{!newEmailCodeSent ? (
|
|||
|
|
displayError ? (
|
|||
|
|
<Text size="sm" style={themed($errorText)}>
|
|||
|
|
{displayError}
|
|||
|
|
</Text>
|
|||
|
|
) : null
|
|||
|
|
) : (
|
|||
|
|
<>
|
|||
|
|
<TextField
|
|||
|
|
labelTx="changeEmailScreen:verificationCode"
|
|||
|
|
value={newEmailCode}
|
|||
|
|
onChangeText={setNewEmailCode}
|
|||
|
|
containerStyle={themed($inputContainer)}
|
|||
|
|
keyboardType="number-pad"
|
|||
|
|
maxLength={6}
|
|||
|
|
placeholder="000000"
|
|||
|
|
/>
|
|||
|
|
|
|||
|
|
{displayError ? (
|
|||
|
|
<Text size="sm" style={themed($errorText)}>
|
|||
|
|
{displayError}
|
|||
|
|
</Text>
|
|||
|
|
) : null}
|
|||
|
|
</>
|
|||
|
|
)}
|
|||
|
|
</View>
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// Render bottom buttons for Step 1
|
|||
|
|
const renderStep1Buttons = () => {
|
|||
|
|
if (emailChangeStep === "idle") {
|
|||
|
|
return (
|
|||
|
|
<Button
|
|||
|
|
tx="changeEmailScreen:sendCode"
|
|||
|
|
preset="reversed"
|
|||
|
|
onPress={handleSendCurrentEmailCode}
|
|||
|
|
disabled={isLoading}
|
|||
|
|
loading={isLoading}
|
|||
|
|
/>
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
return (
|
|||
|
|
<>
|
|||
|
|
<Button
|
|||
|
|
tx="changeEmailScreen:verify"
|
|||
|
|
preset="reversed"
|
|||
|
|
onPress={handleVerifyCurrentEmail}
|
|||
|
|
disabled={isLoading}
|
|||
|
|
loading={isLoading}
|
|||
|
|
/>
|
|||
|
|
<Button
|
|||
|
|
text={
|
|||
|
|
currentEmailCountdown > 0
|
|||
|
|
? `${translate("changeEmailScreen:resendCode")} (${currentEmailCountdown}s)`
|
|||
|
|
: undefined
|
|||
|
|
}
|
|||
|
|
tx={currentEmailCountdown > 0 ? undefined : "changeEmailScreen:resendCode"}
|
|||
|
|
preset="default"
|
|||
|
|
style={themed($resendButton)}
|
|||
|
|
onPress={handleSendCurrentEmailCode}
|
|||
|
|
disabled={isLoading || currentEmailCountdown > 0}
|
|||
|
|
/>
|
|||
|
|
</>
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Render bottom buttons for Step 2
|
|||
|
|
const renderStep2Buttons = () => {
|
|||
|
|
if (!newEmailCodeSent) {
|
|||
|
|
return (
|
|||
|
|
<Button
|
|||
|
|
tx="changeEmailScreen:sendCodeToNewEmail"
|
|||
|
|
preset="reversed"
|
|||
|
|
onPress={handleSendNewEmailCode}
|
|||
|
|
disabled={isLoading || !newEmail}
|
|||
|
|
loading={isLoading}
|
|||
|
|
/>
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
return (
|
|||
|
|
<>
|
|||
|
|
<Button
|
|||
|
|
tx="changeEmailScreen:confirmNewEmail"
|
|||
|
|
preset="reversed"
|
|||
|
|
onPress={handleBindNewEmail}
|
|||
|
|
disabled={isLoading}
|
|||
|
|
loading={isLoading}
|
|||
|
|
/>
|
|||
|
|
<Button
|
|||
|
|
text={
|
|||
|
|
newEmailCountdown > 0
|
|||
|
|
? `${translate("changeEmailScreen:resendCode")} (${newEmailCountdown}s)`
|
|||
|
|
: undefined
|
|||
|
|
}
|
|||
|
|
tx={newEmailCountdown > 0 ? undefined : "changeEmailScreen:resendCode"}
|
|||
|
|
preset="default"
|
|||
|
|
style={themed($resendButton)}
|
|||
|
|
onPress={handleSendNewEmailCode}
|
|||
|
|
disabled={isLoading || newEmailCountdown > 0}
|
|||
|
|
/>
|
|||
|
|
</>
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
useHeader(
|
|||
|
|
{
|
|||
|
|
title: translate("changeEmailScreen:title"),
|
|||
|
|
leftIcon: "back",
|
|||
|
|
onLeftPress: () => navigation.goBack(),
|
|||
|
|
},
|
|||
|
|
[],
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<Screen preset="fixed" safeAreaEdges={["bottom"]} contentContainerStyle={$styles.flex1}>
|
|||
|
|
<KeyboardAwareScrollView
|
|||
|
|
contentContainerStyle={[$styles.container, themed($container)]}
|
|||
|
|
keyboardShouldPersistTaps="handled"
|
|||
|
|
showsVerticalScrollIndicator={false}
|
|||
|
|
>
|
|||
|
|
{/* Step indicator */}
|
|||
|
|
<View style={themed($stepIndicator)}>
|
|||
|
|
{/* Step 1 */}
|
|||
|
|
<View style={themed($stepItem)}>
|
|||
|
|
<View
|
|||
|
|
style={[themed($stepDot), emailChangeStep !== "bind-new" && themed($stepDotActive)]}
|
|||
|
|
>
|
|||
|
|
<Text
|
|||
|
|
style={[
|
|||
|
|
themed($stepNumber),
|
|||
|
|
emailChangeStep !== "bind-new" && themed($stepNumberActive),
|
|||
|
|
]}
|
|||
|
|
>
|
|||
|
|
1
|
|||
|
|
</Text>
|
|||
|
|
</View>
|
|||
|
|
<Text
|
|||
|
|
size="xxs"
|
|||
|
|
style={[
|
|||
|
|
themed($stepLabel),
|
|||
|
|
emailChangeStep !== "bind-new" && themed($stepLabelActive),
|
|||
|
|
]}
|
|||
|
|
>
|
|||
|
|
{translate("changeEmailScreen:step1Label")}
|
|||
|
|
</Text>
|
|||
|
|
</View>
|
|||
|
|
|
|||
|
|
{/* Line */}
|
|||
|
|
<View style={themed($stepLine)} />
|
|||
|
|
|
|||
|
|
{/* Step 2 */}
|
|||
|
|
<View style={themed($stepItem)}>
|
|||
|
|
<View
|
|||
|
|
style={[themed($stepDot), emailChangeStep === "bind-new" && themed($stepDotActive)]}
|
|||
|
|
>
|
|||
|
|
<Text
|
|||
|
|
style={[
|
|||
|
|
themed($stepNumber),
|
|||
|
|
emailChangeStep === "bind-new" && themed($stepNumberActive),
|
|||
|
|
]}
|
|||
|
|
>
|
|||
|
|
2
|
|||
|
|
</Text>
|
|||
|
|
</View>
|
|||
|
|
<Text
|
|||
|
|
size="xxs"
|
|||
|
|
style={[
|
|||
|
|
themed($stepLabel),
|
|||
|
|
emailChangeStep === "bind-new" && themed($stepLabelActive),
|
|||
|
|
]}
|
|||
|
|
>
|
|||
|
|
{translate("changeEmailScreen:step2Label")}
|
|||
|
|
</Text>
|
|||
|
|
</View>
|
|||
|
|
</View>
|
|||
|
|
|
|||
|
|
{emailChangeStep === "bind-new" ? renderStep2Content() : renderStep1Content()}
|
|||
|
|
</KeyboardAwareScrollView>
|
|||
|
|
|
|||
|
|
{/* Fixed Bottom Buttons */}
|
|||
|
|
<View style={themed($bottomContainer)}>
|
|||
|
|
{emailChangeStep === "bind-new" ? renderStep2Buttons() : renderStep1Buttons()}
|
|||
|
|
</View>
|
|||
|
|
|
|||
|
|
{/* Success Dialog */}
|
|||
|
|
<Dialog
|
|||
|
|
visible={successDialogVisible}
|
|||
|
|
onClose={() => {
|
|||
|
|
setSuccessDialogVisible(false)
|
|||
|
|
navigation.goBack()
|
|||
|
|
}}
|
|||
|
|
titleTx="changeEmailScreen:success"
|
|||
|
|
messageTx="changeEmailScreen:successMessage"
|
|||
|
|
onConfirm={() => {
|
|||
|
|
setSuccessDialogVisible(false)
|
|||
|
|
navigation.goBack()
|
|||
|
|
}}
|
|||
|
|
/>
|
|||
|
|
</Screen>
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const $container: ThemedStyle<ViewStyle> = ({ spacing }) => ({
|
|||
|
|
paddingTop: spacing.lg, // 覆盖 $styles.container 的 56px,使用 useHeader 时只需 24px
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
const $content: ThemedStyle<ViewStyle> = () => ({})
|
|||
|
|
|
|||
|
|
const $stepIndicator: ThemedStyle<ViewStyle> = ({ spacing }) => ({
|
|||
|
|
flexDirection: "row",
|
|||
|
|
alignItems: "flex-start",
|
|||
|
|
justifyContent: "center",
|
|||
|
|
marginBottom: spacing.lg,
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
const $stepItem: ThemedStyle<ViewStyle> = () => ({
|
|||
|
|
alignItems: "center",
|
|||
|
|
width: s(80),
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
const $stepDot: ThemedStyle<ViewStyle> = ({ colors }) => ({
|
|||
|
|
width: s(32),
|
|||
|
|
height: s(32),
|
|||
|
|
borderRadius: s(16),
|
|||
|
|
backgroundColor: colors.palette.neutral300,
|
|||
|
|
justifyContent: "center",
|
|||
|
|
alignItems: "center",
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
const $stepDotActive: ThemedStyle<ViewStyle> = ({ colors }) => ({
|
|||
|
|
backgroundColor: colors.tint,
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
const $stepNumber: ThemedStyle<TextStyle> = ({ colors }) => ({
|
|||
|
|
color: colors.textDim,
|
|||
|
|
fontWeight: "bold",
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
const $stepNumberActive: ThemedStyle<TextStyle> = ({ colors }) => ({
|
|||
|
|
color: colors.palette.neutral100,
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
const $stepLabel: ThemedStyle<TextStyle> = ({ colors, spacing }) => ({
|
|||
|
|
color: colors.textDim,
|
|||
|
|
marginTop: spacing.xs,
|
|||
|
|
textAlign: "center",
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
const $stepLabelActive: ThemedStyle<TextStyle> = ({ colors }) => ({
|
|||
|
|
color: colors.tint,
|
|||
|
|
fontWeight: "600",
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
const $stepLine: ThemedStyle<ViewStyle> = ({ colors }) => ({
|
|||
|
|
width: s(40),
|
|||
|
|
height: 2,
|
|||
|
|
backgroundColor: colors.palette.neutral300,
|
|||
|
|
marginTop: s(15),
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
const $stepTitle: ThemedStyle<TextStyle> = ({ spacing }) => ({
|
|||
|
|
marginBottom: spacing.xs,
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
const $description: ThemedStyle<TextStyle> = ({ colors, spacing }) => ({
|
|||
|
|
color: colors.textDim,
|
|||
|
|
marginBottom: spacing.lg,
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
const $currentEmailContainer: ThemedStyle<ViewStyle> = ({ colors, spacing }) => ({
|
|||
|
|
backgroundColor: colors.palette.neutral200,
|
|||
|
|
borderRadius: s(8),
|
|||
|
|
padding: spacing.md,
|
|||
|
|
marginBottom: spacing.lg,
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
const $currentEmailLabel: ThemedStyle<TextStyle> = ({ colors, spacing }) => ({
|
|||
|
|
color: colors.textDim,
|
|||
|
|
marginBottom: spacing.xxs,
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
const $currentEmailText: ThemedStyle<TextStyle> = () => ({})
|
|||
|
|
|
|||
|
|
const $inputContainer: ThemedStyle<ViewStyle> = ({ spacing }) => ({
|
|||
|
|
marginBottom: spacing.md,
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
const $errorText: ThemedStyle<TextStyle> = ({ colors, spacing }) => ({
|
|||
|
|
color: colors.error,
|
|||
|
|
marginBottom: spacing.md,
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
const $bottomContainer: ThemedStyle<ViewStyle> = ({ spacing }) => ({
|
|||
|
|
paddingHorizontal: spacing.lg,
|
|||
|
|
paddingBottom: spacing.md,
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
const $resendButton: ThemedStyle<ViewStyle> = ({ spacing }) => ({
|
|||
|
|
marginTop: spacing.sm,
|
|||
|
|
})
|