Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: initial setup for mobilit bap #287

Merged
merged 1 commit into from
Mar 27, 2024
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
4 changes: 4 additions & 0 deletions apps/mobility-bap/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/node_modules/
/coverage/
/.next/
/public/
29 changes: 29 additions & 0 deletions apps/mobility-bap/Jenkinsfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
pipeline {
agent any
stages {
stage('Executing Shell Script On Server') {
steps {
script {
sshagent(credentials: ['"${credentials}"']) {
sh '''
ssh -t -t ${userName}@${hostIP} -o StrictHostKeyChecking=no << EOF
${listOfCommands}
logout
EOF
'''
}
}
}
}
}
post {
always {
cleanWs(cleanWhenNotBuilt: false,
deleteDirs: true,
disableDeferredWipeout: true,
notFailBuild: true,
patterns: [[pattern: '.gitignore', type: 'INCLUDE'],
[pattern: '.propsfile', type: 'EXCLUDE']])
}
}
}
48 changes: 48 additions & 0 deletions apps/mobility-bap/components/BottomModal/BottomModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { useState } from 'react'
import { Transition } from '@headlessui/react'
import { AiOutlineClose } from 'react-icons/ai'
import { useLanguage } from '../../hooks/useLanguage'
import { Image } from '@chakra-ui/react'

interface ModalProps {
isOpen: boolean
onClose: () => void
children: React.ReactNode
partialClose?: boolean
}

const Modal: React.FC<ModalProps> = ({ isOpen, onClose, children, partialClose }) => {
const { t } = useLanguage()
return (
<Transition
show={isOpen}
onClick={() => onClose()}
>
<div className="fixed z-[9999] inset-0 flex items-end justify-center sm:p-0">
<Transition.Child
unmount={false}
enter="transition-transform duration-300"
enterFrom="translate-y-full"
enterTo="translate-y-0"
leave="transition-transform duration-300"
leaveFrom="translate-y-0"
leaveTo="translate-y-full"
style={{
width: '100vw'
}}
>
<div className="w-full px-4 pb-4 pt-2 mx-auto bg-[#F3F4F5] rounded-t-[1rem] shadow-lg sm:rounded-lg sm:overflow-hidden">
<Image
src="/images/Indicator.svg"
className="mx-auto mb-3"
alt="indicator"
/>
{children}
</div>
</Transition.Child>
</div>
</Transition>
)
}

export default Modal
3 changes: 3 additions & 0 deletions apps/mobility-bap/components/BottomModal/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import BottomModal from './BottomModal'

export default BottomModal
3 changes: 3 additions & 0 deletions apps/mobility-bap/components/Map/CenterMarker.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
148 changes: 148 additions & 0 deletions apps/mobility-bap/components/Map/Map.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import { MapContainer, TileLayer, Marker, Popup, useMap, ZoomControl } from 'react-leaflet'
import { Image } from '@chakra-ui/react'
import React, { useEffect, useState } from 'react'
import Control from 'react-leaflet-custom-control'
import 'leaflet/dist/leaflet.css'
import { GiHamburgerMenu } from 'react-icons/gi'
import { data } from './StoreMarkerData'
import Icon from './store-marker.svg'
import SelectedIcon from './SelectedMarker.svg'
import CenterMarker from './CenterMarker.svg'
import L from 'leaflet'
import { isEmpty } from 'lodash'

interface MapProps {
coords: { lat: number; long: number }
handleModalOpen: () => void
handleOptionDetailOpen: () => void
setSelectedStore: React.Dispatch<React.SetStateAction<any>>
selectedStore: any
// Using any for now since exact structure is not clear
stores: any[]
}

const Map: React.FC<MapProps> = ({
stores,
selectedStore,
coords,
handleModalOpen,
handleOptionDetailOpen,
setSelectedStore
}) => {
const { lat, long } = coords
const [userLocation, setUserLocation] = useState<[number, number] | null>(null)
const [flyToUserLocation, setFlyToUserLocation] = useState(false)

useEffect(() => {
// Get the user's current location using the browser's geolocation API
navigator.geolocation.getCurrentPosition(
position => {
setUserLocation([position.coords.latitude, position.coords.longitude])
},
error => {
console.error('Error getting user location:', error)
}
)
}, [])

// Custom hook to zoom the map to the user's location
const ZoomToUserLocation = () => {
let map = useMap()

if (userLocation && flyToUserLocation) {
map.flyTo(userLocation, 12)
}

return null
}

const customIcon = new L.Icon({
iconUrl: Icon,
iconSize: [25, 35],
iconAnchor: [5, 30]
})

const customCenterMarker = new L.Icon({
iconUrl: CenterMarker,
iconSize: [40, 50],
iconAnchor: [5, 30]
})

const customSelectedIcon = new L.Icon({
iconUrl: Icon,
iconSize: [35, 45],
iconAnchor: [5, 30]
})

function MapView() {
let map = useMap()
map.setView([lat, long], map.getZoom())
return null
}

return (
<MapContainer
style={{ maxHeight: '100vh', height: '90vh' }}
center={[lat, long]}
zoom={16}
zoomControl={false}
scrollWheelZoom={true}
zoomAnimation={true}
>
<TileLayer
attribution='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a>
contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
<Control
prepend
position="topright"
>
<div className="flex flex-col basis-4">
<Image
className="translate-x-0.5"
onClick={() => setFlyToUserLocation(true)}
src="/images/LocateMe.svg"
alt="..."
/>
</div>
</Control>

{!isEmpty(stores) &&
stores.map((item: any, index: number) => {
const isSelected = item.lat === selectedStore?.lat && item.lon === selectedStore?.lon
const IconToUse = isSelected ? customSelectedIcon : customIcon
return (
<Marker
icon={IconToUse}
key={item.lon}
position={[item.lat, item.lon]}
eventHandlers={{
click: () => {
setSelectedStore(item)
handleOptionDetailOpen()
}
}}
></Marker>
)
})}
<Marker
icon={customCenterMarker}
position={[lat, long]}
></Marker>
<ZoomControl position="topright" />

<MapView />
<ZoomToUserLocation />
</MapContainer>
)
}

