diff --git a/.github/workflows/chromatic.yml b/.github/workflows/chromatic.yml
index 69fdefa3c4..65bce2d3b6 100644
--- a/.github/workflows/chromatic.yml
+++ b/.github/workflows/chromatic.yml
@@ -18,6 +18,7 @@ jobs:
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
DATABASE_URL: ${{ secrets.DATABASE_URL }}
DB_DIRECT_URL: ${{ secrets.DB_DIRECT_URL }}
+ NEXT_PUBLIC_GOOGLE_MAPS_API: ''
CI: true
OVERRIDE_CI: true
FORCE_COLOR: true
@@ -30,29 +31,18 @@ jobs:
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3
with:
fetch-depth: 0
- - name: Install Node.js
- uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3
- with:
- node-version: 18
- uses: pnpm/action-setup@d882d12c64e032187b2edb46d3a0d003b7a43598 # v2.4.0
name: Install pnpm
id: pnpm-install
with:
run_install: false
- - name: Get pnpm store directory
- id: pnpm-cache
- shell: bash
- run: 'echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT'
- - uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3
- name: Setup pnpm cache
+ - name: Install Node.js
+ uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3
with:
- path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
- key: pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
- restore-keys: |
- pnpm-store-
- # - name: Install dependencies
- # run: pnpm install
- # 👇 Runs pnpm in ./packages/ui
+ node-version-file: .nvmrc
+ cache: pnpm
+ cache-dependency-path: pnpm-lock.yaml
+
- name: Install dependencies
run: pnpm install
working-directory: packages/ui
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index da3e2b1963..9202ccfe7e 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -30,30 +30,19 @@ jobs:
git_config_global: true
git_commit_gpgsign: true
- - name: Install Node.js
- uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3
- with:
- node-version: 18
-
- uses: pnpm/action-setup@d882d12c64e032187b2edb46d3a0d003b7a43598 # v2.4.0
name: Install pnpm
id: pnpm-install
with:
run_install: false
+ # standalone: true
- - name: Get pnpm store directory
- id: pnpm-cache
- shell: bash
- run: |
- echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
-
- - uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3
- name: Setup pnpm cache
+ - name: Install Node.js
+ uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3
with:
- path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
- key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
- restore-keys: |
- ${{ runner.os }}-pnpm-store-
+ node-version-file: .nvmrc
+ cache: pnpm
+ cache-dependency-path: pnpm-lock.yaml
- name: Install dependencies
run: pnpm install
@@ -89,7 +78,7 @@ jobs:
with:
message: 'chore: lint & format'
commit: --signoff --no-verify
- committer_name: InReach Bot
+ committer_name: InReach [bot]
committer_email: 108850934+InReach-svc@users.noreply.github.com
- author_name: InReach Bot
+ author_name: InReach [bot]
author_email: 108850934+InReach-svc@users.noreply.github.com
diff --git a/InReach.code-workspace b/InReach.code-workspace
index dfc1c5ebed..d932e9b9bb 100644
--- a/InReach.code-workspace
+++ b/InReach.code-workspace
@@ -137,6 +137,8 @@
"eslint.options": {
"cache": true
},
+ "eslint.ignoreUntitled": true,
+ "eslint.nodeEnv": "development",
"eslint.rules.customizations": [
{
"rule": "import/order",
@@ -160,9 +162,7 @@
},
{ "rule": "sort-imports", "severity": "off" }
],
- "eslint.runtime": "node",
"eslint.useESLintClass": true,
- "eslint.workingDirectories": [{ "pattern": "./packages/*/" }, { "pattern": "./apps/*/" }],
"explorer.decorations.badges": true,
"explorer.expandSingleFolderWorkspaces": false,
"explorer.fileNesting.enabled": true,
diff --git a/apps/app/package.json b/apps/app/package.json
index ff53a38e80..d6afb8c11b 100644
--- a/apps/app/package.json
+++ b/apps/app/package.json
@@ -96,6 +96,7 @@
"just-compact": "3.2.0",
"just-compare": "2.3.0",
"luxon": "3.4.0",
+ "mantine-react-table": "1.1.2",
"next": "13.4.18",
"next-auth": "4.23.1",
"next-i18next": "14.0.0",
diff --git a/apps/app/public/locales/en/common.json b/apps/app/public/locales/en/common.json
index 097037af9b..6f761ecd85 100644
--- a/apps/app/public/locales/en/common.json
+++ b/apps/app/public/locales/en/common.json
@@ -84,6 +84,9 @@
"enter-password-placeholder": "Enter password...",
"enter-review": "Enter your review...",
"errors": {
+ "401-title": "You must be logged in to do that.",
+ "403-body": "You do not have permission to access this page. If you feel that you have reached this page in error, please contact your supervisor.",
+ "403-title": "403: Forbidden",
"404-body": "We're sorry, the page you're looking for doesn't exist or has been moved. Start a search below to find safe, verified resources for the diverse LGBTQ+ community in your area.",
"404-title": "404: Page not found.",
"500-body": "We're sorry, something went wrong with our server. Please try again later, or start a search below to find safe, verified LGBTQ+ resources in your area.",
@@ -368,6 +371,7 @@
"visit": "Visit",
"website_one": "Website",
"website_other": "Websites",
+ "welcome-name": "Welcome, {{name}}!",
"words": {
"accept": "Accept",
"account": "Account",
diff --git a/apps/app/src/pages/401.tsx b/apps/app/src/pages/401.tsx
new file mode 100644
index 0000000000..77ddde81df
--- /dev/null
+++ b/apps/app/src/pages/401.tsx
@@ -0,0 +1,52 @@
+import { Container, rem, Stack, Title } from '@mantine/core'
+import { type GetStaticProps } from 'next'
+import { useRouter } from 'next/router'
+import { useTranslation } from 'next-i18next'
+import { type Route } from 'nextjs-routes'
+import { z } from 'zod'
+
+import { getServerSideTranslations } from '~app/utils/i18n'
+import { LoginBody } from '~ui/modals/Login'
+
+const RouteSchema = z.object({
+ pathname: z.string(),
+ query: z.record(z.string()).optional(),
+ locale: z.string().optional(),
+})
+
+const Unauthorized = () => {
+ const { t } = useTranslation('common')
+ const router = useRouter()
+ const callback =
+ typeof router.query.callbackUrl === 'string'
+ ? RouteSchema.safeParse(JSON.parse(Buffer.from(router.query.callbackUrl, 'base64').toString('utf-8')))
+ : undefined
+
+ return (
+
+
+
+ {/* eslint-disable-next-line i18next/no-literal-string */}
+ 🔐
+ {t('errors.401-title')}
+
+
+
+
+ )
+}
+
+export const getStaticProps: GetStaticProps = async ({ locale }) => {
+ return {
+ props: {
+ ...(await getServerSideTranslations(locale, ['common'])),
+ },
+ revalidate: 60 * 60 * 24 * 7,
+ }
+}
+
+export default Unauthorized
diff --git a/apps/app/src/pages/403.tsx b/apps/app/src/pages/403.tsx
new file mode 100644
index 0000000000..40443c403d
--- /dev/null
+++ b/apps/app/src/pages/403.tsx
@@ -0,0 +1,37 @@
+import { Container, rem, Stack, Text, Title } from '@mantine/core'
+import { type GetStaticProps } from 'next'
+import { useTranslation } from 'next-i18next'
+
+import { getServerSideTranslations } from '~app/utils/i18n'
+
+const Forbidden = () => {
+ const { t } = useTranslation('common')
+
+ return (
+
+
+
+ {/* eslint-disable-next-line i18next/no-literal-string */}
+ ⛔️
+ {t('errors.403-title')}
+
+ {t('errors.403-body')}
+
+
+ )
+}
+
+export const getStaticProps: GetStaticProps = async ({ locale }) => {
+ return {
+ props: {
+ ...(await getServerSideTranslations(locale, ['common'])),
+ },
+ revalidate: 60 * 60 * 24 * 7,
+ }
+}
+
+export default Forbidden
diff --git a/apps/app/src/pages/admin/index.tsx b/apps/app/src/pages/admin/index.tsx
new file mode 100644
index 0000000000..37563e4d4c
--- /dev/null
+++ b/apps/app/src/pages/admin/index.tsx
@@ -0,0 +1,62 @@
+import { Container, Stack, Title } from '@mantine/core'
+import { type GetServerSideProps, type NextPage } from 'next'
+import Head from 'next/head'
+import { useSession } from 'next-auth/react'
+import { useTranslation } from 'next-i18next'
+import { type Route, route } from 'nextjs-routes'
+
+import { checkPermissions, getServerSession } from '@weareinreach/auth'
+import { OrganizationTable } from '@weareinreach/ui/components/data-portal/OrganizationTable'
+import { getServerSideTranslations } from '~app/utils/i18n'
+
+const AdminIndex: NextPage = () => {
+ const { t } = useTranslation(['common'])
+ const { data: session, status } = useSession()
+ return (
+ <>
+
+ {t('page-title.base', { title: 'Data Admin' })}
+
+ {/* */}
+
+ {t('welcome-name', { name: session?.user?.name })}
+
+
+ {/* */}
+ >
+ )
+}
+export default AdminIndex
+
+export const getServerSideProps: GetServerSideProps = async (ctx) => {
+ const session = await getServerSession(ctx)
+ if (!session) {
+ const callbackRoute: Route = {
+ pathname: '/admin',
+ }
+ const callbackUrl = Buffer.from(JSON.stringify(callbackRoute)).toString('base64url')
+ return {
+ redirect: {
+ destination: route({ pathname: '/401', query: { callbackUrl } }),
+ permanent: false,
+ },
+ }
+ }
+ const hasPermissions = checkPermissions({ session, permissions: 'root', has: 'some' })
+
+ if (!hasPermissions) {
+ return {
+ redirect: {
+ destination: '/403',
+ permanent: false,
+ },
+ }
+ }
+
+ return {
+ props: {
+ session,
+ ...(await getServerSideTranslations(ctx.locale, ['common'])),
+ },
+ }
+}
diff --git a/apps/app/src/types/nextjs-routes.d.ts b/apps/app/src/types/nextjs-routes.d.ts
index 371fc7a4e9..209036fa75 100644
--- a/apps/app/src/types/nextjs-routes.d.ts
+++ b/apps/app/src/types/nextjs-routes.d.ts
@@ -11,11 +11,14 @@ declare module "nextjs-routes" {
} from "next";
export type Route =
+ | StaticRoute<"/401">
+ | StaticRoute<"/403">
| StaticRoute<"/404">
| StaticRoute<"/500">
| StaticRoute<"/account">
| StaticRoute<"/account/reviews">
| StaticRoute<"/account/saved">
+ | StaticRoute<"/admin">
| StaticRoute<"/admin/quicklink/email">
| StaticRoute<"/admin/quicklink">
| StaticRoute<"/admin/quicklink/phone">
diff --git a/package.json b/package.json
index 69a8d3eb1c..2ccf930cac 100644
--- a/package.json
+++ b/package.json
@@ -72,6 +72,7 @@
"chokidar": ">=3.0.0",
"csstype": "^3.1.2",
"eslint-plugin-import": "npm:eslint-plugin-i@latest",
+ "eslint-plugin-node": "npm:eslint-plugin-n@latest",
"glob-parent@<5.1.2": "^5.1.2",
"http-cache-semantics@<=4.1.0": "^4.1.1",
"listr2@<5": "^5.0.5",
@@ -91,7 +92,6 @@
},
"patchedDependencies": {
"@crowdin/ota-client@1.0.0": "patches/@crowdin__ota-client@1.0.0.patch",
- "eslint-plugin-node@11.1.0": "patches/eslint-plugin-node@11.1.0.patch",
"iso-google-locales@3.0.4": "patches/iso-google-locales@3.0.4.patch",
"trpc-panel@1.3.4": "patches/trpc-panel@1.3.4.patch"
},
diff --git a/packages/api/router/location/query.forGoogleMaps.handler.ts b/packages/api/router/location/query.forGoogleMaps.handler.ts
index 1890cbbecb..6d33ccfd88 100644
--- a/packages/api/router/location/query.forGoogleMaps.handler.ts
+++ b/packages/api/router/location/query.forGoogleMaps.handler.ts
@@ -44,9 +44,12 @@ export const forGoogleMaps = async ({ input }: TRPCHandlerParams 1 ? getBoundary(coordsForBounds) : null
+ const singleLat = result.at(0)?.latitude
+ const singleLon = result.at(0)?.longitude
+
const center =
- result.length === 1 && result.at(0)?.latitude && result.at(0)?.longitude
- ? ({ lat: result.at(0)!.latitude, lng: result.at(0)!.longitude } as { lat: number; lng: number })
+ result.length === 1 && singleLat && singleLon
+ ? ({ lat: singleLat, lng: singleLon } satisfies google.maps.LatLngLiteral)
: getCenter(coordsForBounds)
const zoom = result.length === 1 ? 17 : null
diff --git a/packages/config/tsconfig/base.json b/packages/config/tsconfig/base.json
index 97519a9275..8b042c2e75 100644
--- a/packages/config/tsconfig/base.json
+++ b/packages/config/tsconfig/base.json
@@ -17,7 +17,7 @@
"resolveJsonModule": true,
"skipLibCheck": true,
"strict": true,
- "target": "es2017"
+ "target": "es2020"
},
"display": "Default",
"include": ["../../@types/**/*.ts"],
diff --git a/packages/eslint-config/base.js b/packages/eslint-config/base.js
index 77928b322c..27e40fa271 100644
--- a/packages/eslint-config/base.js
+++ b/packages/eslint-config/base.js
@@ -1,4 +1,3 @@
-/* eslint-disable import/no-unused-modules */
/** @type {import('eslint').ESLint.ConfigData} */
const config = {
plugins: ['codegen', 'turbo', 'node', /*'import',*/ '@tanstack/query'],
@@ -12,6 +11,10 @@ const config = {
'prettier',
],
rules: {
+ '@typescript-eslint/consistent-type-assertions': [
+ 'error',
+ { assertionStyle: 'as', objectLiteralTypeAssertions: 'allow-as-parameter' },
+ ],
'@typescript-eslint/consistent-type-imports': [
'error',
{
@@ -32,7 +35,11 @@ const config = {
],
'@typescript-eslint/no-empty-function': 'off',
'no-duplicate-imports': 'off',
+ 'node/no-deprecated-api': 'error',
'node/no-process-env': 'warn',
+ 'node/no-unsupported-features/es-builtins': 'error',
+ 'node/no-unsupported-features/es-syntax': 'error',
+ 'node/no-unsupported-features/node-builtins': 'error',
'codegen/codegen': 'error',
'react/jsx-key': 'off',
'react/no-unescaped-entities': 'off',
@@ -103,21 +110,28 @@ const config = {
],
parser: '@typescript-eslint/parser',
parserOptions: {
- project: ['./packages/*/tsconfig.json', './apps/*/tsconfig.json', './tsconfig.json'],
+ EXPERIMENTAL_useProjectService: true,
+ // project: [ './packages/*/tsconfig.json', './apps/*/tsconfig.json', './tsconfig.json' ],
+ emitDecoratorMetadata: true,
+ ecmaVersion: 2020,
},
ignorePatterns: ['!.*', 'node_modules', 'dist/', '.next/'],
settings: {
+ 'import/cache': {
+ lifetime: 60,
+ },
'import/extensions': ['.js', '.jsx', '.cjs', '.mjs', '.ts', '.mts', '.tsx'],
+ 'import/internal-regex': '^(?:(?:@weareinreach\\/)|(?:~\\w*\\/)).*',
+ 'import/parsers': {
+ '@typescript-eslint/parser': ['.ts', '.tsx', '.mts'],
+ },
'import/resolver': {
node: true,
typescript: {
alwaysTryTypes: true,
+ project: ['./packages/*/tsconfig.json', './apps/*/tsconfig.json', './tsconfig.json'],
},
},
- 'import/cache': {
- lifetime: 10,
- },
- 'import/internal-regex': '^(?:(?:@weareinreach\\/)|(?:~\\w*\\/)).*',
},
env: {
node: true,
diff --git a/packages/ui/.storybook/i18next.ts b/packages/ui/.storybook/i18next.ts
index d33102dd39..1a4f8e1dfb 100644
--- a/packages/ui/.storybook/i18next.ts
+++ b/packages/ui/.storybook/i18next.ts
@@ -36,7 +36,7 @@ i18n
interpolation: {
escapeValue: true,
skipOnVariables: false,
- format: (value, format, lng, edit) => {
+ format: (value, format) => {
switch (format) {
case 'lowercase': {
if (typeof value === 'string') return value.toLowerCase()
diff --git a/packages/ui/components/core/SearchBox.stories.tsx b/packages/ui/components/core/SearchBox.stories.tsx
index 6ed272e5bd..09111112df 100644
--- a/packages/ui/components/core/SearchBox.stories.tsx
+++ b/packages/ui/components/core/SearchBox.stories.tsx
@@ -49,7 +49,7 @@ export default {
),
-} as Meta
+} satisfies Meta
type StoryDef = StoryObj
export const ByLocation = {
diff --git a/packages/ui/components/core/SocialLink.stories.tsx b/packages/ui/components/core/SocialLink.stories.tsx
index e8f4d0fcea..f341320cfb 100644
--- a/packages/ui/components/core/SocialLink.stories.tsx
+++ b/packages/ui/components/core/SocialLink.stories.tsx
@@ -17,7 +17,7 @@ export default {
type: 'string',
},
},
-} as Meta
+} satisfies Meta
type StoryDef = StoryObj
type StoryGroupDef = StoryObj
diff --git a/packages/ui/components/core/UserMenu.stories.tsx b/packages/ui/components/core/UserMenu.stories.tsx
index b70fb44cf7..b77230f62a 100644
--- a/packages/ui/components/core/UserMenu.stories.tsx
+++ b/packages/ui/components/core/UserMenu.stories.tsx
@@ -5,7 +5,7 @@ import { UserMenu as UserMenuComponent } from '.'
export default {
title: 'Sections/Navbar/User Menu',
component: UserMenuComponent,
-} as Meta
+} satisfies Meta
export const LoggedOut = {
parameters: {
diff --git a/packages/ui/components/data-portal/OrganizationTable.tsx b/packages/ui/components/data-portal/OrganizationTable.tsx
index 73790973a1..3b833479da 100644
--- a/packages/ui/components/data-portal/OrganizationTable.tsx
+++ b/packages/ui/components/data-portal/OrganizationTable.tsx
@@ -5,13 +5,17 @@ import {
type MRT_ColumnDef,
type MRT_ColumnFilterFnsState,
type MRT_ColumnFiltersState,
+ type MRT_Row,
type MRT_SortingState,
+ type MRT_TableInstance,
type MRT_Virtualizer,
useMantineReactTable,
} from 'mantine-react-table'
-import { useRouter } from 'next/router'
+import { type Route } from 'nextjs-routes'
import { type Dispatch, type SetStateAction, useMemo, useRef, useState } from 'react'
+import { type ApiOutput } from '@weareinreach/api'
+import { Link } from '~ui/components/core/Link'
import { Icon } from '~ui/icon'
import { trpc as api } from '~ui/lib/trpcClient'
@@ -27,6 +31,25 @@ const useStyles = createStyles((theme) => ({
},
}))
+const getAlertBanner = ({
+ isError,
+ isFetching,
+ isLoading,
+}: Record<'isError' | 'isFetching' | 'isLoading', boolean>) => {
+ switch (true) {
+ case isError: {
+ return { color: 'red', children: 'Error fetching data' }
+ }
+ case isFetching:
+ case isLoading: {
+ return { color: 'green', children: 'Loading data' }
+ }
+ default: {
+ return { color: 'white', children: null, sx: { backgroundColor: 'transparent' } }
+ }
+ }
+}
+
const ToolbarButtons = ({ columnFilters, setColumnFilters }: ToolbarButtonsProps) => {
const theme = useMantineTheme()
const toggle = (key: 'published' | 'deleted') => {
@@ -101,15 +124,77 @@ const ToolbarButtons = ({ columnFilters, setColumnFilters }: ToolbarButtonsProps
)
}
+const BottomBar = ({ table }: BottomBarProps) => {
+ const { classes } = useStyles()
+ const filteredRowCount = table.getFilteredRowModel().rows.length
+ const preFilteredRowCount = table.getPreFilteredRowModel().rows.length
+
+ if (preFilteredRowCount !== filteredRowCount) {
+ return (
+
+
+ Showing {filteredRowCount} of {preFilteredRowCount} results
+
+
+ )
+ }
+
+ return (
+
+ {preFilteredRowCount} results
+
+ )
+}
+
+const RowAction = ({ row }: RowActionProps) => {
+ const getViewUrl = (): Route => {
+ const parent = row.getParentRow()
+ if (parent) {
+ return {
+ pathname: '/org/[slug]/[orgLocationId]',
+ query: { slug: parent.original.slug, orgLocationId: row.original.id },
+ }
+ } else {
+ return { pathname: '/org/[slug]', query: { slug: row.original.slug } }
+ }
+ }
+ const getEditUrl = (): Route => {
+ const parent = row.getParentRow()
+ if (parent) {
+ return {
+ pathname: '/org/[slug]/[orgLocationId]/edit',
+ query: { slug: parent.original.slug, orgLocationId: row.original.id },
+ }
+ } else {
+ return { pathname: '/org/[slug]/edit', query: { slug: row.original.slug } }
+ }
+ }
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
export const OrganizationTable = () => {
const { classes } = useStyles()
- const router = useRouter()
const { data, isLoading, isError, isFetching } = api.organization.forOrganizationTable.useQuery(undefined, {
placeholderData: [],
select: (data) => data.map(({ locations, ...rest }) => ({ ...rest, subRows: locations })),
+ refetchOnWindowFocus: false,
})
- const columns = useMemo[number]>[]>(
+ // #region Column Definitions
+ const columns = useMemo[]>(
() => [
{
accessorKey: 'name',
@@ -118,6 +203,15 @@ export const OrganizationTable = () => {
filterVariant: 'autocomplete',
enableResizing: true,
minSize: 250,
+ enableColumnFilter: false,
+ Cell: ({ cell, row }) =>
+ row.original.published ? (
+ cell.getValue()
+ ) : (
+
+ {cell.getValue()}
+
+ ),
},
{
accessorKey: 'lastVerified',
@@ -200,7 +294,9 @@ export const OrganizationTable = () => {
// eslint-disable-next-line react-hooks/exhaustive-deps
[]
)
+ // #endregion
+ // #region State
const [columnFilters, setColumnFilters] = useState([
{ id: 'deleted', value: false },
])
@@ -220,22 +316,9 @@ export const OrganizationTable = () => {
{ id: 'name', desc: false },
])
const rowVirtualizerInstanceRef = useRef>(null)
+ // #endregion
- const getAlertBanner = () => {
- switch (true) {
- case isError: {
- return { color: 'red', children: 'Error fetching data' }
- }
- case isFetching:
- case isLoading: {
- return { color: 'green', children: 'Loading data' }
- }
- default: {
- return { color: 'white', children: null, sx: { backgroundColor: 'transparent' } }
- }
- }
- }
-
+ // #region Table Setup
const table = useMantineReactTable({
// #region Basic Props
columns,
@@ -258,10 +341,11 @@ export const OrganizationTable = () => {
enableHiding: true,
getRowId: (originalRow) => originalRow.id,
isMultiSortEvent: () => true,
+ maxLeafRowFilterDepth: 0,
positionGlobalFilter: 'left',
rowCount: data?.length ?? 0,
rowVirtualizerInstanceRef,
- rowVirtualizerProps: { overscan: 5 },
+ rowVirtualizerProps: { overscan: 10, estimateSize: () => 56 },
// #endregion
// #region State
initialState: {
@@ -278,7 +362,7 @@ export const OrganizationTable = () => {
columnFilters,
globalFilter,
isLoading,
- showAlertBanner: getAlertBanner !== undefined,
+ showAlertBanner: isError || isFetching || isLoading,
showProgressBars: isFetching,
sorting,
density: 'xs',
@@ -288,91 +372,22 @@ export const OrganizationTable = () => {
mantinePaperProps: { miw: '85%' },
mantineProgressProps: ({ isTopToolbar }) => ({ style: { display: isTopToolbar ? 'block' : 'none' } }),
mantineSelectCheckboxProps: ({ row }) => ({ style: { display: row.getCanSelect() ? 'block' : 'none' } }),
+ mantineTableBodyProps: { mah: '60vh' },
mantineTableBodyCellProps: ({ row }) => ({
sx: (theme) => ({
textDecoration: row.original.deleted ? 'line-through' : 'none',
color: row.original.published ? undefined : theme.other.colors.secondary.darkGray,
}),
}),
- mantineToolbarAlertBannerProps: getAlertBanner(),
+ mantineToolbarAlertBannerProps: getAlertBanner({ isLoading, isFetching, isError }),
mantineTableProps: { striped: true },
// #endregion
// #region Override sections
renderToolbarInternalActions: () => (
),
- renderBottomToolbar: ({ table }) => {
- if (table.getPreFilteredRowModel().rows.length !== table.getFilteredRowModel().rows.length) {
- return (
-
-
- Showing {table.getFilteredRowModel().rows.length} of{' '}
- {table.getPreFilteredRowModel().rows.length} results
-
-
- )
- }
- return (
-
- {table.getFilteredRowModel().rows.length} results
-
- )
- },
- renderRowActions: ({ row }) => {
- const handleView = () => {
- const parent = row.getParentRow()
- if (parent) {
- router.push({
- pathname: '/org/[slug]/[orgLocationId]',
- query: {
- slug: parent.original.slug,
- orgLocationId: row.original.id,
- },
- })
- } else {
- router.push({
- pathname: '/org/[slug]',
- query: {
- slug: row.original.slug,
- },
- })
- }
- }
- const handleEdit = () => {
- const parent = row.getParentRow()
- if (parent) {
- router.push({
- pathname: '/org/[slug]/[orgLocationId]/edit',
- query: {
- slug: parent.original.slug,
- orgLocationId: row.original.id,
- },
- })
- } else {
- router.push({
- pathname: '/org/[slug]/edit',
- query: {
- slug: row.original.slug,
- },
- })
- }
- }
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
- )
- },
+ renderBottomToolbar: ({ table }) => ,
+ renderRowActions: ({ row }) => ,
// #endregion
// #region Events
onColumnFilterFnsChange: setColumnFilterFns,
@@ -381,6 +396,7 @@ export const OrganizationTable = () => {
onSortingChange: setSorting,
// #endregion
})
+ // #endregion
return
}
@@ -389,3 +405,12 @@ interface ToolbarButtonsProps {
columnFilters: MRT_ColumnFiltersState
setColumnFilters: Dispatch>
}
+interface BottomBarProps {
+ table: MRT_TableInstance
+}
+interface RowActionProps {
+ row: MRT_Row
+}
+type RestucturedDataItem = Omit & {
+ subRows: ApiOutput['organization']['forOrganizationTable'][number]['locations']
+}
diff --git a/packages/ui/lib/trpcClient.ts b/packages/ui/lib/trpcClient.ts
index 14e87a4f55..995844a737 100644
--- a/packages/ui/lib/trpcClient.ts
+++ b/packages/ui/lib/trpcClient.ts
@@ -1,6 +1,10 @@
/* eslint-disable turbo/no-undeclared-env-vars */
/* eslint-disable node/no-process-env */
-import { httpBatchLink, loggerLink } from '@trpc/client'
+import {
+ // httpBatchLink,
+ unstable_httpBatchStreamLink as httpBatchStreamLink,
+ loggerLink,
+} from '@trpc/client'
import { createTRPCNext } from '@trpc/next'
import { createTRPCReact } from '@trpc/react-query'
import { devtoolsLink } from 'trpc-client-devtools-link'
@@ -12,7 +16,7 @@ import { getEnv } from '@weareinreach/env'
export const getBaseUrl = () => {
if (typeof window !== 'undefined') return '' // browser should use relative url
if (getEnv('VERCEL_URL')) return `https://${getEnv('VERCEL_URL')}` // SSR should use vercel url
- return `http://localhost:${getEnv('PORT') ?? 6006}` // dev SSR should use localhost
+ return `http://localhost:${getEnv('PORT') ?? process.env.STORYBOOK ? 6006 : 3000}` // dev SSR should use localhost
}
export const nextTRPC = () =>
@@ -22,18 +26,19 @@ export const nextTRPC = () =>
transformer,
links: [
devtoolsLink({
- // eslint-disable-next-line node/no-process-env
enabled: process.env.NODE_ENV === 'development',
}),
loggerLink({
enabled: (opts) =>
- // eslint-disable-next-line node/no-process-env
process.env.NODE_ENV === 'development' ||
(opts.direction === 'down' && opts.result instanceof Error),
}),
- httpBatchLink({
+ httpBatchStreamLink({
url: `${getBaseUrl()}/api/trpc`,
}),
+ // httpBatchLink({
+ // url: `${getBaseUrl()}/api/trpc`,
+ // }),
],
queryClientConfig: {
defaultOptions: {
diff --git a/packages/ui/modals/Login.stories.tsx b/packages/ui/modals/Login.stories.tsx
index 7e742c09cb..e3430c924d 100644
--- a/packages/ui/modals/Login.stories.tsx
+++ b/packages/ui/modals/Login.stories.tsx
@@ -1,9 +1,9 @@
-import { type Meta } from '@storybook/react'
+import { type Meta, type StoryObj } from '@storybook/react'
import { Button } from '~ui/components/core/Button'
import { cognito, csrf, providers, signin } from '~ui/mockData/login'
-import { LoginModalLauncher } from './Login'
+import { LoginBody, LoginModalLauncher } from './Login'
export default {
title: 'Modals/Login',
@@ -26,3 +26,9 @@ export default {
} satisfies Meta
export const Modal = {}
+export const BodyOnly = {
+ parameters: {
+ layoutWrapper: 'centeredFullscreen',
+ },
+ render: () => ,
+} satisfies StoryObj
diff --git a/packages/ui/modals/Login.tsx b/packages/ui/modals/Login.tsx
index 41f1c2e717..22c6e1896f 100644
--- a/packages/ui/modals/Login.tsx
+++ b/packages/ui/modals/Login.tsx
@@ -12,8 +12,10 @@ import {
} from '@mantine/core'
import { useForm, zodResolver } from '@mantine/form'
import { useDisclosure } from '@mantine/hooks'
+import { useRouter } from 'next/router'
import { signIn } from 'next-auth/react'
import { Trans, useTranslation } from 'next-i18next'
+import { type Route } from 'nextjs-routes'
import { forwardRef, useState } from 'react'
import { z } from 'zod'
@@ -26,43 +28,119 @@ import { ModalTitle } from './ModalTitle'
import { PrivacyStatementModal } from './PrivacyStatement'
import { SignupModalLauncher } from './SignUp'
+interface LoginBodyProps {
+ activateShake?: () => void
+ modalHandler?: {
+ readonly open: () => void
+ readonly close: () => void
+ readonly toggle: () => void
+ }
+ hideTitle?: boolean
+ callbackUrl?: Route
+}
+export const LoginBody = forwardRef(
+ ({ activateShake, modalHandler, hideTitle, callbackUrl }, ref) => {
+ const [isLoading, setLoading] = useState(false)
+ const variants = useCustomVariant()
+ const { t } = useTranslation(['common'])
+ const router = useRouter()
+ const loginErrors = new Map([[401, t('login.error-username-password')]])
+ const LoginSchema = z.object({
+ email: z.string().email({ message: t('form-error-enter-valid-email') as string }),
+ password: z.string().min(1, t('form-error-password-blank') as string),
+ })
+ const form = useForm({
+ validate: zodResolver(LoginSchema),
+ validateInputOnBlur: true,
+ })
+ const loginHandle = async (email: string, password: string) => {
+ try {
+ setLoading(true)
+ if (!form.isValid()) return
+ const result = await signIn('cognito', { email, password, redirect: false })
+ if (result?.error) {
+ const message = loginErrors.get(result.status)
+ form.setFieldError('password', message ?? t('login.error-generic'))
+ if (typeof activateShake === 'function') {
+ activateShake()
+ }
+ }
+ if (result?.ok) {
+ if (modalHandler) {
+ modalHandler.close()
+ } else if (callbackUrl) {
+ router.push(callbackUrl)
+ }
+ }
+ } finally {
+ setLoading(false)
+ }
+ }
+ return (
+
+ {hideTitle ? null : {t('log-in')}}
+
+
+
+
+
+ Privacy Policy
+
+ ),
+ link2: (
+
+ Terms of Use
+
+ ),
+ }}
+ />
+
+
+ {t('forgot-password')}
+ {t('dont-have-account')}
+
+
+ )
+ }
+)
+LoginBody.displayName = 'LoginBody'
+
export const LoginModalBody = forwardRef((props, ref) => {
- const { t } = useTranslation(['common'])
const [opened, handler] = useDisclosure(false)
const { animateCSS, fireEvent } = useShake({ variant: 1 })
- const [isLoading, setLoading] = useState(false)
const { isMobile } = useScreenSize()
- const loginErrors = new Map([[401, t('login.error-username-password')]])
-
const modalTitle = handler.close() }} />
-
- const LoginSchema = z.object({
- email: z.string().email({ message: t('form-error-enter-valid-email') as string }),
- password: z.string().min(1, t('form-error-password-blank') as string),
- })
- const form = useForm({
- validate: zodResolver(LoginSchema),
- validateInputOnBlur: true,
- })
- const variants = useCustomVariant()
- const loginHandle = async (email: string, password: string) => {
- try {
- setLoading(true)
- if (!form.isValid()) return
- const result = await signIn('cognito', { email, password, redirect: false })
- if (result?.error) {
- const message = loginErrors.get(result.status)
- form.setFieldError('password', message ?? t('login.error-generic'))
- fireEvent()
- }
- if (result?.ok) {
- handler.close()
- }
- } finally {
- setLoading(false)
- }
- }
-
return (
<>
className={animateCSS}
fullScreen={isMobile}
>
-
- {t('log-in')}
-
-
-
-
-
- Privacy Policy
-
- ),
- link2: (
-
- Terms of Use
-
- ),
- }}
- />
-
-
- {t('forgot-password')}
- {t('dont-have-account')}
-
-
+
handler.open()} {...props} />
>
diff --git a/packages/ui/modals/MoreFilter.stories.tsx b/packages/ui/modals/MoreFilter.stories.tsx
index e9bf4505ab..13dcdcf676 100644
--- a/packages/ui/modals/MoreFilter.stories.tsx
+++ b/packages/ui/modals/MoreFilter.stories.tsx
@@ -35,6 +35,6 @@ export default {
}, [filter])
return
},
-} as Meta
+} satisfies Meta
export const MoreFilterExample = {}
diff --git a/packages/ui/modals/ServiceFilter.stories.tsx b/packages/ui/modals/ServiceFilter.stories.tsx
index acdcefc75f..083b776fcc 100644
--- a/packages/ui/modals/ServiceFilter.stories.tsx
+++ b/packages/ui/modals/ServiceFilter.stories.tsx
@@ -39,6 +39,6 @@ export default {
return
},
-} as Meta
+} satisfies Meta
export const ServiceFilterExample = {}
diff --git a/packages/ui/theme/colors.ts b/packages/ui/theme/colors.ts
index acbfa541bf..0f90779dc7 100644
--- a/packages/ui/theme/colors.ts
+++ b/packages/ui/theme/colors.ts
@@ -287,7 +287,7 @@ export const customColors = {
'#136776',
'#0e4b56',
],
-} as DefineColors
+} satisfies DefineColors
/** Merge custom color names with Mantine's presets */
type ExtendedCustomColors = CustomColors | DefaultMantineColor
diff --git a/patches/eslint-plugin-node@11.1.0.patch b/patches/eslint-plugin-node@11.1.0.patch
deleted file mode 100644
index 89d0492f73..0000000000
--- a/patches/eslint-plugin-node@11.1.0.patch
+++ /dev/null
@@ -1,81 +0,0 @@
-diff --git a/lib/rules/no-process-env.js b/lib/rules/no-process-env.js
-index f46f00ec06b6bc870588579a5a8a7bd82c06eaea..5226a1e44c805b7542c70c592dd076d5ab53f251 100644
---- a/lib/rules/no-process-env.js
-+++ b/lib/rules/no-process-env.js
-@@ -2,44 +2,44 @@
- * @author Vignesh Anand
- * See LICENSE file in root directory for full license.
- */
--"use strict"
-+'use strict'
-
- //------------------------------------------------------------------------------
- // Rule Definition
- //------------------------------------------------------------------------------
-
- module.exports = {
-- meta: {
-- type: "suggestion",
-- docs: {
-- description: "disallow the use of `process.env`",
-- category: "Stylistic Issues",
-- recommended: false,
-- url:
-- "https://github.com/mysticatea/eslint-plugin-node/blob/v11.1.0/docs/rules/no-process-env.md",
-- },
-- fixable: null,
-- schema: [],
-- messages: {
-- unexpectedProcessEnv: "Unexpected use of process.env.",
-- },
-- },
-+ meta: {
-+ type: 'suggestion',
-+ docs: {
-+ description: 'disallow the use of `process.env`',
-+ category: 'Stylistic Issues',
-+ recommended: false,
-+ url: 'https://github.com/mysticatea/eslint-plugin-node/blob/v11.1.0/docs/rules/no-process-env.md',
-+ },
-+ fixable: null,
-+ schema: [],
-+ messages: {
-+ unexpectedProcessEnv:
-+ "Do not use 'process.env' - use the 'env' object from @weareinreach/config.",
-+ },
-+ },
-
-- create(context) {
-- return {
-- MemberExpression(node) {
-- const objectName = node.object.name
-- const propertyName = node.property.name
-+ create(context) {
-+ return {
-+ MemberExpression(node) {
-+ const objectName = node.object.name
-+ const propertyName = node.property.name
-
-- if (
-- objectName === "process" &&
-- !node.computed &&
-- propertyName &&
-- propertyName === "env"
-- ) {
-- context.report({ node, messageId: "unexpectedProcessEnv" })
-- }
-- },
-- }
-- },
-+ if (
-+ objectName === 'process' &&
-+ !node.computed &&
-+ propertyName &&
-+ propertyName === 'env'
-+ ) {
-+ context.report({ node, messageId: 'unexpectedProcessEnv' })
-+ }
-+ },
-+ }
-+ },
- }
\ No newline at end of file
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index b93ffe6ed4..19d7bf56b4 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -11,6 +11,7 @@ overrides:
chokidar: '>=3.0.0'
csstype: ^3.1.2
eslint-plugin-import: npm:eslint-plugin-i@latest
+ eslint-plugin-node: npm:eslint-plugin-n@latest
glob-parent@<5.1.2: ^5.1.2
http-cache-semantics@<=4.1.0: ^4.1.1
listr2@<5: ^5.0.5
@@ -32,9 +33,6 @@ patchedDependencies:
'@crowdin/ota-client@1.0.0':
hash: refrge56ym5gomc3tkglzjdymy
path: patches/@crowdin__ota-client@1.0.0.patch
- eslint-plugin-node@11.1.0:
- hash: 45p4dc3r2kwi3h2jyimmny42ju
- path: patches/eslint-plugin-node@11.1.0.patch
iso-google-locales@3.0.4:
hash: ltnamflm7ayajalculwqyezjya
path: patches/iso-google-locales@3.0.4.patch
@@ -328,6 +326,9 @@ importers:
luxon:
specifier: 3.4.0
version: 3.4.0
+ mantine-react-table:
+ specifier: 1.1.2
+ version: 1.1.2(@emotion/react@11.11.1)(@mantine/core@6.0.19)(@mantine/dates@6.0.19)(@mantine/hooks@6.0.19)(@tabler/icons-react@2.30.0)(react-dom@18.2.0)(react@18.2.0)
next:
specifier: 13.4.18
version: 13.4.18(@opentelemetry/api@1.4.1)(react-dom@18.2.0)(react@18.2.0)
@@ -1343,8 +1344,8 @@ importers:
specifier: npm:eslint-plugin-i@latest
version: /eslint-plugin-i@2.28.0-2(@typescript-eslint/parser@6.4.0)(eslint-import-resolver-typescript@3.6.0)(eslint@8.47.0)
eslint-plugin-node:
- specifier: 11.1.0
- version: 11.1.0(patch_hash=45p4dc3r2kwi3h2jyimmny42ju)(eslint@8.47.0)
+ specifier: npm:eslint-plugin-n@latest
+ version: /eslint-plugin-n@16.0.1(eslint@8.47.0)
eslint-plugin-react:
specifier: 7.33.2
version: 7.33.2(eslint@8.47.0)
@@ -10011,7 +10012,6 @@ packages:
engines: {node: '>=12'}
dependencies:
remove-accents: 0.4.2
- dev: true
/@tanstack/query-core@4.32.6:
resolution: {integrity: sha512-YVB+mVWENQwPyv+40qO7flMgKZ0uI41Ph7qXC2Zf1ft5AIGfnXnMZyifB2ghhZ27u+5wm5mlzO4Y6lwwadzxCA==}
@@ -10089,7 +10089,6 @@ packages:
dependencies:
'@tanstack/virtual-core': 3.0.0-beta.54
react: 18.2.0
- dev: true
/@tanstack/table-core@8.9.3:
resolution: {integrity: sha512-NpHZBoHTfqyJk0m/s/+CSuAiwtebhYK90mDuf5eylTvgViNOujiaOaxNDxJkQQAsVvHWZftUGAx1EfO1rkKtLg==}
@@ -10097,7 +10096,6 @@ packages:
/@tanstack/virtual-core@3.0.0-beta.54:
resolution: {integrity: sha512-jtkwqdP2rY2iCCDVAFuaNBH3fiEi29aTn2RhtIoky8DTTiCdc48plpHHreLwmv1PICJ4AJUUESaq3xa8fZH8+g==}
- dev: true
/@terraformer/wkt@2.2.0:
resolution: {integrity: sha512-i33rTSqPtmO4sRdeznI0IEc9gpIZZIXN5kGhZ4rTwVtDccDKL3h4uia9cmWdRJlJMlG4Febxatw5b9ylI5YYuA==}
@@ -12653,7 +12651,6 @@ packages:
resolution: {integrity: sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==}
dependencies:
semver: 7.5.4
- dev: false
/bundle-name@3.0.0:
resolution: {integrity: sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==}
@@ -14664,15 +14661,15 @@ packages:
- supports-color
dev: true
- /eslint-plugin-es@3.0.1(eslint@8.47.0):
- resolution: {integrity: sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==}
- engines: {node: '>=8.10.0'}
+ /eslint-plugin-es-x@7.2.0(eslint@8.47.0):
+ resolution: {integrity: sha512-9dvv5CcvNjSJPqnS5uZkqb3xmbeqRLnvXKK7iI5+oK/yTusyc46zbBZKENGsOfojm/mKfszyZb+wNqNPAPeGXA==}
+ engines: {node: ^14.18.0 || >=16.0.0}
peerDependencies:
- eslint: '>=4.19.1'
+ eslint: '>=8'
dependencies:
+ '@eslint-community/eslint-utils': 4.4.0(eslint@8.47.0)
+ '@eslint-community/regexpp': 4.6.2
eslint: 8.47.0
- eslint-utils: 2.1.0
- regexpp: 3.2.0
dev: true
/eslint-plugin-i18next@6.0.3:
@@ -14731,21 +14728,22 @@ packages:
semver: 6.3.1
dev: true
- /eslint-plugin-node@11.1.0(patch_hash=45p4dc3r2kwi3h2jyimmny42ju)(eslint@8.47.0):
- resolution: {integrity: sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==}
- engines: {node: '>=8.10.0'}
+ /eslint-plugin-n@16.0.1(eslint@8.47.0):
+ resolution: {integrity: sha512-CDmHegJN0OF3L5cz5tATH84RPQm9kG+Yx39wIqIwPR2C0uhBGMWfbbOtetR83PQjjidA5aXMu+LEFw1jaSwvTA==}
+ engines: {node: '>=16.0.0'}
peerDependencies:
- eslint: '>=5.16.0'
+ eslint: '>=7.0.0'
dependencies:
+ '@eslint-community/eslint-utils': 4.4.0(eslint@8.47.0)
+ builtins: 5.0.1
eslint: 8.47.0
- eslint-plugin-es: 3.0.1(eslint@8.47.0)
- eslint-utils: 2.1.0
+ eslint-plugin-es-x: 7.2.0(eslint@8.47.0)
ignore: 5.2.4
+ is-core-module: 2.13.0
minimatch: 3.1.2
resolve: 1.22.4
- semver: 6.3.1
+ semver: 7.5.4
dev: true
- patched: true
/eslint-plugin-react-hooks@5.0.0-canary-7118f5dd7-20230705(eslint@8.47.0):
resolution: {integrity: sha512-AZYbMo/NW9chdL7vk6HQzQhT+PvTAEVqWk9ziruUoW2kAOcN5qNyelv70e0F1VNQAbvutOC9oc+xfWycI9FxDw==}
@@ -14826,18 +14824,6 @@ packages:
estraverse: 5.3.0
dev: true
- /eslint-utils@2.1.0:
- resolution: {integrity: sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==}
- engines: {node: '>=6'}
- dependencies:
- eslint-visitor-keys: 1.3.0
- dev: true
-
- /eslint-visitor-keys@1.3.0:
- resolution: {integrity: sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==}
- engines: {node: '>=4'}
- dev: true
-
/eslint-visitor-keys@3.4.3:
resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -18681,7 +18667,6 @@ packages:
'@tanstack/react-virtual': 3.0.0-beta.54(react@18.2.0)
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
- dev: true
/map-obj@1.0.1:
resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==}
@@ -22173,11 +22158,6 @@ packages:
define-properties: 1.2.0
functions-have-names: 1.2.3
- /regexpp@3.2.0:
- resolution: {integrity: sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==}
- engines: {node: '>=8'}
- dev: true
-
/regexpu-core@5.3.2:
resolution: {integrity: sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==}
engines: {node: '>=4'}
@@ -22325,7 +22305,6 @@ packages:
/remove-accents@0.4.2:
resolution: {integrity: sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA==}
- dev: true
/renderkid@3.0.0:
resolution: {integrity: sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==}