Skip to content

Commit

Permalink
allow disabling
Browse files Browse the repository at this point in the history
  • Loading branch information
the-kwisatz-haderach committed Jan 22, 2024
1 parent 988c380 commit fef438c
Show file tree
Hide file tree
Showing 9 changed files with 268 additions and 21 deletions.
40 changes: 36 additions & 4 deletions components/Obituary/components/Obituary.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import React, { useCallback } from 'react'
import { Box, Divider, Flex, Heading, Text, VStack } from '@chakra-ui/react'
import {
Box,
Button,
Divider,
Flex,
Heading,
Text,
VStack,
} from '@chakra-ui/react'
import { Link } from '../../Link'
import { useTranslation } from 'next-i18next'
import useModal from '../../../contexts/ModalContext'
Expand All @@ -9,10 +17,14 @@ import { ObituaryRenderer } from '../ObituaryContainer'
import { isMultiObituary } from 'lib/domain/isMultiObituary'
import { RichText } from 'components/RichText'
import { Timestamp } from '../../Timestamp'
import { useUpdateObituary } from 'hooks/reactQuery/mutations'
import { useAdminContext } from 'contexts/AdminContext'

const htmlTagsRegexp = /<(?:"[^"]*"['"]*|'[^']*'['"]*|[^'">])+>/g

export const Obituary: ObituaryRenderer = (props) => {
const isAdmin = useAdminContext()
const { mutate, isLoading } = useUpdateObituary()
const {
image = '',
_id,
Expand All @@ -30,6 +42,7 @@ export const Obituary: ObituaryRenderer = (props) => {
surname_second,
date_of_birth_second,
date_of_death_second,
disabled,
} = props
const { t } = useTranslation()
const isClicked =
Expand All @@ -48,6 +61,10 @@ export const Obituary: ObituaryRenderer = (props) => {
})
}, [open, props])

const handleDisable = () => {
mutate({ _id, disabled: !disabled })
}

