Skip to content
Open
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
22 changes: 11 additions & 11 deletions APP_DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,11 @@ The app designs are produced in Figma. You can inspect different elements within

Our app is built using React Native working with the [Expo CLI (command-line tool).](https://docs.expo.dev/more/expo-cli/) and [Expo Go.](https://expo.dev/client)

It's easy to be confused about React Native and Expo, partly because they have changed over time as has the relationship between them (it's easy to find articles which are out of date) and because there are different ways to use them. [This article](https://retool.com/blog/expo-cli-vs-react-native-cli/) has quite a good background.
It's easy to be confused about React Native and Expo, partly because they have changed over time as has the relationship between them (it's easy to find articles which are out of date) and because there are different ways to use them. [This article](https://retool.com/blog/expo-cli-vs-react-native-cli/) has quite a good background.

### Expo trade-offs

We are using the Expo CLI and Expo Go because they make development and deployment of the app a lot easier. The main trade-off is that we cannot use every npm package that works with React Native.
We are using the Expo CLI and Expo Go because they make development and deployment of the app a lot easier. The main trade-off is that we cannot use every npm package that works with React Native.

There are many React Native packages we can use, but when it involves interacting with the device hardware (e.g. camera, GPS, etc) or the OS (e.g. the clipboard, file storage or sharing) or occasionally other areas (e.g. SVGs) then we are usually limited to packages that are supported as part of the Expo SDK library (but for the kind of app we're building, this should be ok).

Expand Down Expand Up @@ -145,7 +145,7 @@ When you're building (or changing) a component or container, or changing a theme

NativeBase does some handling of dark mode straight out of the box, so you may not need to change anything.

**If you're switching a container to use NativeBase** check out `SettingsContainer` and `VerticalStackContainer` examples as they're already working reasonably well with dark mode. One of the things you'll need to do in your container is switch from using the old theme and switch from using any `styled` components/views.
**If you're switching a container to use NativeBase** check out `SettingsContainer` example as they're already working reasonably well with dark mode. One of the things you'll need to do in your container is switch from using the old theme and switch from using any `styled` components/views.

**If you need to set colours based on dark/light mode** [see the docs here](https://docs.nativebase.io/dark-mode) and wherever possible set `_light` and `_dark` properties in the `StaTheme` file (approach 1. in the docs) rather than setting them on your individual component -- i.e. try to make settings as universal and as easily reusable as possible.

Expand Down Expand Up @@ -279,27 +279,27 @@ There are broadly two kinds of things that can go wrong on the front-end app:

Ordinarily, when you are developing using Expo Go, you should **not** have `BUGSNAG_API_KEY` set to the real API key value, otherwise it will send crash logs to Bugsnag which we usually don't want (ordinarily we only want to use Bugsnag to track errors and crashes on real devices).

> You do need to set a value otherwise the app will not run, so you can use e.g. `BUGSNAG_API_KEY="no_api_key"`
> You do need to set a value otherwise the app will not run, so you can use e.g. `BUGSNAG_API_KEY="no_api_key"`

> During development, in the terminal window where you are running Expo you may see the message `[bugsnag] Bugsnag.start() was called more than once. Ignoring.` You don't need to worry about this, it's just the app reloading and trying to start Bugsnag again.
> During development, in the terminal window where you are running Expo you may see the message `[bugsnag] Bugsnag.start() was called more than once. Ignoring.` You don't need to worry about this, it's just the app reloading and trying to start Bugsnag again.

### Seeing Bugsnag reports

Ask one of the team to add you to the **it470-volunteer-app-errors** Slack channel where you can see the latest bugs coming in from the app on real devices, and the API production server.

If you know a bug has been fixed or can safely be ignored, please click the 'Mark as fixed' or 'Ignore' button on the error in the Slack message.
If you know a bug has been fixed or can safely be ignored, please click the 'Mark as fixed' or 'Ignore' button on the error in the Slack message.

To get more details on a bug you'll need to go to [our Bugsnag inbox here](https://app.bugsnag.com/scottish-tech-army/volunteer-app/errors) -- you'll need the Bugsnag login details from another team member.
To get more details on a bug you'll need to go to [our Bugsnag inbox here](https://app.bugsnag.com/scottish-tech-army/volunteer-app/errors) -- you'll need the Bugsnag login details from another team member.

> Note: there are two Projects in Bugsnag -- one for the front-end app ('Volunteer app'), another for the API ('Volunteer app API'). Make sure you're looking at the right one. You can also filter by development/production.
> Note: there are two Projects in Bugsnag -- one for the front-end app ('Volunteer app'), another for the API ('Volunteer app API'). Make sure you're looking at the right one. You can also filter by development/production.

> When you look into an error, click on it in the Inbox in Bugsnag, then on the 'Stacktrace' tab you'll need to find where the error originated. The first entry in the stacktrace is just the `logging` module, you need to find what's below that and click to expand it to see where in the code the error actually occurred.
> When you look into an error, click on it in the Inbox in Bugsnag, then on the 'Stacktrace' tab you'll need to find where the error originated. The first entry in the stacktrace is just the `logging` module, you need to find what's below that and click to expand it to see where in the code the error actually occurred.

### Logging errors to Bugsnag during development

You can log errors to Bugsnag when developing in Expo Go if you really need to. **You shouldn't normally need to do this -- Bugsnag error logging is usually to monitor crashes and errors in the production app. You should only use this in development when normal error detection is insufficient** e.g. because you want to figure out why the app is crashing due to a lack of memory. **Don't** use this in place of normal code tools like `console.error` and `console.log` and other normal testing approaches.
You can log errors to Bugsnag when developing in Expo Go if you really need to. **You shouldn't normally need to do this -- Bugsnag error logging is usually to monitor crashes and errors in the production app. You should only use this in development when normal error detection is insufficient** e.g. because you want to figure out why the app is crashing due to a lack of memory. **Don't** use this in place of normal code tools like `console.error` and `console.log` and other normal testing approaches.

You can force the app to report errors to Bugsnag during development using Expo Go (this also overrides the user permissions setting which normally determines whether or not send error reports). To do this:
You can force the app to report errors to Bugsnag during development using Expo Go (this also overrides the user permissions setting which normally determines whether or not send error reports). To do this:

- Create a `.env` file if you don't have one already, and add `BUGSNAG_API_KEY="xxxxxxxxxxxxxxxx"` repacing `xxxxxxxxxxxxxxxx` with the value of our actual Bugsnag API key for the app (ask on the team Slack channel and someone can give you this -- note: the front-end app and the API use different Bugsnag API keys)
- In your `.env` file include the line `BUGSNAG_ALWAYS_SEND_BUGS="true"` (this forces the API to send errors to Bugsnag, even though you're in a development environment)
Expand Down
34 changes: 0 additions & 34 deletions src/Components/Project/ProjectFilterSort.tsx

This file was deleted.

35 changes: 34 additions & 1 deletion src/NativeBase/Components/FreeSearchBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@
* @file Text input for searching.
*/

import { Box, Input } from 'native-base'
import { Box, Icon, IconButton, Input } from 'native-base'
import React, { FC, useState } from 'react'
import MaterialIcons from 'react-native-vector-icons/MaterialIcons'
import SearchIconHighlighted from './SearchIconHighlighted'

export interface FreeSearchBarProps {
handleChangeText?: (updatedText: string) => void
handleSubmit: (submitText: string) => void
handleClearSearch?: () => void
marginBottom?: string
marginTop?: string
}
Expand All @@ -26,6 +28,7 @@ export interface FreeSearchBarProps {
const FreeSearchBar: FC<FreeSearchBarProps> = ({
handleChangeText,
handleSubmit,
handleClearSearch,
marginBottom,
marginTop,
}) => {
Expand All @@ -39,12 +42,34 @@ const FreeSearchBar: FC<FreeSearchBarProps> = ({

const onSubmitEditing = () => handleSubmit(text)

const clearText = () => {
setText('')
if (handleClearSearch) handleClearSearch() // Call the clear function passed as prop
}

return (
<Box alignItems="center">
<Input
accessibilityLabel="Search for text"
height="12"
InputLeftElement={<SearchIconHighlighted />}
InputRightElement={
text ? (
<IconButton
icon={
<Icon
as={MaterialIcons}
name="close"
size={5}
color="gray.500"
/>
}
onPress={clearText}
variant="link"
_pressed={{ bg: 'gray.200' }}
/>
) : undefined
}
lineHeight="md"
marginBottom={marginBottom ?? '4'}
marginTop={marginTop ?? '0'}
Expand All @@ -56,6 +81,14 @@ const FreeSearchBar: FC<FreeSearchBarProps> = ({
placeholder="Search..."
textAlignVertical="center"
value={text}
borderWidth={0}
backgroundColor={text ? 'gray.200' : 'gray.100'}
borderRadius={15}
placeholderTextColor="gray.400"
color="gray.800"
_focus={{
borderColor: 'transparent', // Ensure no border color on focus
}}
/>
</Box>
)
Expand Down
139 changes: 139 additions & 0 deletions src/NativeBase/Components/ProjectTagButtonsFilter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import {
Projects,
ProjectSector,
ProjectsSearchField,
ProjectTechnology,
} from '@/Services/modules/projects'
import { RoleGroupName } from '@/Services/modules/projects/roleGroups'
import { ProjectsState } from '@/Store/Projects'
import { VStack } from 'native-base'
import React, { FC, useState } from 'react'
import { useSelector } from 'react-redux'
import { ListSearch } from '../Containers/ListContainer'
import ChoicesList, {
ChoicesListChoice,
ChoicesListColour,
ChoicesListFontStyle,
} from './ChoicesList'
import TagButtons from './TagButtons'

export interface ProjectSearch extends ListSearch {
results: Projects // the projects results for this search
}
export interface ProjectsTagButtonsFilterProps {
handleQuickSearchSubmit: (
searchField: ProjectsSearchField,
searchQueryChoice: string,
) => void
}

const ProjectsTagButtonsFilter: FC<ProjectsTagButtonsFilterProps> = ({
handleQuickSearchSubmit,
}) => {
// Fetch all projects from the store
const allProjects = useSelector(
(state: { projects: ProjectsState }) => state.projects.projects,
)

// State to track the currently active tag (using simple strings)
const [activeTag, setActiveTag] = useState<string | null>(null)

// Define which quick search options to use
const quickSearchRoleGroupNames: RoleGroupName[] = [
RoleGroupName.WebDeveloper,
RoleGroupName.TechSupport,
RoleGroupName.UIUX,
RoleGroupName.Researcher,
RoleGroupName.BAPM,
RoleGroupName.ScrumMaster,
]
const quickSearchRoleChoices: ChoicesListChoice[] =
quickSearchRoleGroupNames.map(
roleGroupName =>
({
text: roleGroupName,
onPress: () =>
handleQuickSearchSubmit(ProjectsSearchField.Role, roleGroupName),
} as ChoicesListChoice),
)

const quickSearchTechnologies: ChoicesListChoice[] = Object.values(
ProjectTechnology,
).map(
technology =>
({
text: technology,
onPress: () =>
handleQuickSearchSubmit(ProjectsSearchField.Skills, technology),
} as ChoicesListChoice),
)

const quickSearchCauses: ChoicesListChoice[] = Object.values(
ProjectSector,
).map(
cause =>
({
text: cause,
onPress: () =>
handleQuickSearchSubmit(ProjectsSearchField.Sector, cause),
} as ChoicesListChoice),
)

// Define colour and style to use for quick search options
const quickSearchListColour = ChoicesListColour.primary
const quickSearchListStyle = ChoicesListFontStyle.mediumLight

/**
* Handle tag press logic
* If the tag is already open, close it by setting activeTag to null.
* If a different tag is clicked, close the previous one and open the new one.
*/
const handleTagPress = (tag: string) => {
setActiveTag(activeTag === tag ? null : tag)
}

return (
<VStack>
{/* Tag Buttons for Roles, Tech, and Causes */}
<TagButtons
tags={['Roles', 'Tech', 'Causes']} // String tags
iconState={{
Roles: activeTag === 'Roles',
Tech: activeTag === 'Tech',
Causes: activeTag === 'Causes',
}}
handleTagPress={handleTagPress} // String type handler
/>
<VStack padding="2">
{/* Show the list of role choices if Roles tab is active */}
{activeTag === 'Roles' && (
<ChoicesList
choices={quickSearchRoleChoices}
colour={quickSearchListColour}
style={quickSearchListStyle}
/>
)}

{/* Show the list of technology choices if Tech tab is active */}
{activeTag === 'Tech' && (
<ChoicesList
choices={quickSearchTechnologies}
colour={quickSearchListColour}
style={quickSearchListStyle}
/>
)}

{/* Show the list of causes if Causes tag is active */}
{activeTag === 'Causes' && (
<ChoicesList
choices={quickSearchCauses}
colour={quickSearchListColour}
style={quickSearchListStyle}
/>
)}
</VStack>
</VStack>
)
}

export default ProjectsTagButtonsFilter
8 changes: 1 addition & 7 deletions src/NativeBase/Components/SearchIconHighlighted.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,7 @@ import MaterialIcons from 'react-native-vector-icons/MaterialIcons'
* @returns {React.ReactElement} Component
*/
const SearchIconHighlighted = () => (
<Icon
as={MaterialIcons}
color="accentPurple.100"
ml="2"
name="search"
size={6}
/>
<Icon as={MaterialIcons} color="gray.400" ml="2" name="search" size={6} />
)

export default SearchIconHighlighted
56 changes: 56 additions & 0 deletions src/NativeBase/Components/TagButtons.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import React from 'react'
import { HStack, Icon, IconButton, Pressable, Text } from 'native-base'
import MaterialIcons from 'react-native-vector-icons/MaterialIcons'

interface TagButtonsProps {
tags: string[] // An array of tag names, e.g., ['Roles', 'Tech', 'Causes']
iconState: Record<string, boolean> // A dynamic record that maps tags to boolean states
handleTagPress: (tag: string) => void // A function that takes any tag
}

const TagButtons: React.FC<TagButtonsProps> = ({
tags,
iconState,
handleTagPress,
}) => {
return (
<HStack space={2} alignItems="center">
{tags.map(tag => (
<Pressable
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd also extract a single tagbutton into it's own component. This component here (TagButtons) could be then just using those tagbutton components. Naming wise we could also name HTagButtons (H for horinzontal) but as we do have only one version currently I think FilterTagButtons would be a good name as it says that it's about tag buttons used for filtering.

key={tag}
onPress={() => handleTagPress(tag)}
borderRadius={40}
pl={4}
pr={2}
py={2}
display="flex"
flexDirection="row"
alignItems="center"
justifyContent="center"
backgroundColor={iconState[tag] ? 'primary.20' : 'gray.100'}
borderColor={iconState[tag] ? 'primary.100' : 'transparent'}
borderWidth={1}
>
<Text color={iconState[tag] ? 'primary.100' : 'gray.500'}>{tag}</Text>
<IconButton
icon={
<Icon
as={MaterialIcons}
name={
iconState[tag] ? 'keyboard-arrow-up' : 'keyboard-arrow-down'
}
size={5}
color={iconState[tag] ? 'primary.100' : 'gray.500'}
/>
}
variant="outline"
_icon={{ color: 'gray.500' }}
onPress={() => handleTagPress(tag)}
/>
</Pressable>
))}
</HStack>
)
}

export default TagButtons
Loading