Skip to content

Commit

Permalink
Frontend (mobile) implemented (#24)
Browse files Browse the repository at this point in the history
* #feat: added image_url to cart and order endpoints and updated seeders

* Initial frontend commit

* feat: added constants

* feat: implemented GlobalProvider

* feat: added auth view

* feat: implemented sign-in and sign-up

* feat: added tabs route

* feat: implemented cart

* feat: implemented products and products-details

* feat: implemented profile and order details

* feat: implemented custom button components

* feat: implemented custom form field

* feat: implemented order components
  • Loading branch information
KAMMAT03 authored Jan 9, 2025
1 parent 27b14c8 commit 8d64440
Show file tree
Hide file tree
Showing 48 changed files with 13,103 additions and 0 deletions.
36 changes: 36 additions & 0 deletions frontend/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files

# dependencies
node_modules/

# Expo
.expo/
dist/
web-build/
expo-env.d.ts

# Native
*.orig.*
*.jks
*.p8
*.p12
*.key
*.mobileprovision

# Metro
.metro-health-check*

# debug
npm-debug.*
yarn-debug.*
yarn-error.*

# macOS
.DS_Store
*.pem

# local env files
.env*.local

# typescript
*.tsbuildinfo
32 changes: 32 additions & 0 deletions frontend/app.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"expo": {
"name": "pharmacy_app",
"slug": "pharmacy_app",
"scheme": "pharmacy_app",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/icon.png",
"userInterfaceStyle": "light",
"newArchEnabled": true,
"splash": {
"image": "./assets/splash-icon.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
},
"ios": {
"supportsTablet": true
},
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#ffffff"
}
},
"web": {
"favicon": "./assets/favicon.png"
},
"plugins": [
"expo-router"
]
}
}
24 changes: 24 additions & 0 deletions frontend/app/(auth)/_layout.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { View, Text } from 'react-native'
import React from 'react'
import { Stack } from 'expo-router'

const AuthLayout = () => {
return (
<Stack>
<Stack.Screen
name='sign-in'
options={{
headerShown: false
}}
/>
<Stack.Screen
name='sign-up'
options={{
headerShown: false
}}
/>
</Stack>
)
}

export default AuthLayout
126 changes: 126 additions & 0 deletions frontend/app/(auth)/sign-in.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import { View, Text, ScrollView, ActivityIndicator, Alert, KeyboardAvoidingView, Platform } from 'react-native'
import React, { useState, useEffect } from 'react'

import FormField from '../../components/FormField'
import CustomButton from '../../components/CustomButton'
import { useGlobalContext } from '../../context/GlobalProvider'
import { Link, router, useLocalSearchParams } from 'expo-router'
import { API_URL } from '../_layout'

const SignIn = () => {
const [form, setForm] = useState({
username: '',
password: ''
})

const [isSubmitting, setisSubmitting] = useState(false)

const [message, setMessage] = useState('')

const [isLoading, setIsLoading] = useState(false)

const { isLoggedIn, setIsLoggedIn, setState } = useGlobalContext()

const { username } = useLocalSearchParams()

useEffect(() => {

if (isLoggedIn) {
router.replace('/home')
}

if (username) {
setForm({
...form,
username
})
}
}, [])

const submit = () => {

setIsLoading(true)

fetch(`${API_URL}/auth/login`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(form)
}).then(res => res.json())
.then(data => {
setIsLoading(false)

if (data.token) {
setIsLoggedIn(true)
setState({
token: data.token,
})
setMessage('')
router.replace('/home')
} else {
setMessage(`${data.message}! ${data.details}`)
setForm({
...form,
password: ''
})
}
})
.catch(err => {
console.log(err)
setIsLoading(false)
Alert.alert('Internal Server Error. Try again later')
setForm({
username: '',
password: ''
})
})
}

