Skip to content

Commit

Permalink
Merge branch 'dev' into l10n_dev
Browse files Browse the repository at this point in the history
  • Loading branch information
kodiakhq[bot] authored Feb 7, 2024
2 parents f566435 + 24456ff commit a1e5ec3
Show file tree
Hide file tree
Showing 16 changed files with 213 additions and 92 deletions.
6 changes: 6 additions & 0 deletions apps/app/public/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,12 @@
"take-action": "Take Action",
"uncheck-all": "Uncheck all",
"user-avatar": "User avatar",
"user-menu": {
"admin-options": "Admin Options",
"data-portal": "Data Portal Home",
"edit-page": "Edit this page",
"user-options": "User Options"
},
"verify-account": {
"verified": "Account verified!",
"verified-body": "<Text>Your account has been verified! Please <LoginModal>login</LoginModal> to start using InReach's additional features.",
Expand Down
3 changes: 3 additions & 0 deletions apps/app/src/pages/api/i18n/load.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ export default async function handler(req: NextRequest) {
// res.status(200).json(data)
return new Response(JSON.stringify(data), {
status: 200,
headers: {
'Cache-Control': 's-maxage=1, public, stale-while-revalidate=3600',
},
})
} catch (error) {
log.error(error)
Expand Down
3 changes: 2 additions & 1 deletion apps/app/src/pages/api/trpc/[trpc].ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,11 @@ export default createNextApiHandler({
const isQuery = type === 'query'

if (ctx?.res && !shouldSkip && allOk && isQuery) {
console.debug('[tRPC] Setting Cache headers in response.')
const ONE_DAY_IN_SECONDS = 60 * 60 * 24
return {
headers: {
'cache-control': `s-maxage=1, stale-while-revalidate=${ONE_DAY_IN_SECONDS}`,
'Cache-Control': `s-maxage=1, public, stale-while-revalidate=${ONE_DAY_IN_SECONDS}`,
},
}
}
Expand Down
42 changes: 7 additions & 35 deletions apps/app/src/pages/org/[slug]/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,34 +23,13 @@ import { useSearchState } from '@weareinreach/ui/hooks/useSearchState'
import { OrgPageLoading } from '@weareinreach/ui/loading-states/OrgPage'
import { api } from '~app/utils/api'
import { getServerSideTranslations } from '~app/utils/i18n'
import { nsFormatter } from '~app/utils/nsFormatter'

const GoogleMap = dynamic(() =>
import('@weareinreach/ui/components/core/GoogleMap').then((mod) => mod.GoogleMap)
)
const LoadingState = () => (
<>
<Grid.Col sm={8} order={1} pb={40}>
{/* Toolbar */}
<Skeleton h={48} w='100%' radius={8} />
<Stack pt={24} align='flex-start' spacing={40}>
{/* Listing Basic */}
<Skeleton h={260} w='100%' />
{/* Body */}
<Skeleton h={520} w='100%' />
{/* Tab panels */}
</Stack>
</Grid.Col>
<Grid.Col order={2}>
<Stack spacing={40}>
{/* Contact Card */}
<Skeleton h={520} w='100%' />
{/* Visit Card */}
<Skeleton h={260} w='100%' />
</Stack>
</Grid.Col>
</>
)

const formatNS = nsFormatter(['common', 'services', 'attribute', 'phone-type'])
const useStyles = createStyles((theme) => ({
tabsList: {
position: 'sticky',
Expand All @@ -67,11 +46,7 @@ const OrganizationPage = ({
const router = useRouter<'/org/[slug]'>()
const { data, status } = api.organization.forOrgPage.useQuery({ slug }, { enabled: !!slug })
// const { query } = router
const {
t,
i18n,
ready: i18nReady,
} = useTranslation(['common', 'services', 'attribute', 'phone-type', ...(orgId ? [orgId] : [])])
const { t, i18n, ready: i18nReady } = useTranslation(formatNS(orgId))
const [activeTab, setActiveTab] = useState<string | null>('services')
const [loading, setLoading] = useState(true)
const { data: hasRemote } = api.service.forServiceInfoCard.useQuery(
Expand All @@ -94,15 +69,14 @@ const OrganizationPage = ({
const reviewsRef = useRef<HTMLDivElement>(null)

useEffect(() => {
if (i18nReady && data && status === 'success') {
if (i18nReady && data && status === 'success' && !router.isFallback) {
setLoading(false)
if (data.locations?.length > 1) setActiveTab('locations')
}
}, [data, status, i18nReady])
}, [data, status, i18nReady, router.isFallback])

useEffect(() => {
data?.id &&
i18n.reloadResources(i18n.resolvedLanguage, ['common', 'services', 'attribute', 'phone-type', data.id])
orgId && i18n.reloadResources(i18n.resolvedLanguage, formatNS(orgId))
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])

Expand Down Expand Up @@ -287,9 +261,7 @@ export const getStaticProps = async ({
if (!orgId) return { notFound: true }

const [i18n] = await Promise.allSettled([
orgId
? getServerSideTranslations(locale, ['common', 'services', 'attribute', 'phone-type', orgId])
: getServerSideTranslations(locale, ['common', 'services', 'attribute', 'phone-type']),
getServerSideTranslations(locale, formatNS(orgId)),
ssg.organization.forOrgPage.prefetch({ slug }),
])
// await ssg.organization.getBySlug.prefetch({ slug })
Expand Down
2 changes: 0 additions & 2 deletions apps/app/src/utils/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ import { type Namespaces } from '@weareinreach/db/generated/namespaces'

import i18nextConfig from '../../next-i18next.config.mjs'

// type ArrayElementOrSelf<T> = T extends Array<infer U> ? U[] : T[]

type Namespace = LiteralUnion<Namespaces, string>
type NamespaceSSR = string | string[] | undefined
export const getServerSideTranslations = async (
Expand Down
12 changes: 12 additions & 0 deletions apps/app/src/utils/nsFormatter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import compact from 'just-compact'
import { type LiteralUnion } from 'type-fest'

import { type Namespaces } from '@weareinreach/db/generated/namespaces'

type Namespace = LiteralUnion<Namespaces, string>
export const nsFormatter =
(baseNamespaces: Namespace | Namespace[]) => (additionalNamespaces?: string | string[]) =>
compact<Namespace>([
...(Array.isArray(baseNamespaces) ? baseNamespaces : [baseNamespaces]),
...(Array.isArray(additionalNamespaces) ? additionalNamespaces : [additionalNamespaces]),
])
2 changes: 1 addition & 1 deletion packages/api/trpc/ssr.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createServerSideHelpers } from '@trpc/react-query/server'
import { type GetServerSidePropsContext, type NextApiRequest, type NextApiResponse } from 'next'

import { getServerSession } from '@weareinreach/auth/next-auth/get-session'
import { getServerSession } from '@weareinreach/auth/next-auth/session.server'
import { type Session } from '@weareinreach/auth/next-auth/types'
import { transformer } from '@weareinreach/util/transformer'
import { createContextInner } from '~api/lib/context'
Expand Down
1 change: 1 addition & 0 deletions packages/auth/index.browser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { checkPermissions } from './next-auth/session.browser'
5 changes: 2 additions & 3 deletions packages/auth/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
/* eslint-disable import/no-unused-modules */
import { type DefaultSession, type DefaultUser, type User } from 'next-auth/core/types'
import { type DefaultJWT } from 'next-auth/jwt'

export { getServerSession, checkPermissions, checkServerPermissions } from './next-auth/get-session'

export { getServerSession, checkServerPermissions } from './next-auth/session.server'
export { checkPermissions } from './next-auth/session.browser'
export type { Session } from 'next-auth'

export * from './lib'
Expand Down
18 changes: 18 additions & 0 deletions packages/auth/next-auth/session.browser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { type Session } from 'next-auth'

import { type Permission } from '@weareinreach/db/generated/permission'

export interface CheckPermissionsParams {
session: Session | null
permissions: Permission | Permission[]
has: 'all' | 'some'
}
export const checkPermissions = ({ session, permissions, has = 'all' }: CheckPermissionsParams) => {
if (!session) return false
if (session.user.permissions.includes('root')) return true
const userPerms = new Set(session.user.permissions)
const permsToCheck = Array.isArray(permissions) ? permissions : [permissions]
return has === 'all'
? permsToCheck.every((perm) => userPerms.has(perm))
: permsToCheck.some((perm) => userPerms.has(perm))
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { type GetServerSidePropsContext, type NextApiRequest, type NextApiResponse } from 'next'
import { getServerSession as nextauthServerSession, type Session } from 'next-auth'

import { type Permission } from '@weareinreach/db/generated/permission'
import { getServerSession as nextauthServerSession } from 'next-auth'

import { authOptions } from './auth-options'
import { checkPermissions, type CheckPermissionsParams } from './session.browser'

interface SSRContext {
req: GetServerSidePropsContext['req']
Expand All @@ -18,20 +17,6 @@ type ServerContext = SSRContext | ApiContext
export const getServerSession = async (ctx: ServerContext) => {
return nextauthServerSession(ctx.req, ctx.res, authOptions)
}
interface CheckPermissionsParams {
session: Session | null
permissions: Permission | Permission[]
has: 'all' | 'some'
}
export const checkPermissions = ({ session, permissions, has = 'all' }: CheckPermissionsParams) => {
if (!session) return false
if (session.user.permissions.includes('root')) return true
const userPerms = new Set(session.user.permissions)
const permsToCheck = Array.isArray(permissions) ? permissions : [permissions]
return has === 'all'
? permsToCheck.every((perm) => userPerms.has(perm))
: permsToCheck.some((perm) => userPerms.has(perm))
}

interface CheckServerPermissions extends Omit<CheckPermissionsParams, 'session'> {
ctx: ServerContext
Expand Down
9 changes: 9 additions & 0 deletions packages/auth/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@
"version": "0.100.0",
"private": true,
"license": "GPL-3.0-only",
"exports": {
".": {
"browser": "./index.browser.ts",
"default": "./index.ts"
},
"./*": {
"default": "./*"
}
},
"main": "./index.ts",
"types": "./index.ts",
"scripts": {
Expand Down
45 changes: 42 additions & 3 deletions packages/ui/components/core/UserMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,16 @@ import {
UnstyledButton,
} from '@mantine/core'
import dynamic from 'next/dynamic'
import { useRouter } from 'next/router'
import { signOut, useSession } from 'next-auth/react'
import { useTranslation } from 'next-i18next'

import { checkPermissions } from '@weareinreach/auth'
import { Button } from '~ui/components/core/Button'
import { LangPicker } from '~ui/components/core/LangPicker'
import { Link } from '~ui/components/core/Link'
import { useCustomVariant } from '~ui/hooks/useCustomVariant'
// import { LoginModalLauncher } from '~ui/modals/Login'
// import { SignupModalLauncher } from '~ui/modals/SignUp'

// import { UserAvatar } from './UserAvatar'
// @ts-expect-error Next Dynamic doesn't like polymorphic components
const LoginModalLauncher = dynamic(() =>
import('~ui/modals/LoginSignUp').then((mod) => mod.LoginModalLauncher)
Expand Down Expand Up @@ -70,10 +69,31 @@ const useStyles = createStyles((theme) => ({
export const UserMenu = ({ className, classNames, styles, unstyled }: UserMenuProps) => {
const { t } = useTranslation('common')
const { data: session, status } = useSession()
const router = useRouter()
const { classes, cx } = useStyles(undefined, { name: 'UserMenu', classNames, styles, unstyled })
const variant = useCustomVariant()

const isLoading = status === 'loading'
const canAccessDataPortal = checkPermissions({
session,
permissions: ['dataPortalBasic', 'dataPortalAdmin', 'dataPortalManager'],
has: 'some',
})
const editablePaths: (typeof router.pathname)[] = ['/org/[slug]', '/org/[slug]/[orgLocationId]']
const isEditablePage = editablePaths.includes(router.pathname)
const getEditPathname = (): typeof router.pathname => {
switch (router.pathname) {
case '/org/[slug]': {
return '/org/[slug]/edit'
}
case '/org/[slug]/[orgLocationId]': {
return '/org/[slug]/[orgLocationId]/edit'
}
default: {
return router.pathname
}
}
}

if ((session?.user && status === 'authenticated') || isLoading) {
return (
Expand Down Expand Up @@ -104,6 +124,25 @@ export const UserMenu = ({ className, classNames, styles, unstyled }: UserMenuPr
</UnstyledButton>
</Menu.Target>
<Menu.Dropdown>
{canAccessDataPortal && (
<>
<Menu.Label>{t('user-menu.admin-options')}</Menu.Label>
<Menu.Item component={Link} href='/admin' target='_self'>
{t('user-menu.data-portal')}
</Menu.Item>
{isEditablePage && (
<Menu.Item
component={Link}
onClick={() => router.replace({ pathname: getEditPathname(), query: router.query })}
target='_self'
>
{t('user-menu.edit-page')}
</Menu.Item>
)}
<Menu.Divider />
<Menu.Label>{t('user-menu.user-options')}</Menu.Label>
</>
)}
<Menu.Item component={Link} href='/account/saved' target='_self'>
{t('words.saved')}
</Menu.Item>
Expand Down
Loading

0 comments on commit a1e5ec3

Please sign in to comment.