Skip to content

Commit

Permalink
feat: move feature available in application into a provider
Browse files Browse the repository at this point in the history
  • Loading branch information
karrui committed Jul 7, 2023
1 parent 4288ed2 commit f5adbb4
Show file tree
Hide file tree
Showing 8 changed files with 83 additions and 27 deletions.
53 changes: 53 additions & 0 deletions src/components/AppProviders/FeatureProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import {
createContext,
type FC,
type PropsWithChildren,
useContext,
} from 'react'
import { env } from '~/env.mjs'

type FeatureContextProps = {
storage: boolean
sgid: boolean
}

// Exported for testing.
export const FeatureContext = createContext<FeatureContextProps | undefined>(
undefined
)

/**
* Provider component that wraps your app and makes the available features available to any
* child component that calls `useFeatures()`.
*/
export const FeatureProvider: FC<PropsWithChildren> = ({ children }) => {
const features = useProvideFeatures()

return (
<FeatureContext.Provider value={features}>
{children}
</FeatureContext.Provider>
)
}

/**
* Hook for components nested in FeatureProvider component to get the current feature object.
*/
export const useFeatures = (): FeatureContextProps => {
const context = useContext(FeatureContext)
if (!context) {
throw new Error(
`useFeatures must be used within a FeatureProvider component`
)
}
return context
}

// Provider hook that creates auth object and handles state
const useProvideFeatures = () => {
// Return the user object and auth methods
return {
storage: !!env.NEXT_PUBLIC_ENABLE_STORAGE,
sgid: !!env.NEXT_PUBLIC_ENABLE_SGID,
}
}
1 change: 1 addition & 0 deletions src/components/AppProviders/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './FeatureProvider'
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
import { useDisclosure } from '@chakra-ui/react'
import { Button } from '@opengovsg/design-system-react'
import { NewPostModal } from './NewPostModal'
import { env } from '~/env.mjs'

const CAN_UPLOAD = !!env.NEXT_PUBLIC_ENABLE_STORAGE
import { useFeatures } from '~/components/AppProviders'

