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
13 changes: 3 additions & 10 deletions apps/api/src/endpoints/trpc/auth/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,12 @@ export const login = publicProcedure
.mutation(async ({ input }) => {
const user = await User.findOne({ email: input.email.toLowerCase().trim() })

if (!user) {
throw new TRPCError({
code: 'UNAUTHORIZED',
message: 'Invalid email or password',
})
}

const isValidPassword = await bcrypt.compare(input.password, user.password)
const isValidPassword = user && await bcrypt.compare(input.password, user.password)

if (!isValidPassword) {
if (!user || !isValidPassword) {
throw new TRPCError({
code: 'UNAUTHORIZED',
message: 'Invalid email or password',
message: 'Incorrect email or password',
})
}

Expand Down
2 changes: 1 addition & 1 deletion apps/api/src/endpoints/trpc/auth/updatePassword.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const updatePassword = authProcedure

if (!isValidPassword) {
throw new TRPCError({
code: 'UNAUTHORIZED',
code: 'BAD_REQUEST',
message: 'Current password is incorrect',
})
}
Expand Down
2 changes: 1 addition & 1 deletion apps/api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ app.use(
router: trpcRouter,
createContext: ({ req }) => ({ req }),
onError: ({ error, path }) => {
console.error(`❌ [TRPC Error on ${path}]`, error)
console.error(`❌ [TRPC Error on ${path}]`, error, error.cause)
},
})
)
Expand Down
2 changes: 0 additions & 2 deletions apps/api/src/models/User.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,6 @@ export const userSchema = new mongoose.Schema<IUser>({
phone: {
type: String,
required: false,
unique: true,
sparse: true,
lowercase: true,
trim: true,
match: [/^[0-9]{10}$/, 'Please fill a valid phone number'], // Phone validation
Expand Down
27 changes: 26 additions & 1 deletion apps/api/src/trpc/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { initTRPC } from '@trpc/server'
import superjson from 'superjson'
import type { IncomingMessage } from 'http'
import { MongoServerError } from 'mongodb'
import type { Document } from 'mongoose'
import { ZodError } from 'zod'

import type { IUser } from '../models/User'
import type { Document } from 'mongoose'

export interface Context {
req?: IncomingMessage,
Expand All @@ -18,6 +20,29 @@ export interface Context {
export const tRPCContext = initTRPC
.context<Context>()
.create({
errorFormatter: ({ error, shape }) => {
let message = shape.message
let fieldErrors = null

if (error.cause instanceof ZodError && error.code === 'BAD_REQUEST') {
message = error.cause.issues.map(issue => issue.message).join(', ')
fieldErrors = error.cause.issues
}

if (error.cause instanceof MongoServerError) {
// Squelch database errors from being logged to the client
message = 'An internal error has occurred'
}

return {
...shape,
message,
data: {
...shape.data,
fieldErrors,
},
}
},
transformer: superjson,
})

Expand Down
3 changes: 2 additions & 1 deletion apps/mobile/src/app/(tabs)/fertilizers.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react'
import { Modal, ScrollView, StyleSheet, Switch, Text, TextInput, TouchableOpacity, View } from 'react-native'
import { Modal, ScrollView, StyleSheet, Switch, Text, TouchableOpacity, View } from 'react-native'
import { useLayoutEffect } from 'react'
import { useLocalSearchParams, useNavigation } from 'expo-router'
import { keepPreviousData } from '@tanstack/react-query'
Expand All @@ -12,6 +12,7 @@ import { LoadingSkeleton, LoadingSkeletonLine } from '../../components/LoadingSk
import { ScreenTitle } from '../../components/ScreenTitle'
import { ScreenWrapper } from '../../components/ScreenWrapper'
import { SwipeToDelete } from '../../components/SwipeToDelete'
import { TextInput } from '../../components/TextInput'

import { useAlert } from '../../contexts/AlertContext'

Expand Down
3 changes: 2 additions & 1 deletion apps/mobile/src/app/(tabs)/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useEffect, useLayoutEffect, useRef, useState } from 'react'
import { Text, View, ScrollView, TouchableOpacity, TextInput, Modal, Switch, StyleSheet } from 'react-native'
import { Text, View, ScrollView, TouchableOpacity, Modal, Switch, StyleSheet } from 'react-native'
import { useNavigation, useRouter } from 'expo-router'
import { keepPreviousData } from '@tanstack/react-query'

Expand All @@ -13,6 +13,7 @@ import { ScreenTitle } from '../../components/ScreenTitle'
import { ScreenWrapper } from '../../components/ScreenWrapper'
import { SnoozeChoreModal } from '../../components/SnoozeChoreModal'
import { SwipeToDelete } from '../../components/SwipeToDelete'
import { TextInput } from '../../components/TextInput'

import { useAlert } from '../../contexts/AlertContext'

Expand Down
28 changes: 15 additions & 13 deletions apps/mobile/src/app/(tabs)/settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -141,20 +141,22 @@ export function SettingsScreen() {
style: 'destructive',
onPress: () => {
// Second confirmation prompt
alert(
'Just double checking...',
'You want to PERMANENTLY delete your account?\n\nWe\'re sorry to see you go but we wish you the best!',
[
{ text: 'Cancel', style: 'cancel' },
{
text: 'Yes, Delete',
style: 'destructive',
onPress: () => {
deleteMeMutation.mutate()
setTimeout(() => {
alert(
'Just double checking...',
'You want to PERMANENTLY delete your account?\n\nWe\'re sorry to see you go but we wish you the best!',
[
{ text: 'Cancel', style: 'cancel' },
{
text: 'Yes, Delete',
style: 'destructive',
onPress: () => {
deleteMeMutation.mutate()
},
},
},
]
)
]
)
}, 1000)
},
},
]
Expand Down
41 changes: 18 additions & 23 deletions apps/mobile/src/app/create-account.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import React, { useEffect, useState } from 'react'
import { ActivityIndicator, StyleSheet, Text, TextInput, TouchableOpacity, View } from 'react-native'
import { ActivityIndicator, StyleSheet, Text, TouchableOpacity, View } from 'react-native'
import * as Localization from 'expo-localization'
import { useRouter } from 'expo-router'

import { ScreenWrapper } from '../components/ScreenWrapper'
import { TextInput } from '../components/TextInput'

import { useAlert } from '../contexts/AlertContext'
import { useAuth } from '../contexts/AuthContext'
Expand Down Expand Up @@ -34,28 +35,31 @@ export default function CreateAccountScreen() {
router.replace('/(tabs)')
},
onError: (error) => {
if (
error.shape?.data?.fieldErrors?.find(error => error.path.includes('name'))
|| error.shape?.data?.fieldErrors?.find(error => error.path.includes('email'))
|| error.shape?.data?.fieldErrors?.find(error => error.path.includes('password'))
|| error.shape?.data?.fieldErrors?.find(error => error.path.includes('confirmPassword'))
) {
// Error is handled by input below, do not show alert
return
}

// Unable to handle error on input, show alert
alert('Account Creation Failed', error.message || 'Failed to create account')
},
})

const handleCreateAccount = () => {
if (!name.trim() || !email.trim() || !password.trim()) {
alert('Error', 'Please fill in all fields')
return
}

if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
alert('Error', 'Please enter a valid email address')
return
}

if (password.length < 6) {
alert('Error', 'Password must be at least 6 characters')
return
}

if (password !== confirmPassword) {
alert('Error', 'Passwords do not match')

return
}

Expand All @@ -74,39 +78,39 @@ export default function CreateAccountScreen() {
<Text style={localStyles.subtitle}>Sign up for Plannting</Text>

<TextInput
style={localStyles.input}
placeholder="Name"
value={name}
onChangeText={setName}
autoCapitalize="words"
fieldError={createAccountMutation.error?.shape?.data?.fieldErrors?.find(error => error.path.includes('name'))}
/>

<TextInput
style={localStyles.input}
placeholder="Email"
value={email}
onChangeText={setEmail}
keyboardType="email-address"
autoCapitalize="none"
autoComplete="email"
fieldError={createAccountMutation.error?.shape?.data?.fieldErrors?.find(error => error.path.includes('email'))}
/>

<TextInput
style={localStyles.input}
placeholder="Password (min 6 characters)"
value={password}
onChangeText={setPassword}
secureTextEntry
autoCapitalize="none"
fieldError={createAccountMutation.error?.shape?.data?.fieldErrors?.find(error => error.path.includes('password'))}
/>

<TextInput
style={localStyles.input}
placeholder="Confirm Password"
value={confirmPassword}
onChangeText={setConfirmPassword}
secureTextEntry
autoCapitalize="none"
fieldError={createAccountMutation.error?.shape?.data?.fieldErrors?.find(error => error.path.includes('confirmPassword'))}
/>

<TouchableOpacity
Expand Down Expand Up @@ -155,15 +159,6 @@ const localStyles = StyleSheet.create({
textAlign: 'center',
marginBottom: 32,
},
input: {
backgroundColor: '#fff',
borderWidth: 1,
borderColor: '#ddd',
borderRadius: 8,
padding: 12,
fontSize: 16,
marginBottom: 16,
},
button: {
borderRadius: 8,
padding: 16,
Expand Down
41 changes: 17 additions & 24 deletions apps/mobile/src/app/edit-password.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import React, { useState } from 'react'
import { ActivityIndicator, ScrollView, StyleSheet, Text, TextInput, TouchableOpacity, View } from 'react-native'
import { ActivityIndicator, ScrollView, StyleSheet, Text, TouchableOpacity, View } from 'react-native'
import { useRouter } from 'expo-router'
import { useQueryClient } from '@tanstack/react-query'

import { ScreenWrapper } from '../components/ScreenWrapper'
import { TextInput } from '../components/TextInput'

import { useAlert } from '../contexts/AlertContext'

Expand All @@ -28,28 +29,36 @@ export default function EditPasswordScreen() {
router.back()
},
onError: (error) => {
if (
error.shape?.data?.fieldErrors?.find(error => error.path.includes('currentPassword'))
|| error.shape?.data?.fieldErrors?.find(error => error.path.includes('newPassword'))
|| error.shape?.data?.fieldErrors?.find(error => error.path.includes('confirmPassword'))
) {
// Error is handled by input below, do not show alert
return
}

// Unable to handle error on input, show alert
alert('Error', error.message || 'Failed to update password')
},
})

const handleSubmit = () => {
if (!currentPassword.trim()) {
alert('Error', 'Please enter your current password')

return
}

if (!newPassword.trim()) {
alert('Error', 'Please enter a new password')
return
}

if (newPassword.length < 6) {
alert('Error', 'Password must be at least 6 characters')
return
}

if (newPassword !== confirmPassword) {
alert('Error', 'New password and confirm password do not match')

return
}

Expand All @@ -68,40 +77,34 @@ export default function EditPasswordScreen() {

<Text style={localStyles.label}>Current Password</Text>
<TextInput
style={localStyles.input}
placeholder='Current Password'
value={currentPassword}
onChangeText={setCurrentPassword}
secureTextEntry
autoCapitalize='none'
fieldError={updatePasswordMutation.error?.shape?.data?.fieldErrors?.find(error => error.path.includes('currentPassword'))}
/>

<Text style={localStyles.label}>New Password</Text>
<TextInput
style={localStyles.input}
placeholder='New Password (min 6 characters)'
value={newPassword}
onChangeText={setNewPassword}
secureTextEntry
autoCapitalize='none'
fieldError={updatePasswordMutation.error?.shape?.data?.fieldErrors?.find(error => error.path.includes('newPassword'))}
/>

<Text style={localStyles.label}>Confirm New Password</Text>
<TextInput
style={localStyles.input}
placeholder='Confirm New Password'
value={confirmPassword}
onChangeText={setConfirmPassword}
secureTextEntry
autoCapitalize='none'
fieldError={updatePasswordMutation.error?.shape?.data?.fieldErrors?.find(error => error.path.includes('confirmPassword'))}
/>

{updatePasswordMutation.error && (
<Text style={localStyles.errorText}>
{updatePasswordMutation.error.message}
</Text>
)}

<TouchableOpacity
style={[localStyles.button, localStyles.primaryButton]}
onPress={handleSubmit}
Expand Down Expand Up @@ -155,16 +158,6 @@ const localStyles = StyleSheet.create({
fontWeight: '600',
color: '#333',
marginBottom: 8,
marginTop: 8,
},
input: {
backgroundColor: '#fff',
borderWidth: 1,
borderColor: '#ddd',
borderRadius: 8,
padding: 12,
fontSize: 16,
marginBottom: 16,
},
button: {
borderRadius: 8,
Expand Down
Loading