+
+
+ Resource reviews
+
+
+ Proposed edits to existing PHLask resources and brand-new site
+ submissions, pending approval.
+
+
+
+
+
+ New site submissions
+
+
+
+
+
+
+ Edits to existing resources
+
+
+
+
);
};
diff --git a/app/routes/unauthenticated/login.tsx b/app/routes/unauthenticated/login.tsx
index 72cca10..691a4e3 100644
--- a/app/routes/unauthenticated/login.tsx
+++ b/app/routes/unauthenticated/login.tsx
@@ -1,4 +1,5 @@
-import { Visibility, VisibilityOff } from "@mui/icons-material";
+import Visibility from "@mui/icons-material/Visibility";
+import VisibilityOff from "@mui/icons-material/VisibilityOff";
import {
Alert,
Button,
diff --git a/app/routes/unauthenticated/reset-password.tsx b/app/routes/unauthenticated/reset-password.tsx
index 12ff94d..1f8f52c 100644
--- a/app/routes/unauthenticated/reset-password.tsx
+++ b/app/routes/unauthenticated/reset-password.tsx
@@ -1,4 +1,5 @@
-import { Visibility, VisibilityOff } from "@mui/icons-material";
+import Visibility from "@mui/icons-material/Visibility";
+import VisibilityOff from "@mui/icons-material/VisibilityOff";
import {
Alert,
Button,
diff --git a/app/types/ResourceEdit.ts b/app/types/ResourceEdit.ts
index 934482a..79fe672 100644
--- a/app/types/ResourceEdit.ts
+++ b/app/types/ResourceEdit.ts
@@ -31,3 +31,28 @@ export type ResourceEdit = ResourceEntry & {
export type ResourceEditQueueRow = ResourceEdit & {
current_resource: ResourceEntry & { id: number };
};
+
+/**
+ * A row from `resource_edit_counts` — the number of PENDING edits currently
+ * queued against a given resource. Useful for flagging resources with
+ * multiple competing proposed edits before a reviewer picks one to approve.
+ */
+export type ResourceEditCount = {
+ resource_id: number;
+ pending_count: number;
+};
+
+/**
+ * A row from `resource_change_log` — a decided (APPROVED or REJECTED) edit,
+ * stripped down to just the review-lifecycle columns. One row per past
+ * decision, ordered by `resource_id` then most-recently-reviewed first.
+ */
+export type ResourceChangeLogEntry = {
+ edit_id: number;
+ resource_id: number | null;
+ review_status: EditReviewStatus;
+ submitted_by: string | null;
+ reviewed_by: string | null;
+ reviewed_at: string | null;
+ review_notes: string | null;
+};
diff --git a/app/utils/chipColors.tsx b/app/utils/chipColors.tsx
index 6c15edb..79cd5cc 100644
--- a/app/utils/chipColors.tsx
+++ b/app/utils/chipColors.tsx
@@ -1,4 +1,7 @@
-import { LocalDining, Park, WaterDrop, Wc } from "@mui/icons-material";
+import LocalDining from "@mui/icons-material/LocalDining";
+import Park from "@mui/icons-material/Park";
+import WaterDrop from "@mui/icons-material/WaterDrop";
+import Wc from "@mui/icons-material/Wc";
import type { ChipProps } from "@mui/material";
import type { ReactElement } from "react";
import type { EditReviewStatus } from "~/types/ResourceEdit";
diff --git a/package.json b/package.json
index 837a91a..dc150ed 100644
--- a/package.json
+++ b/package.json
@@ -22,6 +22,7 @@
"@supabase/ssr": "^0.8.0",
"@supabase/supabase-js": "^2.96.0",
"@tanstack/react-table": "^8.21.3",
+ "@vis.gl/react-google-maps": "^1.9.0",
"composable-functions": "^5.0.0",
"express": "^5.2.1",
"isbot": "^5.1.35",
@@ -35,6 +36,7 @@
"devDependencies": {
"@biomejs/biome": "2.3.14",
"@react-router/dev": "^7.13.0",
+ "@types/google.maps": "^3.65.5",
"@types/node": "^22.19.11",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 8ff0d9c..919062e 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -38,6 +38,9 @@ importers:
'@tanstack/react-table':
specifier: ^8.21.3
version: 8.21.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@vis.gl/react-google-maps':
+ specifier: ^1.9.0
+ version: 1.9.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
composable-functions:
specifier: ^5.0.0
version: 5.0.0
@@ -72,6 +75,9 @@ importers:
'@react-router/dev':
specifier: ^7.13.0
version: 7.13.0(@react-router/serve@7.13.0(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3))(@types/node@22.19.11)(babel-plugin-macros@3.1.0)(jiti@2.6.1)(lightningcss@1.30.2)(react-router@7.13.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(typescript@5.9.3)(vite@7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.30.2))
+ '@types/google.maps':
+ specifier: ^3.65.5
+ version: 3.65.5
'@types/node':
specifier: ^22.19.11
version: 22.19.11
@@ -496,6 +502,9 @@ packages:
cpu: [x64]
os: [win32]
+ '@googlemaps/js-api-loader@2.1.1':
+ resolution: {integrity: sha512-yUpAwksbHrlZIWD49JmveNSfBG4oAK0AwMknfSaPMnP5N7UT8oFRVCqwjGb1XQovi//7KLbPQKZpbofiLGzpDw==}
+
'@jridgewell/gen-mapping@0.3.13':
resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
@@ -845,6 +854,9 @@ packages:
'@types/estree@1.0.8':
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
+ '@types/google.maps@3.65.5':
+ resolution: {integrity: sha512-oR9Hq/WaKGzIguZEwoCuyQ0ofDhCbefmS9RrjLUZ5scMziwTE6xA90drJYt2ZsyUCYmCqnxQcDpfGqeRE/Zgog==}
+
'@types/node@22.19.11':
resolution: {integrity: sha512-BH7YwL6rA93ReqeQS1c4bsPpcfOmJasG+Fkr6Y59q83f9M1WcBRHR2vM+P9eOisYRcN3ujQoiZY8uk5W+1WL8w==}
@@ -873,6 +885,12 @@ packages:
'@types/ws@8.18.1':
resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==}
+ '@vis.gl/react-google-maps@1.9.0':
+ resolution: {integrity: sha512-ELL/zo8mRMPGDQZwHRClf2tE1/PMYYKjg+C/dUn8rmlQ0e+s7rkcNmCgaeBAV48GJzEcrBsr+vjDv4AvzJrlUA==}
+ peerDependencies:
+ react: '>=16.8.0 || ^19.0 || ^19.0.0-rc'
+ react-dom: '>=16.8.0 || ^19.0 || ^19.0.0-rc'
+
accepts@1.3.8:
resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
engines: {node: '>= 0.6'}
@@ -1114,6 +1132,10 @@ packages:
exsolve@1.0.8:
resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==}
+ fast-equals@6.0.2:
+ resolution: {integrity: sha512-sAjhj9ZhOxYCGiNMnZLaucOqf5ZeFnHNoKoAZiD9thhJ0N8RP85qJK759/97C/3L7NzzmGVB5uiX9AUpySZmUQ==}
+ engines: {node: '>=6.0.0'}
+
fdir@6.5.0:
resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
engines: {node: '>=12.0.0'}
@@ -2260,6 +2282,10 @@ snapshots:
'@esbuild/win32-x64@0.27.3':
optional: true
+ '@googlemaps/js-api-loader@2.1.1':
+ dependencies:
+ '@types/google.maps': 3.65.5
+
'@jridgewell/gen-mapping@0.3.13':
dependencies:
'@jridgewell/sourcemap-codec': 1.5.5
@@ -2588,6 +2614,8 @@ snapshots:
'@types/estree@1.0.8': {}
+ '@types/google.maps@3.65.5': {}
+
'@types/node@22.19.11':
dependencies:
undici-types: 6.21.0
@@ -2614,6 +2642,14 @@ snapshots:
dependencies:
'@types/node': 22.19.11
+ '@vis.gl/react-google-maps@1.9.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
+ dependencies:
+ '@googlemaps/js-api-loader': 2.1.1
+ '@types/google.maps': 3.65.5
+ fast-equals: 6.0.2
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
+
accepts@1.3.8:
dependencies:
mime-types: 2.1.35
@@ -2926,6 +2962,8 @@ snapshots:
exsolve@1.0.8: {}
+ fast-equals@6.0.2: {}
+
fdir@6.5.0(picomatch@4.0.3):
optionalDependencies:
picomatch: 4.0.3
diff --git a/tsconfig.json b/tsconfig.json
index cbe49c7..348b505 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -7,7 +7,7 @@
],
"compilerOptions": {
"lib": ["DOM", "DOM.Iterable", "ES2022"],
- "types": ["node", "vite/client"],
+ "types": ["node", "vite/client", "google.maps"],
"target": "ES2022",
"module": "ES2022",
"moduleResolution": "bundler",