Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 73 additions & 9 deletions mobile/app/(auth)/login.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,85 @@
import { Link } from "expo-router"
import { Text, View } from "react-native"
import { zodResolver } from "@hookform/resolvers/zod"

Check warning on line 1 in mobile/app/(auth)/login.tsx

View workflow job for this annotation

GitHub Actions / mobile-checks

Insert `;`
import { Link, useRouter } from "expo-router"

Check warning on line 2 in mobile/app/(auth)/login.tsx

View workflow job for this annotation

GitHub Actions / mobile-checks

Insert `;`
import { Controller, useForm } from "react-hook-form"

Check warning on line 3 in mobile/app/(auth)/login.tsx

View workflow job for this annotation

GitHub Actions / mobile-checks

Insert `;`
import { View } from "react-native"

Check warning on line 4 in mobile/app/(auth)/login.tsx

View workflow job for this annotation

GitHub Actions / mobile-checks

Insert `;`

Check warning on line 4 in mobile/app/(auth)/login.tsx

View workflow job for this annotation

GitHub Actions / mobile-checks

There should be at least one empty line between import groups
import { Button, Input, Typography } from "../../src/components"

Check warning on line 5 in mobile/app/(auth)/login.tsx

View workflow job for this annotation

GitHub Actions / mobile-checks

Insert `;`
import { useAuthStore } from "../../src/store/useAuthStore"

Check warning on line 6 in mobile/app/(auth)/login.tsx

View workflow job for this annotation

GitHub Actions / mobile-checks

Insert `;`
import { colors, spacing } from "../../src/theme/tokens"

Check warning on line 7 in mobile/app/(auth)/login.tsx

View workflow job for this annotation

GitHub Actions / mobile-checks

Insert `;`
import { type LoginFormValues, loginSchema } from "../../src/validation/auth"

Check warning on line 8 in mobile/app/(auth)/login.tsx

View workflow job for this annotation

GitHub Actions / mobile-checks

Insert `;`