return (
<Box
height="100%"
Expand All @@ -56,6 +73,7 @@ export const Obituary: ObituaryRenderer = (props) => {
borderWidth={1}
borderStyle="solid"
borderRadius="sm"
bg={disabled ? 'gray.100' : 'unset'}
_hover={{
boxShadow: `0 20px 25px -5px rgba(${
isClicked ? '222,135,31,0.2' : '0,0,0,0.1'
Expand All @@ -73,7 +91,7 @@ export const Obituary: ObituaryRenderer = (props) => {
>
{t(type)}
</Text>
<Flex gap={10}>
<Flex gap={10} opacity={disabled ? 0.5 : 1}>
<VStack textAlign="center" spacing={3}>
<Box
borderStyle="solid"
Expand Down Expand Up @@ -137,7 +155,7 @@ export const Obituary: ObituaryRenderer = (props) => {
)}
</Flex>
{preamble && (
<Text fontSize="sm" fontStyle="italic">
<Text fontSize="sm" fontStyle="italic" opacity={disabled ? 0.5 : 1}>
{preamble}
</Text>
)}
Expand All @@ -146,6 +164,7 @@ export const Obituary: ObituaryRenderer = (props) => {
className="capitalize"
textAlign="justify"
fontSize="sm"
opacity={disabled ? 0.5 : 1}
sx={{
display: '-webkit-box',
WebkitLineClamp: 6,
Expand All @@ -163,7 +182,20 @@ export const Obituary: ObituaryRenderer = (props) => {
</Text>
)}
<Divider flex={1} alignSelf="flex-end" />
<Flex alignItems="flex-end" justifyContent="flex-end" width="100%">
<Flex alignItems="center" justifyContent="space-between" width="100%">
{isAdmin ? (
<Button
onClick={handleDisable}
isLoading={isLoading}
disabled={isLoading}
colorScheme={!disabled ? 'red' : 'green'}
size="sm"
>
{disabled ? t('show') : t('hide')}
</Button>
) : (
<div />
)}
<Link
onClick={openModal}
href={`/${formattedType}/${_id}`}
Expand Down
13 changes: 13 additions & 0 deletions contexts/AdminContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { ReactNode, createContext, useContext } from 'react'

const AdminContext = createContext(false)

export const AdminProvider = ({
children,
isAdmin,
}: {
children: ReactNode
isAdmin: boolean
}) => <AdminContext.Provider value={isAdmin}>{children}</AdminContext.Provider>

export const useAdminContext = () => useContext(AdminContext)
31 changes: 31 additions & 0 deletions hooks/reactQuery/mutations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,37 @@ import {
} from '@tanstack/react-query'
import { IObituary } from 'lib/domain/types'

export const useUpdateObituary = () => {
const queryClient = useQueryClient()
return useMutation<
IObituary | null,
unknown,
Pick<IObituary, '_id' | 'disabled'>
>(
['disableObituary'],
async ({ _id, disabled }) => {
const res = await fetch('/api/obituaries/' + _id, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
disabled,
}),
})
if (res.ok) {
return res.json()
}
return null
},
{
onSuccess: () => {
queryClient.invalidateQueries(['obituariesInfinite'])
},
}
)
}

export const useIncrementAppreciation = () => {
const queryClient = useQueryClient()

Expand Down
36 changes: 25 additions & 11 deletions lib/domain/getObituaries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,28 @@ import { IObituary, IObituaryQuery, ObituaryType } from './types'

type IGetObituaries = (
db: Db,
query: IObituaryQuery
query: IObituaryQuery,
isAdmin?: boolean
) => Promise<{
data: IObituary[]
next: string
}>

const withCache = (fn: IGetObituaries): IGetObituaries => async (
db,
{ next = '', search = '', category = '', limit = DEFAULT_LIST_LIMIT }
{ next = '', search = '', category = '', limit = DEFAULT_LIST_LIMIT },
isAdmin = false
) => {
if (search === '' || process.env.DISABLE_CACHE === 'true') {
return await fn(db, { next, search, category, limit })
if (search === '' || isAdmin || process.env.DISABLE_CACHE === 'true') {
return await fn(db, { next, search, category, limit }, isAdmin)
}
const kvKey = [next, search, category, limit].join('&')
const cached = await kv.get<ReturnType<IGetObituaries>>(kvKey)
if (cached !== null) {
return cached
}

const res = await fn(db, { next, search, category, limit })
const res = await fn(db, { next, search, category, limit }, isAdmin)
try {
await kv.set(kvKey, JSON.stringify(res), {
px: 60 * 5 * 1000,
Expand All @@ -37,7 +39,8 @@ const withCache = (fn: IGetObituaries): IGetObituaries => async (

const getObituaries: IGetObituaries = async (
db,
{ next = '', search = '', category = '', limit = DEFAULT_LIST_LIMIT }
{ next = '', search = '', category = '', limit = DEFAULT_LIST_LIMIT },
isAdmin = false
) => {
const $regex = new RegExp(search.split(/\s+/).join('|'), 'i')
const obituaries: IObituary[] = JSON.parse(
Expand All @@ -46,11 +49,22 @@ const getObituaries: IGetObituaries = async (
.collection<Omit<IObituary, '_id'>>('obituaries')
.find(
{
...(category && {
...((category || !isAdmin) && {
$and: [
{
type: category as ObituaryType,
},
...(category
? [
{
type: category as ObituaryType,
},
]
: []),
...(isAdmin
? []
: [
{
disabled: { $ne: true },
},
]),
],
}),
...(search && {
Expand Down Expand Up @@ -82,7 +96,7 @@ const getObituaries: IGetObituaries = async (
},
{
limit: limit + 1,
sort: { _id: -1 },
sort: { _id: 'desc' },
}
)
.toArray()
Expand Down
1 change: 1 addition & 0 deletions lib/domain/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export interface IObituary {
is_crawled: boolean
appreciations: number
symbol_image?: string | StoryblokAsset
disabled?: boolean
}

export interface ContactFormInput {
Expand Down
89 changes: 89 additions & 0 deletions pages/admin.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import cookie from 'cookie'
import { ReactElement, useState } from 'react'
import { GetServerSideProps } from 'next'
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
import { useTranslation } from 'next-i18next'
import { Flex } from '@chakra-ui/react'
import { useRouter } from 'next/router'
import { ObituaryGrid } from 'components/ObituaryGrid'
import { AdminProvider } from 'contexts/AdminContext'
import { useObituaries } from 'hooks/reactQuery/queries'
import { ProgressBar } from 'components/ProgessBar'
import { Contained } from 'components/Contained/Contained'
import { SearchInput } from 'components/SearchInput'

interface Props {}

export default function Admin(): ReactElement {
const { t } = useTranslation()
const { query: routerQuery } = useRouter()
const [query, setQuery] = useState((routerQuery?.search as string) ?? '')
const {
isLoading,
isFetchingNextPage,
hasNextPage,
isFetching,
fetchNextPage,
data,
} = useObituaries({ query })

return (
<AdminProvider isAdmin>
<Flex backgroundColor="gray.300">
<Contained
display="flex"
justifyContent="center"
height={32}
py={[2, 4, 6]}
alignItems="center"
>
<SearchInput
title={t('search')}
value={query}
onChange={setQuery}
placeholder={t('search-placeholder')}
/>
</Contained>
</Flex>
<ProgressBar show={isFetching} />
<ObituaryGrid
isLoading={isLoading}
isLoadingNext={isFetchingNextPage}
obituaries={data.pages.flatMap((page) => page.data)}
hasMore={hasNextPage}
onLoadMore={fetchNextPage}
/>
</AdminProvider>
)
}

export const getServerSideProps: GetServerSideProps<Props> = async ({
locale,
query,
req,
res,
}) => {
if (
req.cookies?.['admin-session'] === 'true' ||
query?.secret === process.env.ADMIN_SECRET
) {
res.setHeader(
'Set-Cookie',
cookie.serialize('admin-session', 'true', {
httpOnly: true,
secure: true,
expires: new Date(Date.now() + 1000 * 15 * 60),
path: '/',
sameSite: 'strict',
})
)
return {
props: {
...(await serverSideTranslations(locale, ['common'])),
},
}
}
return {
notFound: true,
}
}
34 changes: 34 additions & 0 deletions pages/api/logout.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import cookie from 'cookie'
import { NextApiRequest, NextApiResponse } from 'next'

export default async (
req: NextApiRequest,
res: NextApiResponse
): Promise<void> => {
switch (req.method) {
case 'GET': {
try {
res.setHeader(
'Set-Cookie',
cookie.serialize('admin-session', '', {
httpOnly: true,
secure: true,
sameSite: 'strict',
path: '/',
expires: new Date(0),
})
)
res.status(200).end()
return
} catch (err) {
console.error(err)
res.status(400).end()
return
}
}
default: {
res.status(404).end()
return
}
}
}
Loading

1 comment on commit fef438c

@vercel
Copy link

@vercel vercel bot commented on fef438c Jan 22, 2024

Choose a reason for hiding this comment

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

Please sign in to comment.