// React memo not working for some reason
//TODO Needed because the map is re-rendered even if the co-ords are same causing a flickering issue on the map. Will fix this later
export default React.memo(Map, (prevProps, nextProps) => {
if (prevProps.coords.lat === nextProps.coords.lat && prevProps.coords.long === nextProps.coords.long) {
return true
}
return false
})
126 changes: 126 additions & 0 deletions apps/mobility-bap/components/Map/MapSearch.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import React, { useState, useEffect } from 'react'
import { Spinner } from '@chakra-ui/react'
import { GoSearch } from 'react-icons/go'
import { isEmpty } from 'lodash'
import { Image } from '@chakra-ui/react'
import { useLanguage } from '../../hooks/useLanguage'
import useDebounce from '../../hooks/useDebounce'

interface LocalNameFormat {
primaryName: string
secondaryName: string
}

function formatLocationName(name: string): LocalNameFormat {
const index = name.indexOf(',')
if (index !== -1) {
return {
primaryName: name.slice(0, index).trim(),
secondaryName: name.slice(index + 1).trim()
}
} else {
return {
primaryName: name.trim(),
secondaryName: ''
}
}
}

export interface SearchBarProp {
setQuery: React.Dispatch<React.SetStateAction<string>>
locations: any[]
query: string
handleLocationClick: (lat: number, long: number) => void
fetchResults: (query: string) => void
setShowSuggestions: React.Dispatch<React.SetStateAction<boolean>>
}

const SearchBar: React.FC<SearchBarProp> = ({
setQuery,
locations,
query,
handleLocationClick,
fetchResults,
setShowSuggestions
}) => {
const { t } = useLanguage()

//TODO Pseudo loading for now. Need to figure out to do this using map load events
const [loading, setLoading] = useState<boolean>(false)

const [value, setValue] = useState<string>('')

const debouncedValue = useDebounce(value, 300)

useEffect(() => {
setShowSuggestions(!isEmpty(value) && locations && !isEmpty(locations))
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [locations, value])

useEffect(() => {
setLoading(true)
setTimeout(() => {
setLoading(false)
}, 500)
}, [debouncedValue])

useEffect(() => {
// setQuery(debouncedValue);
fetchResults(debouncedValue)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [debouncedValue])

return (
<>
<div className="max-w-[50rem] w-[90%] md:w-[90%] px-4 mx-auto mt-4 mb-3 border border-[#C9C9C9] border-solid md:ltr:ml-4 md:rtl:mr-4 rounded-[12px] dark:bg-slate-800 flex items-center justify-center flex-grow">
<GoSearch
style={{
color: 'rgb(156 163 175)'
}}
/>
<input
className="px-4 py-2 md:py-3 bg-transparent outline-none w-full text-[15px]"
type="search"
placeholder={`${t.search}`}
onChange={e => setValue(e.target.value)}
/>
{loading && (
<Spinner
color="#A71B4A"
size="sm"
/>
)}
</div>
{!isEmpty(value) && locations && !isEmpty(locations) && (
<div className="flex flex-col overflow-scroll max-h-[100vh] bg-white rounded-md h-[100vh] relative z-[9995] divide-y">
{locations.map((singleLocation, index) => {
const { primaryName, secondaryName } = formatLocationName(singleLocation.display_name)
return (
<div
key={singleLocation.place_id}
className="text-ellipsis flex items-start gap-3 my-1 p-2 "
onClick={() => {
handleLocationClick(singleLocation.lat, singleLocation.lon)
setQuery(primaryName)
setValue('')
}}
>
<Image
className="mt-1"
src="/images/SearchLocationMarker.svg"
alt="Location point"
/>
<div>
<h3 className="text-[15px]/[22.5px] font-[600] text-[#37474F]">{primaryName}</h3>
<h4 className="text-[15px]/[17.5px] font-[400] text-[#7C7C7C]">{secondaryName}</h4>
</div>
</div>
)
})}
</div>
)}
</>
)
}

export default SearchBar
Loading