export default function LoginScreen() {
const router = useRouter()

Check warning on line 11 in mobile/app/(auth)/login.tsx

View workflow job for this annotation

GitHub Actions / mobile-checks

Insert `;`
const setToken = useAuthStore((state) => state.setToken)
const {
control,
handleSubmit,
formState: { errors, isSubmitting },
} = useForm<LoginFormValues>({
resolver: zodResolver(loginSchema),
defaultValues: {
email: "",
password: "",
},
mode: "onBlur",
})

const onSubmit = async ({ email }: LoginFormValues) => {
const token = `session_${email.toLowerCase()}`
setToken(token)
router.replace("/(tabs)/discover")
}

return (
<View
style={{
flex: 1,
alignItems: "center",
justifyContent: "center",
backgroundColor: "#FFFFFF",
gap: 12,
padding: 24,
backgroundColor: colors.background,
gap: spacing.md,
padding: spacing.lg,
}}
>
<Text style={{ fontSize: 28, fontWeight: "700" }}>Welcome Back</Text>
<Text>Phase 1 auth UI scaffold.</Text>
<Link href="/(tabs)/discover">Mock Sign In</Link>
<Typography variant="h1">Welcome Back</Typography>
<Typography variant="body" color={colors.muted}>
Sign in to continue matching with dishes around you.
</Typography>
<Controller
control={control}
name="email"
render={({ field: { onChange, onBlur, value } }) => (
<Input
label="Email Address"
placeholder="hello@discoverly.app"
autoCapitalize="none"
keyboardType="email-address"
autoComplete="email"
textContentType="emailAddress"
value={value}
onChangeText={onChange}
onBlur={onBlur}
error={errors.email?.message}
/>
)}
/>
<Controller
control={control}
name="password"
render={({ field: { onChange, onBlur, value } }) => (
<Input
label="Password"
placeholder="Enter your password"
secureTextEntry
autoCapitalize="none"
autoComplete="password"
textContentType="password"
value={value}
onChangeText={onChange}
onBlur={onBlur}
error={errors.password?.message}
/>
)}
/>
<Button label="Sign In" loading={isSubmitting} onPress={handleSubmit(onSubmit)} />
<Link href="/(auth)/register">Go To Register</Link>
</View>
)
Expand Down
123 changes: 111 additions & 12 deletions mobile/app/(auth)/register.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,120 @@
import { Link } from "expo-router"
import { Text, View } from "react-native"
import { zodResolver } from "@hookform/resolvers/zod"
import { Link, useRouter } from "expo-router"
import { Controller, useForm } from "react-hook-form"
import { ScrollView, View } from "react-native"
import { Button, Input, Typography } from "../../src/components"
import { useAuthStore } from "../../src/store/useAuthStore"
import { colors, spacing } from "../../src/theme/tokens"
import { type RegisterFormValues, registerSchema } from "../../src/validation/auth"

export default function RegisterScreen() {
const router = useRouter()
const setToken = useAuthStore((state) => state.setToken)
const {
control,
handleSubmit,
formState: { errors, isSubmitting },
} = useForm<RegisterFormValues>({
resolver: zodResolver(registerSchema),
defaultValues: {
fullName: "",
email: "",
password: "",
confirmPassword: "",
},
mode: "onBlur",
})

const onSubmit = async ({ email }: RegisterFormValues) => {
const token = `session_${email.toLowerCase()}`
setToken(token)
router.replace("/(tabs)/discover")
}

return (
<View
style={{
flex: 1,
alignItems: "center",
<ScrollView
contentContainerStyle={{
flexGrow: 1,
justifyContent: "center",
backgroundColor: "#FFFFFF",
gap: 12,
padding: 24,
backgroundColor: colors.background,
gap: spacing.md,
padding: spacing.lg,
}}
keyboardShouldPersistTaps="handled"
>
<Text style={{ fontSize: 28, fontWeight: "700" }}>Create Account</Text>
<Text>Phase 1 registration UI scaffold.</Text>
<Typography variant="h1">Create Account</Typography>
<Typography variant="body" color={colors.muted}>
Create your account to start discovering meals instantly.
</Typography>
<Controller
control={control}
name="fullName"
render={({ field: { onChange, onBlur, value } }) => (
<Input
label="Full Name"
placeholder="Alex Carter"
autoCapitalize="words"
value={value}
onChangeText={onChange}
onBlur={onBlur}
error={errors.fullName?.message}
/>
)}
/>
<Controller
control={control}
name="email"
render={({ field: { onChange, onBlur, value } }) => (
<Input
label="Email Address"
placeholder="hello@discoverly.app"
autoCapitalize="none"
keyboardType="email-address"
autoComplete="email"
textContentType="emailAddress"
value={value}
onChangeText={onChange}
onBlur={onBlur}
error={errors.email?.message}
/>
)}
/>
<Controller
control={control}
name="password"
render={({ field: { onChange, onBlur, value } }) => (
<Input
label="Password"
placeholder="Minimum 8 characters"
secureTextEntry
autoCapitalize="none"
autoComplete="password-new"
textContentType="newPassword"
value={value}
onChangeText={onChange}
onBlur={onBlur}
error={errors.password?.message}
/>
)}
/>
<Controller
control={control}
name="confirmPassword"
render={({ field: { onChange, onBlur, value } }) => (
<Input
label="Confirm Password"
placeholder="Re-enter password"
secureTextEntry
autoCapitalize="none"
value={value}
onChangeText={onChange}
onBlur={onBlur}
error={errors.confirmPassword?.message}
/>
)}
/>
<Button label="Sign Up" loading={isSubmitting} onPress={handleSubmit(onSubmit)} />
<Link href="/(auth)/login">Back To Login</Link>
</View>
</ScrollView>
)
}
5 changes: 4 additions & 1 deletion mobile/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,18 @@
"export:check": "expo export --platform web --output-dir dist-export --clear"
},
"dependencies": {
"@hookform/resolvers": "^5.2.2",
"expo": "^51.0.0",
"expo-image": "^1.12.15",
"expo-haptics": "^13.0.1",
"expo-image": "^1.12.15",
"expo-router": "^3.5.18",
"expo-secure-store": "^13.0.2",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-hook-form": "^7.71.2",
"react-native": "0.74.5",
"react-native-web": "~0.19.10",
"zod": "^4.3.6",
"zustand": "^4.5.5"
},
"devDependencies": {
Expand Down
98 changes: 98 additions & 0 deletions mobile/src/components/Button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import type { ReactNode } from "react"
import { ActivityIndicator, Pressable, type ViewStyle } from "react-native"
import { colors, radius, spacing } from "../theme/tokens"
import { Typography } from "./Typography"

type ButtonVariant = "primary" | "secondary" | "outlined"

type ButtonProps = {
label: string
onPress?: () => void
variant?: ButtonVariant
loading?: boolean
disabled?: boolean
leftIcon?: ReactNode
style?: ViewStyle
}

function getContainerStyle(variant: ButtonVariant, disabled: boolean): ViewStyle {
if (variant === "outlined") {
return {
backgroundColor: colors.surface,
borderColor: disabled ? colors.disabled : colors.crypto,
borderWidth: 1.5,
}
}

if (variant === "secondary") {
return {
backgroundColor: disabled ? colors.disabled : colors.crypto,
}
}

return {
backgroundColor: disabled ? colors.disabled : colors.primary,
}
}

function getLabelColor(variant: ButtonVariant, disabled: boolean): string {
if (disabled) {
return variant === "outlined" ? colors.muted : colors.surface
}

if (variant === "outlined") {
return colors.crypto
}

if (variant === "secondary") {
return colors.onCrypto
}

return colors.onPrimary
}

export function Button({
label,
onPress,
variant = "primary",
loading = false,
disabled = false,
leftIcon,
style,
}: ButtonProps) {
const isDisabled = disabled || loading
const labelColor = getLabelColor(variant, isDisabled)

return (
<Pressable
onPress={onPress}
disabled={isDisabled}
style={({ pressed }) => [
{
minHeight: 52,
borderRadius: radius.md,
paddingHorizontal: spacing.lg,
paddingVertical: spacing.md,
alignItems: "center",
justifyContent: "center",
flexDirection: "row",
gap: spacing.sm,
opacity: pressed ? 0.9 : 1,
},
getContainerStyle(variant, isDisabled),
style,
]}
>
{loading ? (
<ActivityIndicator color={labelColor} />
) : (
<>
{leftIcon}
<Typography variant="body" color={labelColor} style={{ fontWeight: "700" }}>
{label}
</Typography>
</>
)}
</Pressable>
)
}
28 changes: 28 additions & 0 deletions mobile/src/components/Card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type { ReactNode } from "react"
import { View, type ViewStyle } from "react-native"
import { colors, radius, shadows, spacing } from "../theme/tokens"

type CardProps = {
children: ReactNode
style?: ViewStyle
}

export function Card({ children, style }: CardProps) {
return (
<View
style={[
{
borderRadius: radius.md,
backgroundColor: colors.surface,
borderWidth: 1,
borderColor: colors.border,
padding: spacing.md,
...shadows.soft,
},
style,
]}
>
{children}
</View>
)
}
Loading