return (
<KeyboardAvoidingView
className='bg-primary h-full bg-slate-100'
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
>
<ScrollView>
<View className='w-full justify-center min-h-[85vh] px-8 my-6'>
<Text className='text-2xl text-red mt-10 font-psemibold'>Log in to MedShop</Text>
<Text className='text-lg text-red text-center font-bold -mb-5 mt-2'>{message}</Text>
<FormField
title='Username'
value={form.username}
handleChangeText={e => setForm({...form, username: e})}
otherStyles='mt-7'
/>
<FormField
title='Password'
value={form.password}
handleChangeText={e => setForm({...form, password: e})}
otherStyles='mt-7'
/>
<CustomButton
title='Sign In'
handlePress={submit}
containerStyles='mt-7'
isLoading={isSubmitting}
/>
<View className='justify-center pt-5 flex-row gap-2'>
<Text className='text-lg text-red font-pregular'>
Don't have an account?
</Text>
<Link href='/sign-up' className='text-lg font-bold text-secondary'>
Sign Up
</Link>
</View>
</View>
</ScrollView>
{isLoading && (
<View className="absolute inset-0 bg-slate-700/50 flex justify-center items-center">
<ActivityIndicator size="large" color="#ffffff" />
</View>
)}
</KeyboardAvoidingView>
)
}

export default SignIn
122 changes: 122 additions & 0 deletions frontend/app/(auth)/sign-up.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import { View, Text, ScrollView, ActivityIndicator, KeyboardAvoidingView, Platform } from 'react-native'
import React, { useState, useEffect } from 'react'
import { SafeAreaView } from 'react-native-safe-area-context'

import FormField from '../../components/FormField'
import CustomButton from '../../components/CustomButton'
import { useGlobalContext } from '../../context/GlobalProvider'
import { Link, router } from 'expo-router'
import { API_URL } from '../_layout'

const SignUp = () => {
const [form, setForm] = useState({
username: '',
email: '',
password: ''
})

const [isSubmitting, setisSubmitting] = useState(false)

const [message, setMessage] = useState('')

const [isLoading, setIsLoading] = useState(false)

const { isLoggedIn } = useGlobalContext()

useEffect(() => {
if (isLoggedIn) {
router.replace('/home')
}
}, [])

const register = () => {

setIsLoading(true)

fetch(`${API_URL}/auth/register`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(form)
}).then(res => res.json())
.then(data => {
setIsLoading(false)

if (data.message == 'Registration successful') {
alert('Account created successfully! You can now sign in')
router.replace(`/sign-in?username=${form.username}`)
} else {
setMessage(`${data.message}! ${data.details}`)
setForm({
...form,
password: ''
})
}
})
.catch(err => {
console.log(err)
setIsLoading(false)
Alert.alert('Internal Server Error. Try again later')
setForm({
username: '',
email: '',
password: ''
})
})
}

return (
<KeyboardAvoidingView
className='bg-primary h-full bg-slate-100'
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
>
<ScrollView>
<View className='w-full justify-center min-h-[85vh] px-8 my-6'>
<Text className='text-2xl text-red mt-10 font-psemibold'>Register to MedShop</Text>
<Text className='text-lg text-red text-center font-bold -mb-5 mt-2'>{message}</Text>
<FormField
title='Username'
value={form.username}
handleChangeText={e => setForm({...form, username: e})}
otherStyles='mt-7'
/>
<FormField
title='Email'
value={form.email}
handleChangeText={e => setForm({...form, email: e})}
otherStyles='mt-7'
keyboardType='email-address'
/>
<FormField
title='Password'
value={form.password}
handleChangeText={e => setForm({...form, password: e})}
otherStyles='mt-7'
/>
<CustomButton
title='Sign Up'
handlePress={register}
containerStyles='mt-7'
isLoading={isSubmitting}
/>
<View className='justify-center pt-5 flex-row gap-2'>
<Text className='text-lg text-red font-pregular'>
Already have an account?
</Text>
<Link href='/sign-in' className='text-lg font-bold text-secondary'>
Sign In
</Link>
</View>
</View>
</ScrollView>
{isLoading && (
<View className="absolute inset-0 bg-slate-700/50 flex justify-center items-center">
<ActivityIndicator size="large" color="#ffffff" />
</View>
)}
</KeyboardAvoidingView>
)
}

export default SignUp
30 changes: 30 additions & 0 deletions frontend/app/(tabs)/(products)/_layout.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { View, Text } from 'react-native'
import React from 'react'
import { Stack } from 'expo-router'

const ProductsLayout = () => {
return (
<Stack>
<Stack.Screen
name='home'
options={{
headerShown: false
}}
/>
<Stack.Screen
name='product-details'
options={{
headerStyle: {
backgroundColor: '#f1f5f9',
},
headerBackTitle: 'All products',
headerTintColor: '#d72638',
headerTitle: '',
headerShadowVisible: false,
}}
/>
</Stack>
)
}

export default ProductsLayout
Loading

0 comments on commit 8d64440

Please sign in to comment.