export const NewPostModalButton = (): JSX.Element => {
const { isOpen, onOpen, onClose } = useDisclosure()
const { storage } = useFeatures()
return (
<>
<Button size="xs" onClick={onOpen}>
New post
</Button>
<NewPostModal
allowImageUpload={CAN_UPLOAD}
allowImageUpload={storage}
isOpen={isOpen}
onClose={onClose}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@ import { type MouseEventHandler } from 'react'
import { BiMessageRounded } from 'react-icons/bi'
import { type RouterOutput } from '~/utils/trpc'
import { AddCommentModal } from '../AddCommentModal'
import { env } from '~/env.mjs'
import { useFeatures } from '~/components/AppProviders'

const CAN_UPLOAD = !!env.NEXT_PUBLIC_ENABLE_STORAGE
interface AddCommentActionProps {
post: RouterOutput['post']['byUser']['posts'][number]
onSuccess?: () => void
Expand All @@ -17,6 +16,7 @@ export const AddCommentAction = ({
onSuccess,
}: AddCommentActionProps): JSX.Element => {
const { isOpen, onOpen, onClose } = useDisclosure()
const { storage } = useFeatures()

const handleOpenModal: MouseEventHandler = (e) => {
e.stopPropagation()
Expand All @@ -35,7 +35,7 @@ export const AddCommentAction = ({
{post._count.replies}
</Button>
<AddCommentModal
allowImageUpload={CAN_UPLOAD}
allowImageUpload={storage}
parentPost={post}
isOpen={isOpen}
onClose={onClose}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@ import { Button } from '@opengovsg/design-system-react'
import { type MouseEventHandler } from 'react'
import { type RouterOutput } from '~/utils/trpc'
import { AddCommentModal } from '../AddCommentModal'
import { env } from '~/env.mjs'
import { useFeatures } from '~/components/AppProviders'

const CAN_UPLOAD = !!env.NEXT_PUBLIC_ENABLE_STORAGE
interface ReplyToPostActionProps {
post: RouterOutput['post']['byUser']['posts'][number]
onSuccess?: () => void
Expand All @@ -18,6 +17,7 @@ export const ReplyToPostAction = ({
onSuccess,
}: ReplyToPostActionProps): JSX.Element => {
const { isOpen, onOpen, onClose } = useDisclosure()
const { storage } = useFeatures()

const handleOpenModal: MouseEventHandler = (e) => {
e.stopPropagation()
Expand All @@ -38,7 +38,7 @@ export const ReplyToPostAction = ({
</Button>
</Box>
<AddCommentModal
allowImageUpload={CAN_UPLOAD}
allowImageUpload={storage}
parentPost={post}
isOpen={isOpen}
onClose={onClose}
Expand Down
13 changes: 6 additions & 7 deletions src/features/settings/components/AvatarUpload.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,17 @@ import { Input, useToast } from '@opengovsg/design-system-react'
import { type ChangeEventHandler, useMemo, useState } from 'react'
import { BiImageAdd } from 'react-icons/bi'
import { Avatar } from '~/components/Avatar'
import { env } from '~/env.mjs'
import { ACCEPTED_FILE_TYPES } from '~/utils/image'
import { useUploadAvatarMutation } from '../api'
import { useFeatures } from '~/components/AppProviders'

interface AvatarUploadProps {
name?: string | null
url?: string | null
}

const CAN_UPLOAD = !!env.NEXT_PUBLIC_ENABLE_STORAGE

export const AvatarUpload = ({ url, name }: AvatarUploadProps): JSX.Element => {
const { storage } = useFeatures()
// Will load this over `url` if provided for UX.
const [isHover, setIsHover] = useState(false)

Expand Down Expand Up @@ -47,14 +46,14 @@ export const AvatarUpload = ({ url, name }: AvatarUploadProps): JSX.Element => {
}

const hoverProps = useMemo(() => {
if (!CAN_UPLOAD) {
if (!storage) {
return {}
}
return {
onMouseOver: () => setIsHover(true),
onMouseLeave: () => setIsHover(false),
}
}, [])
}, [storage])

return (
<Box pos="relative">
Expand All @@ -75,13 +74,13 @@ export const AvatarUpload = ({ url, name }: AvatarUploadProps): JSX.Element => {
align="center"
justify="center"
cursor={
!CAN_UPLOAD || uploadAvatarMutation.isLoading ? 'default' : 'pointer'
!storage || uploadAvatarMutation.isLoading ? 'default' : 'pointer'
}
w="7rem"
h="7rem"
>
<Input
isDisabled={!CAN_UPLOAD || uploadAvatarMutation.isLoading}
isDisabled={!storage || uploadAvatarMutation.isLoading}
type="file"
id="avatar-upload"
accept={ACCEPTED_FILE_TYPES.join(',')}
Expand Down
5 changes: 3 additions & 2 deletions src/features/sign-in/components/SgidLoginButton.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { Button } from '@opengovsg/design-system-react'
import { useRouter } from 'next/router'
import { env } from '~/env.mjs'
import { useFeatures } from '~/components/AppProviders'
import { trpc } from '~/utils/trpc'

export const SgidLoginButton = (): JSX.Element | null => {
const router = useRouter()
const { sgid } = useFeatures()
const sgidLoginMutation = trpc.auth.sgid.login.useMutation({
onSuccess: async ({ redirectUrl }) => {
await router.push(redirectUrl)
Expand All @@ -15,7 +16,7 @@ export const SgidLoginButton = (): JSX.Element | null => {
return sgidLoginMutation.mutate({})
}

if (!env.NEXT_PUBLIC_ENABLE_SGID) {
if (!sgid) {
return null
}

Expand Down
19 changes: 11 additions & 8 deletions src/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { type NextPageWithLayout } from '~/lib/types'
import { DefaultLayout } from '~/templates/layouts/DefaultLayout'
import { theme } from '~/theme'
import { trpc } from '~/utils/trpc'
import { FeatureProvider } from '~/components/AppProviders'

type AppPropsWithAuthAndLayout = AppProps & {
Component: NextPageWithLayout
Expand All @@ -22,14 +23,16 @@ const MyApp = ((props: AppPropsWithAuthAndLayout) => {
// Must wrap Jotai's provider in SSR context, see https://jotai.org/docs/guides/nextjs#provider.
<Provider>
<ThemeProvider theme={theme}>
<ErrorBoundary>
<Suspense fallback={<Skeleton width="100vw" height="100vh" />}>
<ChildWithLayout {...props} />
{process.env.NODE_ENV !== 'production' && (
<ReactQueryDevtools initialIsOpen={false} />
)}
</Suspense>
</ErrorBoundary>
<FeatureProvider>
<ErrorBoundary>
<Suspense fallback={<Skeleton width="100vw" height="100vh" />}>
<ChildWithLayout {...props} />
{process.env.NODE_ENV !== 'production' && (
<ReactQueryDevtools initialIsOpen={false} />
)}
</Suspense>
</ErrorBoundary>
</FeatureProvider>
</ThemeProvider>
</Provider>
)
Expand Down

0 comments on commit f5adbb4

Please sign in to comment.