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
9 changes: 1 addition & 8 deletions src/shared/components/FormSwitch/FormSwitch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,11 @@ import Typography from "../../design/Typography"

export interface FormSwitchProps {
label: string
hint?: string
value: boolean
onChange: (value: boolean) => void
}

const FormSwitch: React.FC<FormSwitchProps> = ({
label,
hint,
value,
onChange,
}) => {
const FormSwitch: React.FC<FormSwitchProps> = ({ label, value, onChange }) => {
const theme = useTheme()
const id = useId()

Expand All @@ -37,7 +31,6 @@ const FormSwitch: React.FC<FormSwitchProps> = ({
<Switch
accessibilityLabel={label}
accessibilityLabelledBy={id}
accessibilityHint={hint}
onValueChange={onChange}
value={value}
thumbColor={theme.palette.primary.contrast}
Expand Down
16 changes: 11 additions & 5 deletions src/shared/components/FormTextField/FormTextField.test.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { fireEvent, render, screen } from "@testing-library/react-native"
import { render, screen, userEvent } from "@testing-library/react-native"

import FormTextField from "./FormTextField"

describe("FormTextField component", () => {
it("renders label", () => {
it("renders label", async () => {
const handleChangeMock = jest.fn()

render(
Expand All @@ -14,9 +14,15 @@ describe("FormTextField component", () => {
></FormTextField>,
)

expect(screen.getByText(/Hello/)).toBeOnTheScreen()
const user = userEvent.setup()
expect(screen.getByText(/Hello/)).toBeTruthy()

fireEvent.changeText(screen.getByLabelText(/Hello/i), "test")
expect(handleChangeMock).toHaveBeenCalledWith("test")
await user.type(screen.getByLabelText(/Hello/i), "test")

expect(handleChangeMock).toHaveBeenCalledTimes(4)
expect(handleChangeMock).toHaveBeenNthCalledWith(1, "responset")
expect(handleChangeMock).toHaveBeenNthCalledWith(2, "responsee")
expect(handleChangeMock).toHaveBeenNthCalledWith(3, "responses")
expect(handleChangeMock).toHaveBeenNthCalledWith(4, "responset")
})
})
6 changes: 0 additions & 6 deletions src/shared/components/FormTextField/FormTextField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,12 @@ import Typography from "../../design/Typography"
export interface FormTextFieldProps {
type?: "text"
label: string
hint?: string
placeholder?: string
value: string
onChange?: (value: string) => void
}

const FormTextField: React.FC<FormTextFieldProps> = ({
label,
hint,
placeholder,
value,
onChange,
}) => {
Expand All @@ -32,10 +28,8 @@ const FormTextField: React.FC<FormTextFieldProps> = ({
<TextInput
accessibilityLabel={label}
accessibilityLabelledBy={id}
accessibilityHint={hint}
onChangeText={onChange}
value={value}
placeholder={placeholder}
style={{
flex: 1,
paddingVertical: 0,
Expand Down
3 changes: 3 additions & 0 deletions src/shared/components/RestaurantHeader/RestaurantHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,10 @@ function getStyles(theme: Theme): {
} {
return StyleSheet.create({
heroBackground: {
width: "100%",
maxWidth: 768,
height: 180,
margin: "auto",
justifyContent: "flex-end",
alignItems: "flex-start",
},
Expand Down
47 changes: 10 additions & 37 deletions src/shared/design/Box/Box.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,43 +53,16 @@ function getStyles(
{
display: "flex",
},
margin && spacingToStyles(theme, "margin", margin),
padding && spacingToStyles(theme, "padding", padding),
typeof margin === "string" && { margin: theme.spacing[margin] },
Array.isArray(margin) && {
marginVertical: theme.spacing[margin[0]],
marginHorizontal: theme.spacing[margin[1]],
},
typeof padding === "string" && { padding: theme.spacing[padding] },
Array.isArray(padding) && {
paddingVertical: theme.spacing[padding[0]],
paddingHorizontal: theme.spacing[padding[1]],
},
]),
})
}

function spacingToStyles(
theme: Theme,
property: "margin" | "padding",
value: ThemeMargin | ThemePadding,
): ViewStyle {
if (typeof value === "string") {
return {
[property]: theme.spacing[value],
}
}

if (value.length === 1) {
return {
[property]: theme.spacing[value[0]],
}
}

if (value.length === 2) {
return {
[`${property}Vertical`]: theme.spacing[value[0]],
[`${property}Horizontal`]: theme.spacing[value[1]],
}
}

if (value.length === 3) {
return {
[`${property}Top`]: theme.spacing[value[0]],
[`${property}Horizontal`]: theme.spacing[value[1]],
[`${property}Bottom`]: theme.spacing[value[2]],
}
}

throw new Error(`Invalid spacing value: ${value}`)
}
39 changes: 28 additions & 11 deletions src/shared/design/Button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,41 +6,58 @@ import {
StyleSheet,
Pressable,
View,
Text,
} from "react-native"

import { Theme, useTheme } from "../theme"
import Typography from "../Typography"

type Variant = "primary" | "secondary" | "outline"

export interface ButtonProps extends PressableProps {
variant?: Variant
margin?: keyof Theme["spacing"]
padding?: keyof Theme["spacing"]
fontSize?: TextStyle["fontSize"]
fontWeight?: TextStyle["fontWeight"]
disabled?: boolean
children: string
}

const Button: React.ForwardRefRenderFunction<View, ButtonProps> = (
{ variant = "primary", disabled, children, ...props },
{
variant = "primary",
margin,
padding,
fontSize = 20,
fontWeight = "400",
disabled,
children,
...props
},
ref,
) => {
const theme = useTheme()
const styles = getStyles(theme, variant)

return (
<Pressable
ref={ref}
{...props}
ref={ref}
style={StyleSheet.compose(styles.pressable, {
...(margin ? { margin: theme.spacing[margin] } : {}),
...(padding ? { padding: theme.spacing[padding] } : {}),
opacity: disabled ? 0.5 : 1,
})}
disabled={disabled}
style={[
styles.pressable,
{
opacity: disabled ? 0.5 : 1,
},
]}
>
<Typography variant="button" style={styles.text}>
<Text
style={StyleSheet.compose(styles.text, {
fontSize,
fontWeight,
})}
>
{children}
</Typography>
</Text>
</Pressable>
)
}
Expand Down
1 change: 1 addition & 0 deletions src/shared/design/Card/Card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ function getStyles(theme: Theme): {
} {
return StyleSheet.create({
container: {
width: "100%",
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.25,

Expand Down
2 changes: 1 addition & 1 deletion src/shared/design/Typography/Typography.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const Typography: React.FC<TypographyProps> = ({
const styles = getStyles(theme, variant)

return (
<Text {...props} style={[styles.text, style]}>
<Text {...props} style={StyleSheet.compose(styles.text, style)}>
{children}
</Text>
)
Expand Down
4 changes: 0 additions & 4 deletions src/shared/design/theme/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,10 @@ export interface Theme {

export type ThemeMargin =
| keyof Theme["spacing"]
| [keyof Theme["spacing"]]
| [keyof Theme["spacing"], keyof Theme["spacing"]]
| [keyof Theme["spacing"], keyof Theme["spacing"], keyof Theme["spacing"]]
export type ThemePadding =
| keyof Theme["spacing"]
| [keyof Theme["spacing"]]
| [keyof Theme["spacing"], keyof Theme["spacing"]]
| [keyof Theme["spacing"], keyof Theme["spacing"], keyof Theme["spacing"]]

const light: Theme = {
palette: {
Expand Down
36 changes: 34 additions & 2 deletions src/shared/services/auth/AuthProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,35 +19,65 @@
const AuthProvider: React.FC<{ children: React.ReactNode }> = ({
children,
}) => {
const [error, setError] = useState<Error | undefined>()
const [isPending, setIsPending] = useState<boolean>(true)
const [userInfo, setUserInfo] = useState<UserInfo | null>()

const signIn = useCallback(async () => {
try {
setError(undefined)
setIsPending(true)
const userInfo = await GoogleSignin.signIn()
setIsPending(false)
setUserInfo(userInfo)
return userInfo.user
} catch (error) {
setUserInfo(null)
console.error("GoogleSignin.signIn() error", error)
setError(error as Error)
setIsPending(false)
setUserInfo(null)
return false
}
}, [])

const signOut = useCallback(async () => {
try {
setError(undefined)
setIsPending(true)

await GoogleSignin.signOut()
setIsPending(false)
setUserInfo(null)

return true
} catch (error) {
console.error("GoogleSignin.signOut() error", error)
setError(error as Error)

return false
}
}, [])

useEffect(() => {
async function run() {
const userInfo = await GoogleSignin.getCurrentUser()
setUserInfo(userInfo)
try {
setError(undefined)
setIsPending(true)

const userInfo = await GoogleSignin.getCurrentUser()

setIsPending(false)
setUserInfo(userInfo || undefined)
} catch (error) {
console.error(
"Call to GoogleSignin.getCurrentUser() failed with error:",
error,
)

setError(error as Error)
setIsPending(false)
}
}

run()
Expand All @@ -57,12 +87,14 @@
() => ({
signIn,
signOut,
error,
isAuthenticated: userInfo ? true : userInfo === null ? false : undefined,
isPending,
user: userInfo?.user,
scopes: userInfo?.scopes,
idToken: userInfo?.idToken,
}),
[signIn, signOut, userInfo],

Check warning on line 97 in src/shared/services/auth/AuthProvider.tsx

View workflow job for this annotation

GitHub Actions / verify

React Hook useMemo has missing dependencies: 'error' and 'isPending'. Either include them or remove the dependency array
)

return <AuthContextProvider value={value}>{children}</AuthContextProvider>
Expand Down
4 changes: 4 additions & 0 deletions src/shared/services/auth/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,12 @@ export interface AuthContext {
signIn: () => Promise<UserInfo["user"] | false>
/** Log the user out from Google Auth. Return boolean success. */
signOut: () => Promise<boolean>
/** Error if any Google API returns an error. Undefined if no errors in the last API call. */
error?: Error | undefined
/** Boolean if the user is authenticated or not. Undefined if unknown. */
isAuthenticated?: boolean
/** Boolean if a Google API call is being made. */
isPending: boolean
/** Google Auth User object. Undefined if not signed in. */
user?: UserInfo["user"]
/** List of Google Auth scopes. Undefined if not signed in. */
Expand Down
1 change: 1 addition & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"@shared/*": ["./src/shared/*"],
"@screens/*": ["./src/screens/*"]
},
"types": ["react-native", "jest", "node"],

"module": "ESNext",
"strict": true,
Expand Down