Massive Next.js (14->16) and React (18->19) upgrade

This commit is contained in:
MartinBraquet
2026-03-01 16:55:19 +01:00
parent 8c68312597
commit 699890a0be
114 changed files with 1680 additions and 1936 deletions

View File

@@ -528,7 +528,6 @@ How we apply it here
This project uses three complementary test types. Use the right level for the job:
- Unit tests
- Purpose: Verify a single function/module in isolation; fast, deterministic.
- Where: Each package under `tests/unit` (e.g., `backend/api/tests/unit`, `web/tests/unit`, `common/tests/unit`,
etc.).
@@ -537,7 +536,6 @@ This project uses three complementary test types. Use the right level for the jo
- When to use: Pure logic, utilities, hooks, reducers, small components with mocked dependencies.
- Integration tests
- Purpose: Verify multiple units working together (e.g., function + DB/client, component + context/provider) without
spinning up the full app.
- Where: Each package under `tests/integration` (e.g., `backend/shared/tests/integration`, `web/tests/integration`).

View File

@@ -30,4 +30,4 @@ runs:
- name: Post-install
if: steps.cache-node-modules.outputs.cache-hit == 'true'
run: yarn postinstall
shell: bash
shell: bash

View File

@@ -522,7 +522,6 @@ How we apply it here
This project uses three complementary test types. Use the right level for the job:
- Unit tests
- Purpose: Verify a single function/module in isolation; fast, deterministic.
- Where: Each package under `tests/unit` (e.g., `backend/api/tests/unit`, `web/tests/unit`, `common/tests/unit`,
etc.).
@@ -531,7 +530,6 @@ This project uses three complementary test types. Use the right level for the jo
- When to use: Pure logic, utilities, hooks, reducers, small components with mocked dependencies.
- Integration tests
- Purpose: Verify multiple units working together (e.g., function + DB/client, component + context/provider) without
spinning up the full app.
- Where: Each package under `tests/integration` (e.g., `backend/shared/tests/integration`, `web/tests/integration`).

View File

@@ -1,7 +1,7 @@
name: CD Android Live Update
on:
push:
branches: [ main, master ]
branches: [main, master]
paths:
- 'android/capawesome.json'
- '.github/workflows/cd-android-live-update.yml'

View File

@@ -1,7 +1,7 @@
name: API Release
on:
push:
branches: [ main, master ]
branches: [main, master]
paths:
- 'backend/api/package.json'
- '.github/workflows/cd-api.yml'

View File

@@ -2,9 +2,9 @@ name: E2E Tests
on:
push:
branches: [ main ]
branches: [main]
pull_request:
branches: [ main ]
branches: [main]
jobs:
e2e:

View File

@@ -2,9 +2,9 @@ name: CI
on:
push:
branches: [ main ]
branches: [main]
pull_request:
branches: [ main ]
branches: [main]
jobs:
lint:

View File

@@ -522,7 +522,6 @@ How we apply it here
This project uses three complementary test types. Use the right level for the job:
- Unit tests
- Purpose: Verify a single function/module in isolation; fast, deterministic.
- Where: Each package under `tests/unit` (e.g., `backend/api/tests/unit`, `web/tests/unit`, `common/tests/unit`,
etc.).
@@ -531,7 +530,6 @@ This project uses three complementary test types. Use the right level for the jo
- When to use: Pure logic, utilities, hooks, reducers, small components with mocked dependencies.
- Integration tests
- Purpose: Verify multiple units working together (e.g., function + DB/client, component + context/provider) without
spinning up the full app.
- Where: Each package under `tests/integration` (e.g., `backend/shared/tests/integration`, `web/tests/integration`).

View File

@@ -7,7 +7,7 @@
"bracketSpacing": false,
"printWidth": 100,
"trailingComma": "all",
"plugins": ["prettier-plugin-sql"],
"plugins": ["prettier-plugin-sql", "prettier-plugin-packagejson"],
"overrides": [
{
"files": "*.sql",

View File

@@ -1,66 +1,44 @@
{
"name": "@compass/api",
"description": "Backend API endpoints",
"version": "1.19.0",
"private": true,
"description": "Backend API endpoints",
"main": "src/serve.ts",
"scripts": {
"watch:serve": "tsx watch src/serve.ts",
"watch:compile": "npx concurrently \"tsc -b --watch --preserveWatchOutput\" \"(cd ../../common && tsc-alias --watch)\" \"(cd ../shared && tsc-alias --watch)\" \"(cd ../email && tsc-alias --watch)\" \"tsc-alias --watch\"",
"dev": "yarn watch:serve",
"prod": "npx concurrently -n COMPILE,SERVER -c cyan,green \"yarn watch:compile\" \"yarn watch:serve\"",
"build": "yarn compile && yarn dist:clean && yarn dist:copy",
"build:fast": "yarn compile && yarn dist:copy",
"clean": "rm -rf lib && (cd ../../common && rm -rf lib) && (cd ../shared && rm -rf lib) && (cd ../email && rm -rf lib)",
"compile": "tsc -b && tsc-alias && (cd ../../common && tsc-alias) && (cd ../shared && tsc-alias) && (cd ../email && tsc-alias) && cp -r src/public/ lib/",
"debug": "nodemon -r tsconfig-paths/register --watch src -e ts --watch ../../common/src --watch ../shared/src --exec \"yarn build && node --inspect-brk src/serve.ts\"",
"dev": "yarn watch:serve",
"dist": "yarn dist:clean && yarn dist:copy",
"dist:clean": "rm -rf dist && mkdir -p dist/common/lib dist/backend/shared/lib dist/backend/api/lib dist/backend/email/lib",
"dist:copy": "rsync -a --delete ../../common/lib/ dist/common/lib && rsync -a ../../common/messages/ dist/common/messages/ && rsync -a --delete ../shared/lib/ dist/backend/shared/lib && rsync -a --delete ../email/lib/ dist/backend/email/lib && rsync -a --delete ./lib/* dist/backend/api/lib && cp ../../yarn.lock dist && cp package.json dist && cp package.json dist/backend/api && cp metadata.json dist && cp metadata.json dist/backend/api",
"watch": "tsc -w",
"lint": "npx eslint . --max-warnings 0",
"lint-fix": "npx eslint . --fix",
"typecheck": "yarn build && npx tsc --noEmit",
"prod": "npx concurrently -n COMPILE,SERVER -c cyan,green \"yarn watch:compile\" \"yarn watch:serve\"",
"regen-types": "cd ../supabase && make ENV=prod regen-types",
"regen-types-dev": "cd ../supabase && make ENV=dev regen-types-dev",
"test": "jest --config jest.config.ts",
"test:coverage": "jest --config jest.config.ts --coverage"
"test:coverage": "jest --config jest.config.ts --coverage",
"typecheck": "yarn build && npx tsc --noEmit",
"watch:compile": "npx concurrently \"tsc -b --watch --preserveWatchOutput\" \"(cd ../../common && tsc-alias --watch)\" \"(cd ../shared && tsc-alias --watch)\" \"(cd ../email && tsc-alias --watch)\" \"tsc-alias --watch\"",
"watch:serve": "tsx watch src/serve.ts"
},
"engines": {
"node": ">=20.0.0"
},
"main": "src/serve.ts",
"dependencies": {
"@google-cloud/monitoring": "4.0.0",
"@google-cloud/secret-manager": "4.2.1",
"@react-email/components": "0.0.33",
"@supabase/supabase-js": "2.38.5",
"@tiptap/core": "2.10.4",
"@tiptap/extension-blockquote": "2.10.4",
"@tiptap/extension-bold": "2.10.4",
"@tiptap/extension-bubble-menu": "2.10.4",
"@tiptap/extension-floating-menu": "2.10.4",
"@tiptap/extension-image": "2.10.4",
"@tiptap/extension-link": "2.10.4",
"@tiptap/extension-mention": "2.10.4",
"@tiptap/html": "2.10.4",
"@tiptap/pm": "2.10.4",
"@tiptap/starter-kit": "2.10.4",
"@tiptap/suggestion": "2.10.4",
"colors": "1.4.0",
"cors": "2.8.5",
"dayjs": "1.11.4",
"dayjs": "1.11.19",
"express": "5.0.0",
"firebase-admin": "13.5.0",
"gcp-metadata": "6.1.0",
"jsonwebtoken": "9.0.0",
"lodash": "4.17.23",
"openapi-types": "12.1.3",
"pg-promise": "11.5.5",
"pg-promise": "12.6.1",
"posthog-node": "4.11.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"resend": "4.1.2",
"string-similarity": "4.0.4",
"swagger-jsdoc": "6.2.8",
"swagger-ui-express": "5.0.1",
"tsconfig-paths": "4.2.0",
@@ -71,10 +49,13 @@
},
"devDependencies": {
"@types/cors": "2.8.17",
"@types/react": "18.3.5",
"@types/react-dom": "18.3.0",
"@types/jsonwebtoken": "^9.0.0",
"@types/lodash": "^4.17.0",
"@types/swagger-ui-express": "4.1.8",
"@types/web-push": "3.6.4",
"@types/ws": "8.5.10"
},
"engines": {
"node": ">=20.9.0"
}
}

View File

@@ -3,24 +3,24 @@
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "email dev --port 3001",
"build": "tsc -b",
"test": "jest --config jest.config.ts --passWithNoTests",
"dev": "email dev --port 3001",
"lint": "npx eslint . --max-warnings 0",
"lint-fix": "npx eslint . --fix",
"test": "jest --config jest.config.ts --passWithNoTests",
"typecheck": "npx tsc --noEmit"
},
"dependencies": {
"@react-email/components": "0.0.33",
"@react-email/components": "1.0.8",
"react": "19.2.3",
"react-dom": "19.2.3",
"react-icons": "5.5.0",
"resend": "4.1.2",
"react": "18.2.0",
"react-dom": "18.2.0"
"resend": "4.1.2"
},
"devDependencies": {
"@types/html-to-text": "9.0.4",
"@types/prismjs": "1.26.5",
"@types/react": "18.3.5",
"@types/react-dom": "18.3.0"
"@types/react": "19.2.3",
"@types/react-dom": "19.2.3"
}
}

View File

@@ -2,22 +2,20 @@
"name": "shared",
"version": "1.0.0",
"private": true,
"sideEffects": false,
"scripts": {
"build": "tsc -b && yarn --cwd=../../common tsc-alias && tsc-alias",
"compile": "tsc -b",
"lint": "npx eslint . --max-warnings 0",
"lint-fix": "npx eslint . --fix",
"typecheck": "npx tsc --noEmit",
"test": "jest --config jest.config.ts --passWithNoTests"
"test": "jest --config jest.config.ts --passWithNoTests",
"typecheck": "npx tsc --noEmit"
},
"sideEffects": false,
"dependencies": {
"@google-cloud/monitoring": "4.0.0",
"@google-cloud/secret-manager": "4.2.1",
"@tiptap/core": "2.10.4",
"@tiptap/html": "2.10.4",
"colors": "1.4.0",
"dayjs": "1.11.4",
"colorette": "2.0.20",
"dayjs": "1.11.19",
"firebase-admin": "13.5.0",
"gcp-metadata": "6.1.0",
"lodash": "4.17.23",
@@ -25,5 +23,11 @@
"pg-query-stream": "4.12.0",
"posthog-node": "4.11.0",
"string-similarity": "4.0.4"
},
"devDependencies": {
"@types/jest": "^29",
"@types/lodash": "^4.17.0",
"jest": "^29",
"ts-jest": "29.4.6"
}
}

View File

@@ -1,6 +1,6 @@
import {format} from 'node:util'
import {dim, red, yellow} from 'colors/safe'
import {dim, red, yellow} from 'colorette'
import {IS_GOOGLE_CLOUD} from 'common/hosting/constants'
import {isError, omit, pick} from 'lodash'

View File

@@ -2,17 +2,17 @@
"name": "common",
"version": "1.0.0",
"private": true,
"sideEffects": false,
"scripts": {
"build": "tsc -b && tsc-alias",
"compile": "tsc -b",
"lint": "npx eslint . --max-warnings 0",
"lint-fix": "npx eslint . --fix",
"typecheck": "npx tsc --noEmit",
"test": "jest --config jest.config.ts --passWithNoTests"
"test": "jest --config jest.config.ts --passWithNoTests",
"typecheck": "npx tsc --noEmit"
},
"sideEffects": false,
"dependencies": {
"@supabase/supabase-js": "2.38.5",
"@supabase/supabase-js": "2.98.0",
"@tiptap/core": "2.10.4",
"@tiptap/extension-image": "2.10.4",
"@tiptap/extension-link": "2.10.4",
@@ -20,17 +20,16 @@
"@tiptap/pm": "2.10.4",
"@tiptap/starter-kit": "2.10.4",
"@tiptap/suggestion": "2.10.4",
"dayjs": "1.11.4",
"dayjs": "1.11.19",
"lodash": "4.17.23",
"string-similarity": "4.0.4",
"zod": "3.22.3"
},
"devDependencies": {
"@types/jest": "29.2.4",
"@types/lodash": "4.14.178",
"@types/jest": "^29",
"@types/lodash": "^4.17.0",
"@types/string-similarity": "4.0.0",
"jest": "29.3.1",
"supabase": "2.15.8",
"jest": "^29",
"ts-jest": "29.4.6"
}
}

View File

@@ -22,7 +22,7 @@ export type Row<T extends Selectable> = T extends TableName
: never
export type Column<T extends Selectable> = keyof Row<T> & string
export type SupabaseClient = SupabaseClientGeneric<Database, 'public', Schema>
export type SupabaseClient = SupabaseClientGeneric<Database, 'public'>
export function createClient(
instanceIdOrUrl: string,

View File

@@ -25,7 +25,6 @@ How we apply it here
This project uses three complementary test types. Use the right level for the job:
- Unit tests
- Purpose: Verify a single function/module in isolation; fast, deterministic.
- Where: Each package under `tests/unit` (e.g., `backend/api/tests/unit`, `web/tests/unit`, `common/tests/unit`,
etc.).
@@ -34,7 +33,6 @@ This project uses three complementary test types. Use the right level for the jo
- When to use: Pure logic, utilities, hooks, reducers, small components with mocked dependencies.
- Integration tests
- Purpose: Verify multiple units working together (e.g., function + DB/client, component + context/provider) without
spinning up the full app.
- Where: Each package under `tests/integration` (e.g., `backend/shared/tests/integration`, `web/tests/integration`).

View File

@@ -92,7 +92,7 @@ Compass/
| Category | Technology | Version |
| ---------- | --------------------- | ------- |
| Framework | Next.js | 14.1.0 |
| UI Library | React | 18.2.0 |
| UI Library | React | 19.2.3 |
| Language | TypeScript | 5.5.4 |
| Styling | Tailwind CSS | 3.3.3 |
| State | React Context + Hooks | - |
@@ -183,37 +183,40 @@ User Sign-In:
```sql
-- Users table
users (
CREATE TABLE users (
id UUID PRIMARY KEY,
username TEXT UNIQUE,
email TEXT,
created_at TIMESTAMP,
deleted_at TIMESTAMP
)
);
-- Profile information
profiles (
CREATE TABLE profiles (
id UUID PRIMARY KEY,
user_id UUID REFERENCES users,
name TEXT,
age INTEGER,
bio TEXT,
bio TEXT
-- many more fields
)
);
-- Private user data
private_users (
CREATE TABLE private_users (
id UUID PRIMARY KEY,
user_id UUID REFERENCES users,
email TEXT,
notification_settings JSONB
)
);
-- Messages
private_user_messages (
CREATE TABLE private_user_messages (
id UUID PRIMARY KEY,
from_user_id UUID,
to_user_id UUID,
content TEXT,
created_at TIMESTAMP
)
);
```
See `supabase/migrations/` for full schema.
@@ -245,18 +248,16 @@ See `supabase/migrations/` for full schema.
```typescript
// Success
{
// Response data
return {
// Response data
}
// Error
{
error: {
status: 400,
message
:
"Error description"
}
return {
error: {
status: 400,
message: 'Error description',
},
}
```
@@ -271,14 +272,21 @@ See `supabase/migrations/` for full schema.
### State Persistence
In-memory (lost on refresh)
```typescript
// In-memory (lost on refresh)
const [state, setState] = useState(initialValue)
```
// Local storage (persists)
Local storage (persists)
```typescript
const [state, setState] = usePersistentLocalState(initialValue, 'key')
```
// Session storage (persists until tab closed)
Session storage (persists until tab closed)
```typescript
const [state, setState] = usePersistentInMemoryState(initialValue, 'key')
```

View File

@@ -11,95 +11,40 @@
"web"
],
"scripts": {
"lint": "yarn --cwd=web lint; yarn --cwd=common lint; yarn --cwd=backend/api lint; yarn --cwd=backend/shared lint; yarn --cwd=backend/email lint",
"lint-fix": "yarn --cwd=web lint-fix; yarn --cwd=common lint-fix; yarn --cwd=backend/api lint-fix; yarn --cwd=backend/shared lint-fix; yarn --cwd=backend/email lint-fix",
"typecheck": "yarn --cwd=web typecheck; yarn --cwd=backend/api typecheck; yarn --cwd=common typecheck; yarn --cwd=backend/shared typecheck; yarn --cwd=backend/email typecheck",
"prettier": "prettier --write .",
"prettier:check": "prettier --check .",
"android-live-update": "./scripts/android_live_update.sh",
"build-sync-android": "./scripts/build_sync_android.sh",
"build-web-view": "./scripts/build_web_view.sh",
"clean-install": "./scripts/install.sh",
"dev": "./scripts/run_local.sh dev",
"dev:isolated": "./scripts/run_local_isolated.sh",
"prod": "./scripts/run_local.sh prod",
"clean-install": "./scripts/install.sh",
"build-web-view": "./scripts/build_web_view.sh",
"build-sync-android": "./scripts/build_sync_android.sh",
"android-live-update": "./scripts/android_live_update.sh",
"sync-android": "./scripts/sync_android.sh",
"emulate": "firebase emulators:start --only auth,storage --project compass-57c3c",
"postinstall": "./scripts/post_install.sh",
"lint": "yarn --cwd=web lint; yarn --cwd=common lint; yarn --cwd=backend/api lint; yarn --cwd=backend/shared lint; yarn --cwd=backend/email lint",
"lint-fix": "yarn --cwd=web lint-fix; yarn --cwd=common lint-fix; yarn --cwd=backend/api lint-fix; yarn --cwd=backend/shared lint-fix; yarn --cwd=backend/email lint-fix",
"migrate": "./scripts/migrate.sh",
"test": "yarn workspaces run test",
"test:coverage": "yarn workspaces run test --coverage",
"test:watch": "yarn workspaces run test --watch",
"test:update": "yarn workspaces run test --updateSnapshot",
"test:e2e": "./scripts/e2e.sh",
"test:e2e:dev": "./scripts/e2e-dev.sh",
"test:e2e:ui": "./scripts/e2e.sh --ui",
"test:e2e:debug": "./scripts/e2e.sh --debug",
"test:e2e:services": "./scripts/e2e_services.sh",
"test:db:reset": "./scripts/test_db_reset.sh",
"test:db:reset-postgres": "docker compose -f scripts/docker-compose.test.yml down -v && docker compose -f scripts/docker-compose.test.yml up -d",
"test:db:migrate": "./scripts/test_db_migration.sh",
"test:db:seed": "./scripts/seed.sh",
"playwright": "playwright test",
"playwright:ui": "playwright test --ui",
"playwright:debug": "playwright test --debug",
"playwright:report": "npx playwright show-report tests/reports/playwright-report",
"postinstall": "./scripts/post_install.sh",
"emulate": "firebase emulators:start --only auth,storage --project compass-57c3c",
"prepare": "npx husky"
},
"dependencies": {
"@capacitor/app": "7.1.0",
"@capacitor/core": "7.4.4",
"@capacitor/keyboard": "7.0.3",
"@capacitor/push-notifications": "7.0.3",
"@capacitor/status-bar": "7.0.3",
"@capawesome/capacitor-live-update": "7.2.2",
"@capgo/capacitor-social-login": "7.14.9",
"colorette": "^2.0.20",
"prismjs": "^1.30.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-markdown": "10.1.0",
"supabase": "2.76.9",
"wait-on": "9.0.4"
},
"devDependencies": {
"@capacitor/android": "7.4.4",
"@capacitor/assets": "3.0.5",
"@capacitor/cli": "7.4.4",
"@faker-js/faker": "10.1.0",
"@playwright/test": "1.58.2",
"@testing-library/dom": "^10.0.0",
"@testing-library/jest-dom": "^6.6.4",
"@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^14.6.1",
"@types/jest": "29.2.4",
"@types/node": "20.12.11",
"@eslint/js": "^9.39.0",
"typescript-eslint": "^8.32.0",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-config-prettier": "^10.1.5",
"@next/eslint-plugin-next": "^15.3.3",
"chalk": "5.6.2",
"concurrently": "8.2.2",
"dotenv-cli": "10.0.0",
"eslint": "9.39.3",
"eslint-plugin-lodash": "7.4.0",
"eslint-plugin-simple-import-sort": "12.1.1",
"eslint-plugin-unused-imports": "4.1.4",
"firebase-tools": "14.27.0",
"husky": "9.1.7",
"jest": "29.3.1",
"nodemon": "2.0.20",
"prettier": "3.6.2",
"prettier-plugin-sql": "0.19.2",
"prettier-plugin-tailwindcss": "0.2.8",
"ts-jest": "29.4.6",
"ts-node": "10.9.1",
"tsc-alias": "1.8.2",
"tsconfig-paths": "4.2.0",
"tsx": "4.20.6",
"typescript": "5.5.4"
"playwright:ui": "playwright test --ui",
"prepare": "npx husky",
"prettier": "prettier --write .",
"prettier:check": "prettier --check .",
"prod": "./scripts/run_local.sh prod",
"sync-android": "./scripts/sync_android.sh",
"test": "yarn workspaces run test",
"test:coverage": "yarn workspaces run test --coverage",
"test:db:migrate": "./scripts/test_db_migration.sh",
"test:db:reset": "./scripts/test_db_reset.sh",
"test:db:reset-postgres": "docker compose -f scripts/docker-compose.test.yml down -v && docker compose -f scripts/docker-compose.test.yml up -d",
"test:db:seed": "./scripts/seed.sh",
"test:e2e": "./scripts/e2e.sh",
"test:e2e:debug": "./scripts/e2e.sh --debug",
"test:e2e:dev": "./scripts/e2e-dev.sh",
"test:e2e:services": "./scripts/e2e_services.sh",
"test:e2e:ui": "./scripts/e2e.sh --ui",
"test:update": "yarn workspaces run test --updateSnapshot",
"test:watch": "yarn workspaces run test --watch",
"typecheck": "yarn --cwd=web typecheck; yarn --cwd=backend/api typecheck; yarn --cwd=common typecheck; yarn --cwd=backend/shared typecheck; yarn --cwd=backend/email typecheck"
},
"resolutions": {
"@tiptap/core": "2.10.4",
@@ -107,19 +52,62 @@
"@tiptap/extension-bold": "2.10.4",
"@tiptap/extension-bubble-menu": "2.10.4",
"@tiptap/extension-floating-menu": "2.10.4",
"@tiptap/extension-horizontal-rule": "2.10.4",
"@tiptap/extension-image": "2.10.4",
"@tiptap/extension-link": "2.10.4",
"@tiptap/extension-mention": "2.10.4",
"@tiptap/extension-horizontal-rule": "2.10.4",
"@tiptap/html": "2.10.4",
"@tiptap/pm": "2.10.4",
"@tiptap/starter-kit": "2.10.4",
"@tiptap/suggestion": "2.10.4",
"@types/react": "18.3.5",
"@types/react-dom": "18.3.0",
"@types/jest": "^29",
"@types/node": "20.19.35",
"@types/react": "19.2.3",
"@types/react-dom": "19.2.3",
"jest": "^29",
"lodash": "4.17.23",
"pg-query-stream/pg": "8.x",
"prosemirror-model": "1.x",
"react": "18.2.0",
"react-dom": "18.2.0",
"pg-query-stream/pg": "8.x"
"react": "19.2.3",
"react-dom": "19.2.3",
"zod": "3.22.3"
},
"devDependencies": {
"@eslint/js": "^9.39.0",
"@faker-js/faker": "10.1.0",
"@next/eslint-plugin-next": "16.1.6",
"@playwright/test": "1.58.2",
"@testing-library/dom": "^10.0.0",
"@testing-library/jest-dom": "^6.6.4",
"@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^14.6.1",
"@types/jest": "^29",
"@types/node": "20.19.35",
"concurrently": "8.2.2",
"dotenv-cli": "10.0.0",
"eslint": "9.39.3",
"eslint-config-prettier": "^10.1.5",
"eslint-plugin-lodash": "7.4.0",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-simple-import-sort": "12.1.1",
"eslint-plugin-unused-imports": "4.1.4",
"firebase-tools": "14.27.0",
"husky": "9.1.7",
"jest": "^29",
"nodemon": "3.1.14",
"prettier": "3.6.2",
"prettier-plugin-packagejson": "3.0.0",
"prettier-plugin-sql": "0.19.2",
"prettier-plugin-tailwindcss": "0.2.8",
"supabase": "2.76.9",
"ts-jest": "29.4.6",
"ts-node": "10.9.1",
"tsc-alias": "1.8.2",
"tsconfig-paths": "4.2.0",
"tsx": "4.20.6",
"typescript": "5.5.4",
"typescript-eslint": "^8.32.0",
"wait-on": "9.0.4"
}
}

View File

@@ -10,7 +10,7 @@ This is the frontend of the Compass platform, a transparent platform for forming
- **Framework**: Next.js 14.1.0
- **Language**: TypeScript
- **UI Library**: React 18.2.0
- **UI Library**: React 19.2.3
- **Styling**: Tailwind CSS 3.3.3
- **State Management**: React Context + Custom Hooks
- **Forms**: React Hook Form
@@ -220,7 +220,6 @@ Catches React errors and shows user-friendly message:
```tsx
import {ErrorBoundary} from 'web/components/error-boundary'
;<ErrorBoundary>
<MyComponent />
</ErrorBoundary>
@@ -250,7 +249,6 @@ Keyboard users can skip to main content:
```tsx
import {SkipLink, MainContent} from 'web/components/skip-link'
;<>
<SkipLink />
<MainContent>...</MainContent>

View File

@@ -1,4 +1,4 @@
import {PlusIcon, XIcon} from '@heroicons/react/outline'
import {PlusIcon, XMarkIcon} from '@heroicons/react/24/outline'
import {MAX_ANSWER_LENGTH} from 'common/envs/constants'
import {debug} from 'common/logger'
import {MAX_COMPATIBILITY_QUESTION_LENGTH} from 'common/profiles/constants'
@@ -156,7 +156,9 @@ function CreateCompatibilityModalContent(props: {
<ExpandingInput
maxLength={MAX_COMPATIBILITY_QUESTION_LENGTH}
value={question}
onChange={(e) => setQuestion(e.target.value || '')}
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) =>
setQuestion(e.target.value || '')
}
/>
</Col>
<Col className="gap-1">
@@ -169,7 +171,9 @@ function CreateCompatibilityModalContent(props: {
<div key={index} className="relative">
<ExpandingInput
value={options[index]}
onChange={(e) => onOptionChange(index, e.target.value)}
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) =>
onOptionChange(index, e.target.value)
}
className="w-full"
placeholder={t('answers.add.option_placeholder', 'Option {n}', {
n: String(index + 1),
@@ -182,7 +186,7 @@ function CreateCompatibilityModalContent(props: {
className="bg-ink-400 text-ink-0 hover:bg-ink-600 transition-color absolute -right-1.5 -top-1.5 rounded-full p-0.5"
onClick={() => deleteOption(index)}
>
<XIcon className="z-10 h-3 w-3" />
<XMarkIcon className="z-10 h-3 w-3" />
</button>
)}
</div>

View File

@@ -1,5 +1,5 @@
import {RadioGroup} from '@headlessui/react'
import {UserIcon} from '@heroicons/react/solid'
import {UserIcon} from '@heroicons/react/24/solid'
import clsx from 'clsx'
import {Row as rowFor} from 'common/supabase/utils'
import {User} from 'common/user'
@@ -221,7 +221,9 @@ export function AnswerCompatibilityQuestionContent(props: {
className={'w-full'}
rows={3}
value={answer.explanation ?? ''}
onChange={(e) => setAnswer({...answer, explanation: e.target.value})}
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) =>
setAnswer({...answer, explanation: e.target.value})
}
/>
</Col>
</Col>

View File

@@ -1,4 +1,4 @@
import {CheckCircleIcon, XCircleIcon} from '@heroicons/react/outline'
import {CheckCircleIcon, XCircleIcon} from '@heroicons/react/24/outline'
import clsx from 'clsx'
import {Row as rowFor} from 'common/supabase/utils'
import {User} from 'common/user'

View File

@@ -1,4 +1,4 @@
import {PencilIcon, TrashIcon} from '@heroicons/react/outline'
import {PencilIcon, TrashIcon} from '@heroicons/react/24/outline'
import clsx from 'clsx'
import {
getAnswerCompatibility,

View File

@@ -1,4 +1,4 @@
import {ArrowLeftIcon, PlusIcon} from '@heroicons/react/outline'
import {ArrowLeftIcon, PlusIcon} from '@heroicons/react/24/outline'
import clsx from 'clsx'
import {User} from 'common/user'
import {TbMessage} from 'react-icons/tb'

View File

@@ -1,4 +1,4 @@
import {PencilIcon, XIcon} from '@heroicons/react/outline'
import {PencilIcon, XMarkIcon} from '@heroicons/react/24/outline'
import {Profile} from 'common/profiles/profile'
import {Row as rowFor} from 'common/supabase/utils'
import {User} from 'common/user'
@@ -122,7 +122,7 @@ function AnswerBlock(props: {
},
{
name: t('answers.menu.delete', 'Delete'),
icon: <XIcon className="h-5 w-5" />,
icon: <XMarkIcon className="h-5 w-5" />,
onClick: () => deleteAnswer(answer, user.id).then(() => refreshAnswers()),
},
{

View File

@@ -1,4 +1,4 @@
import {PencilIcon} from '@heroicons/react/outline'
import {PencilIcon} from '@heroicons/react/24/outline'
import clsx from 'clsx'
import {Row as rowFor} from 'common/supabase/utils'
import {capitalize, orderBy} from 'lodash'

View File

@@ -1,4 +1,4 @@
import {ArrowLeftIcon} from '@heroicons/react/solid'
import {ArrowLeftIcon} from '@heroicons/react/24/solid'
import clsx from 'clsx'
import {useRouter} from 'next/navigation'
import {useEffect, useState} from 'react'

View File

@@ -1,4 +1,4 @@
import {PencilIcon, XIcon} from '@heroicons/react/outline'
import {PencilIcon, XMarkIcon} from '@heroicons/react/24/outline'
import {JSONContent} from '@tiptap/core'
import clsx from 'clsx'
import {Profile} from 'common/profiles/profile'
@@ -61,7 +61,7 @@ export function BioBlock(props: {
},
{
name: t('profile.bio.delete', 'Delete'),
icon: <XIcon className="h-5 w-5" />,
icon: <XMarkIcon className="h-5 w-5" />,
onClick: async () => {
const {error} = await tryCatch(updateProfile({bio: null}))
if (error) console.error(error)

View File

@@ -1,4 +1,4 @@
import {QuestionMarkCircleIcon} from '@heroicons/react/outline'
import {QuestionMarkCircleIcon} from '@heroicons/react/24/outline'
import {JSONContent} from '@tiptap/core'
import {MAX_INT, MIN_BIO_LENGTH} from 'common/constants'
import {Profile} from 'common/profiles/profile'

View File

@@ -141,7 +141,7 @@
// className={'!h-10 max-w-[200px] self-end text-sm'}
// value={query}
// placeholder={'Search name'}
// onChange={(e) => {
// onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => {
// setQuery(e.target.value)
// }}
// />

View File

@@ -1,5 +1,5 @@
import clsx from 'clsx'
import {forwardRef, MouseEventHandler, ReactNode, Ref} from 'react'
import {ComponentPropsWithoutRef, forwardRef, MouseEventHandler, ReactNode, Ref} from 'react'
import {LoadingIndicator} from 'web/components/widgets/loading-indicator'
export type SizeType = '2xs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'
@@ -75,7 +75,7 @@ export const Button = forwardRef(function Button(
color?: ColorType | null
type?: 'button' | 'reset' | 'submit'
loading?: boolean
} & JSX.IntrinsicElements['button'],
} & ComponentPropsWithoutRef<'button'>,
ref: Ref<HTMLButtonElement>,
) {
const {

View File

@@ -1,5 +1,5 @@
import clsx from 'clsx'
import {ReactNode, useState} from 'react'
import {ReactElement, ReactNode, useState} from 'react'
import {Col} from '../layout/col'
import {Modal} from '../layout/modal'
@@ -9,7 +9,7 @@ import {Button, ColorType, SizeType} from './button'
export function ConfirmationButton(props: {
openModalBtn: {
label: string
icon?: JSX.Element
icon?: ReactElement
className?: string
color?: ColorType
size?: SizeType
@@ -84,7 +84,7 @@ export function ConfirmationButton(props: {
<Button
className={openModalBtn.className}
onClick={(e) => {
onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault()
e.stopPropagation()
if (disabled) {

View File

@@ -1,5 +1,5 @@
import {CheckIcon, ClipboardCopyIcon, DuplicateIcon} from '@heroicons/react/outline'
import {LinkIcon} from '@heroicons/react/solid'
import {CheckIcon, ClipboardIcon, DocumentDuplicateIcon} from '@heroicons/react/24/outline'
import {LinkIcon} from '@heroicons/react/24/solid'
import clsx from 'clsx'
import {ComponentProps, useState} from 'react'
import toast from 'react-hot-toast'
@@ -121,7 +121,11 @@ export const CopyLinkRow = (props: {
<div className={'select-all truncate'}>{displayUrl}</div>
{url && (
<div className={linkButtonClassName}>
{!iconPressed ? <DuplicateIcon className="h-5 w-5" /> : <CheckIcon className="h-5 w-5" />}
{!iconPressed ? (
<DocumentDuplicateIcon className="h-5 w-5" />
) : (
<CheckIcon className="h-5 w-5" />
)}
</div>
)}
</button>
@@ -152,7 +156,7 @@ export function SimpleCopyTextButton(props: {
noTap
placement="bottom"
>
<ClipboardCopyIcon className={'h-5'} aria-hidden="true" />
<ClipboardIcon className={'h-5'} aria-hidden="true" />
</Tooltip>
</IconButton>
)

View File

@@ -25,7 +25,7 @@ export function FileUploadButton(props: {
accept=".gif,.jpg,.jpeg,.png,.webp, image/*"
multiple
className="hidden"
onChange={(e) => {
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
const files = e.target.files
if (files) {
onFiles(Array.from(files))

View File

@@ -1,4 +1,4 @@
import {DotsHorizontalIcon} from '@heroicons/react/outline'
import {EllipsisHorizontalIcon} from '@heroicons/react/24/outline'
import clsx from 'clsx'
import {User} from 'common/user'
import {buildArray} from 'common/util/array'
@@ -47,7 +47,7 @@ export function MoreOptionsUserButton(props: {user: User}) {
className="rounded-none px-6"
onClick={() => setIsModalOpen(true)}
>
<DotsHorizontalIcon className={clsx('h-5 w-5 flex-shrink-0')} aria-hidden="true" />
<EllipsisHorizontalIcon className={clsx('h-5 w-5 flex-shrink-0')} aria-hidden="true" />
</Button>
</Tooltip>

View File

@@ -1,5 +1,5 @@
import {DotsHorizontalIcon, PencilIcon, TrashIcon} from '@heroicons/react/outline'
import {EmojiHappyIcon} from '@heroicons/react/solid'
import {EllipsisHorizontalIcon, PencilIcon, TrashIcon} from '@heroicons/react/24/outline'
import {FaceSmileIcon} from '@heroicons/react/24/solid'
import {JSONContent} from '@tiptap/react'
import clsx from 'clsx'
import {PrivateChatMessage} from 'common/chat-message'
@@ -111,7 +111,7 @@ export function MessageActions(props: {
},
{
name: t('messages.action.add_reaction', 'Add Reaction'),
icon: <EmojiHappyIcon className="h-4 w-4" />,
icon: <FaceSmileIcon className="h-4 w-4" />,
onClick: () => {
setShowEmojiPicker(!showEmojiPicker)
},
@@ -119,7 +119,7 @@ export function MessageActions(props: {
].filter(Boolean) as DropdownItem[]
}
closeOnClick={true}
icon={<DotsHorizontalIcon className="h-5 w-5 text-gray-500" />}
icon={<EllipsisHorizontalIcon className="h-5 w-5 text-gray-500" />}
menuWidth="w-40"
className="text-ink-500 hover:text-ink-700 rounded-full p-1 hover:bg-canvas-50"
/>

View File

@@ -1,90 +1,90 @@
import {Comment} from 'common/comment'
import {run} from 'common/supabase/utils'
import {useEffect, useState} from 'react'
import {Col} from 'web/components/layout/col'
import {Modal} from 'web/components/layout/modal'
import {Content} from 'web/components/widgets/editor'
import {LoadingIndicator} from 'web/components/widgets/loading-indicator'
import {Title} from 'web/components/widgets/title'
import {useIsClient} from 'web/hooks/use-is-client'
import {useT} from 'web/lib/locale'
import {db} from 'web/lib/supabase/db'
import {shortenedFromNow} from 'web/lib/util/shortenedFromNow'
import {formatTimeShort} from 'web/lib/util/time'
import {DateTimeTooltip} from '../widgets/datetime-tooltip'
type EditHistory = Comment & {
editCreatedTime: number
}
export const CommentEditHistoryButton = (props: {comment: Comment}) => {
const {comment} = props
const [showEditHistory, setShowEditHistory] = useState(false)
const [edits, setEdits] = useState<EditHistory[] | undefined>(undefined)
const isClient = useIsClient()
const t = useT()
const loadEdits = async () => {
const {data} = await run(
db
.from('contract_comment_edits')
.select('*')
.eq('comment_id', comment.id)
.order('created_time', {ascending: false}),
)
// created_time is the time the row is created, but the row's content is the content before the edit, aka created_time is when the content is deleted and replaced
const comments = data.map((edit, i) => {
const comment = edit.data as Comment
const editCreatedTime =
i === data.length - 1 ? comment.createdTime : new Date(data[i + 1].created_time).valueOf()
return {...comment, editCreatedTime}
})
setEdits(comments)
}
useEffect(() => {
if (showEditHistory && edits === undefined) {
loadEdits()
}
}, [showEditHistory])
if (!comment.editedTime) return null
return (
<>
<DateTimeTooltip time={comment.editedTime} placement={'top'}>
<button
className={
'text-ink-400 hover:bg-ink-50 mx-1 inline-block whitespace-nowrap rounded px-0.5 text-sm'
}
onClick={() => setShowEditHistory(true)}
>
(edited) {isClient && shortenedFromNow(comment.editedTime, t)}
</button>
</DateTimeTooltip>
<Modal size={'md'} open={showEditHistory} setOpen={setShowEditHistory}>
<div className={'bg-canvas-50 rounded-2xl p-4'}>
<Title className="w-full text-center">Edit History</Title>
{!edits ? (
<LoadingIndicator />
) : (
<Col className="gap-4">
{edits.map((edit) => (
<Col key={edit.id} className={'bg-ink-100 gap-2 rounded-xl rounded-tl-none p-2'}>
<div className="text-ink-500 text-sm">
{formatTimeShort(edit.editCreatedTime)}
</div>
<Content size="sm" className="mt-1 grow" content={edit.content || edit.text} />
</Col>
))}
</Col>
)}
</div>
</Modal>
</>
)
}
// import {Comment} from 'common/comment'
// import {run} from 'common/supabase/utils'
// import {useEffect, useState} from 'react'
// import {Col} from 'web/components/layout/col'
// import {Modal} from 'web/components/layout/modal'
// import {Content} from 'web/components/widgets/editor'
// import {LoadingIndicator} from 'web/components/widgets/loading-indicator'
// import {Title} from 'web/components/widgets/title'
// import {useIsClient} from 'web/hooks/use-is-client'
// import {useT} from 'web/lib/locale'
// import {db} from 'web/lib/supabase/db'
// import {shortenedFromNow} from 'web/lib/util/shortenedFromNow'
// import {formatTimeShort} from 'web/lib/util/time'
//
// import {DateTimeTooltip} from '../widgets/datetime-tooltip'
//
// type EditHistory = Comment & {
// editCreatedTime: number
// }
// export const CommentEditHistoryButton = (props: {comment: Comment}) => {
// const {comment} = props
// const [showEditHistory, setShowEditHistory] = useState(false)
// const [edits, setEdits] = useState<EditHistory[] | undefined>(undefined)
//
// const isClient = useIsClient()
// const t = useT()
//
// const loadEdits = async () => {
// const {data} = await run(
// db
// .from('contract_comment_edits')
// .select('*')
// .eq('comment_id', comment.id)
// .order('created_time', {ascending: false}),
// )
//
// // created_time is the time the row is created, but the row's content is the content before the edit, aka created_time is when the content is deleted and replaced
// const comments = data.map((edit, i) => {
// const comment = edit.data as Comment
// const editCreatedTime =
// i === data.length - 1 ? comment.createdTime : new Date(data[i + 1].created_time).valueOf()
//
// return {...comment, editCreatedTime}
// })
//
// setEdits(comments)
// }
//
// useEffect(() => {
// if (showEditHistory && edits === undefined) {
// loadEdits()
// }
// }, [showEditHistory])
//
// if (!comment.editedTime) return null
//
// return (
// <>
// <DateTimeTooltip time={comment.editedTime} placement={'top'}>
// <button
// className={
// 'text-ink-400 hover:bg-ink-50 mx-1 inline-block whitespace-nowrap rounded px-0.5 text-sm'
// }
// onClick={() => setShowEditHistory(true)}
// >
// (edited) {isClient && shortenedFromNow(comment.editedTime, t)}
// </button>
// </DateTimeTooltip>
// <Modal size={'md'} open={showEditHistory} setOpen={setShowEditHistory}>
// <div className={'bg-canvas-50 rounded-2xl p-4'}>
// <Title className="w-full text-center">Edit History</Title>
// {!edits ? (
// <LoadingIndicator />
// ) : (
// <Col className="gap-4">
// {edits.map((edit) => (
// <Col key={edit.id} className={'bg-ink-100 gap-2 rounded-xl rounded-tl-none p-2'}>
// <div className="text-ink-500 text-sm">
// {formatTimeShort(edit.editCreatedTime)}
// </div>
// <Content size="sm" className="mt-1 grow" content={edit.content || edit.text} />
// </Col>
// ))}
// </Col>
// )}
// </div>
// </Modal>
// </>
// )
// }

View File

@@ -1,4 +1,4 @@
import {PaperAirplaneIcon} from '@heroicons/react/solid'
import {PaperAirplaneIcon} from '@heroicons/react/24/solid'
import {Editor} from '@tiptap/react'
import clsx from 'clsx'
import {MAX_COMMENT_LENGTH, ReplyToUserInfo} from 'common/comment'

View File

@@ -1,8 +1,8 @@
import {Popover} from '@headlessui/react'
import {DotsHorizontalIcon} from '@heroicons/react/solid'
import {flip, offset, shift, useFloating} from '@floating-ui/react'
import {Popover, PopoverButton, PopoverPanel} from '@headlessui/react'
import {EllipsisHorizontalIcon} from '@heroicons/react/24/solid'
import clsx from 'clsx'
import {ReactNode, useState} from 'react'
import {usePopper} from 'react-popper'
import {ReactNode} from 'react'
import {AnimationOrNothing} from 'web/components/comments/dropdown-menu'
import {Col} from 'web/components/layout/col'
@@ -27,34 +27,32 @@ export default function DropdownMenu(props: {
withinOverflowContainer,
buttonContent,
} = props
const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>()
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>()
const {styles, attributes} = usePopper(referenceElement, popperElement, {
const {refs, floatingStyles} = useFloating({
strategy: withinOverflowContainer ? 'fixed' : 'absolute',
middleware: [offset(8), flip(), shift({padding: 8})],
})
const icon = props.icon ?? <DotsHorizontalIcon className="h-5 w-5" aria-hidden="true" />
const icon = props.icon ?? <EllipsisHorizontalIcon className="h-5 w-5" aria-hidden="true" />
return (
<Popover className={clsx('relative inline-block text-left', className)}>
{({open}) => (
<>
<Popover.Button
ref={setReferenceElement}
<PopoverButton
ref={refs.setReference}
className={clsx('text-ink-500 hover:text-ink-800 flex items-center', buttonClass)}
onClick={(e: any) => {
e.stopPropagation()
}}
onClick={(e: React.MouseEvent<HTMLButtonElement>) => e.stopPropagation()}
disabled={buttonDisabled}
>
<span className="sr-only">Open options</span>
{buttonContent ? buttonContent(open) : icon}
</Popover.Button>
</PopoverButton>
<AnimationOrNothing show={open} animate={!withinOverflowContainer}>
<Popover.Panel
ref={setPopperElement}
style={styles.popper}
{...attributes.popper}
<PopoverPanel
ref={refs.setFloating}
style={floatingStyles}
className={clsx(
'bg-canvas-0 ring-ink-1000 z-30 mt-2 rounded-md shadow-lg ring-1 ring-opacity-5 focus:outline-none',
menuWidth ?? 'w-34',
@@ -62,8 +60,8 @@ export default function DropdownMenu(props: {
'p-1',
)}
>
<Col className={'gap-1'}>{items.map((item) => item)}</Col>
</Popover.Panel>
<Col className="gap-1">{items}</Col>
</PopoverPanel>
</AnimationOrNothing>
</>
)}

View File

@@ -1,9 +1,9 @@
import {Popover, Transition} from '@headlessui/react'
import {ChevronDownIcon, ChevronUpIcon, DotsHorizontalIcon} from '@heroicons/react/solid'
import {flip, offset, shift, useFloating} from '@floating-ui/react'
import {Popover, PopoverButton, PopoverPanel, Transition} from '@headlessui/react'
import {ChevronDownIcon, ChevronUpIcon, EllipsisHorizontalIcon} from '@heroicons/react/24/solid'
import clsx from 'clsx'
import {toKey} from 'common/parsing'
import {Fragment, ReactNode, useState} from 'react'
import {usePopper} from 'react-popper'
import {Fragment, ReactNode} from 'react'
import {Col} from 'web/components/layout/col'
import {Row} from 'web/components/layout/row'
import {useT} from 'web/lib/locale'
@@ -39,33 +39,32 @@ export default function DropdownMenu(props: {
withinOverflowContainer,
buttonContent,
} = props
const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>()
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>()
const {styles, attributes} = usePopper(referenceElement, popperElement, {
const {refs, floatingStyles} = useFloating({
strategy: withinOverflowContainer ? 'fixed' : 'absolute',
middleware: [offset(8), flip(), shift({padding: 8})],
})
const icon = props.icon ?? <DotsHorizontalIcon className="h-5 w-5" aria-hidden="true" />
const icon = props.icon ?? <EllipsisHorizontalIcon className="h-5 w-5" aria-hidden="true" />
return (
<Popover className={clsx('relative inline-block text-left', className)}>
{({open, close}) => (
<>
<Popover.Button
ref={setReferenceElement}
<PopoverButton
ref={refs.setReference}
className={clsx('text-ink-500 hover-bold flex items-center', buttonClass)}
onClick={(e: any) => {
e.stopPropagation()
}}
onClick={(e: React.MouseEvent<HTMLButtonElement>) => e.stopPropagation()}
disabled={buttonDisabled}
>
<span className="sr-only">Open options</span>
{buttonContent ? buttonContent(open) : icon}
</Popover.Button>
</PopoverButton>
<AnimationOrNothing show={open} animate={!withinOverflowContainer}>
<Popover.Panel
ref={setPopperElement}
style={styles.popper}
{...attributes.popper}
<PopoverPanel
ref={refs.setFloating}
style={floatingStyles}
className={clsx(
'bg-canvas-0 ring-ink-1000 z-30 mt-2 rounded-md shadow-lg ring-1 ring-opacity-5 focus:outline-none',
menuWidth ?? 'w-34',
@@ -74,36 +73,33 @@ export default function DropdownMenu(props: {
)}
>
{items.map((item) => (
<div key={item.name}>
<button
onClick={(e) => {
e.stopPropagation()
e.preventDefault()
item.onClick()
if (closeOnClick) {
close()
}
}}
className={clsx(
selectedItemName && item.name == selectedItemName
? 'bg-primary-100'
: 'hover:bg-canvas-100 hover:text-ink-900',
'text-ink-700',
'flex w-full gap-2 px-4 py-2 text-left text-sm rounded-md',
)}
>
{item.icon && <div className="w-5">{item.icon}</div>}
{item.name}
</button>
</div>
<button
key={item.name}
onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
e.stopPropagation()
e.preventDefault()
item.onClick()
if (closeOnClick) close()
}}
className={clsx(
selectedItemName && item.name === selectedItemName
? 'bg-primary-100'
: 'hover:bg-canvas-100 hover:text-ink-900',
'text-ink-700 flex w-full gap-2 px-4 py-2 text-left text-sm rounded-md',
)}
>
{item.icon && <div className="w-5">{item.icon}</div>}
{item.name}
</button>
))}
</Popover.Panel>
</PopoverPanel>
</AnimationOrNothing>
</>
)}
</Popover>
)
}
export const AnimationOrNothing = (props: {
animate: boolean
show: boolean
@@ -134,7 +130,6 @@ export function DropdownOptions(props: {
translationPrefix?: string
}) {
const {items, onClick, activeKey, translationPrefix} = props
const t = useT()
const translateOption = (key: string, value: string) => {
@@ -143,25 +138,23 @@ export function DropdownOptions(props: {
}
return (
<Col className={''}>
<Col>
{Object.entries(items).map(([key, item]) => (
<div key={key}>
<button
onClick={(e) => {
e.stopPropagation()
e.preventDefault()
onClick(key)
}}
className={clsx(
key == activeKey ? 'bg-primary-100' : 'hover:bg-canvas-100 hover:text-ink-900',
'text-ink-700',
'flex w-full gap-2 px-4 py-2 text-left text-sm rounded-md',
)}
>
{item.icon && <div className="w-5">{item.icon}</div>}
{translateOption(key, item.label ?? item)}
</button>
</div>
<button
key={key}
onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
e.stopPropagation()
e.preventDefault()
onClick(key)
}}
className={clsx(
key === activeKey ? 'bg-primary-100' : 'hover:bg-canvas-100 hover:text-ink-900',
'text-ink-700 flex w-full gap-2 px-4 py-2 text-left text-sm rounded-md',
)}
>
{item.icon && <div className="w-5">{item.icon}</div>}
{translateOption(key, item.label ?? item)}
</button>
))}
</Col>
)

View File

@@ -83,7 +83,7 @@ export function EmbedModal(props: {
className="bg-canvas-50 border-ink-300 placeholder:text-ink-300 focus:border-primary-500 focus:ring-primary-500 block w-full rounded-md shadow-sm sm:text-sm"
placeholder="e.g. https://www.youtube.com/watch?v=dQw4w9WgXcQ"
value={input}
onChange={(e) => setInput(e.target.value)}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setInput(e.target.value)}
/>
{embed && <div dangerouslySetInnerHTML={{__html: embed}}></div>}

View File

@@ -1,4 +1,4 @@
import {CheckIcon, LinkIcon, TrashIcon} from '@heroicons/react/solid'
import {CheckIcon, LinkIcon, TrashIcon} from '@heroicons/react/24/solid'
import {Editor} from '@tiptap/core'
import {BubbleMenu} from '@tiptap/react'
import clsx from 'clsx'
@@ -71,7 +71,7 @@ export function FloatingFormatMenu(props: {
inputMode="url"
className="h-5 border-0 bg-inherit text-sm !shadow-none !ring-0"
placeholder="Type or paste a link"
onChange={(e) => setUrl(e.target.value)}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setUrl(e.target.value)}
/>
<button onClick={() => (setLink(), setUrl(null))}>
<CheckIcon className="h-5 w-5" />

View File

@@ -1,5 +1,5 @@
import {EmojiHappyIcon} from '@heroicons/react/outline'
import {CodeIcon, PhotographIcon} from '@heroicons/react/solid'
import {FaceSmileIcon} from '@heroicons/react/24/outline'
import {CodeBracketIcon, PhotoIcon} from '@heroicons/react/24/solid'
import {Editor} from '@tiptap/react'
import {MouseEventHandler, useState} from 'react'
import {Row} from 'web/components/layout/row'
@@ -30,13 +30,13 @@ export function StickyFormatMenu(props: {
<ToolbarButton
key={'embed-button'}
label={t('sticky_format_menu.add_embed', 'Add embed')}
onClick={(e) => {
onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
e.stopPropagation()
e.preventDefault()
setIframeOpen(true)
}}
>
<CodeIcon className="h-5 w-5" aria-hidden="true" />
<CodeBracketIcon className="h-5 w-5" aria-hidden="true" />
</ToolbarButton>
)}
<ToolbarButton
@@ -44,7 +44,7 @@ export function StickyFormatMenu(props: {
label={t('sticky_format_menu.add_emoji', 'Add emoji')}
onClick={() => insertEmoji(editor)}
>
<EmojiHappyIcon className="h-5 w-5" />
<FaceSmileIcon className="h-5 w-5" />
</ToolbarButton>
<EmbedModal editor={editor} open={iframeOpen} setOpen={setIframeOpen} />
@@ -69,7 +69,7 @@ function UploadButton(props: {upload: UploadMutation}) {
className="hover:text-ink-700 disabled:text-ink-300 active:text-ink-800 text-ink-400 relative flex rounded px-3 py-1 pl-4 transition-colors"
>
<Row className={'items-center justify-start gap-2'}>
<PhotographIcon className="h-5 w-5" aria-hidden="true" />
<PhotoIcon className="h-5 w-5" aria-hidden="true" />
{upload?.isLoading && (
<LoadingIndicator
className="absolute bottom-0 left-0 right-0 top-0"

View File

@@ -1,16 +1,15 @@
import {computePosition, flip, offset, shift} from '@floating-ui/dom'
import type {MentionOptions} from '@tiptap/extension-mention'
import {ReactRenderer} from '@tiptap/react'
import {beginsWith} from 'common/util/parse'
import {sortBy} from 'lodash'
import tippy from 'tippy.js'
import {searchUsers} from 'web/lib/supabase/users'
import {MentionList} from './mention-list'
type Render = Suggestion['render']
type Render = Suggestion['render']
type Suggestion = MentionOptions['suggestion']
// copied from https://tiptap.dev/api/nodes/mention#usage
export const mentionSuggestion: Suggestion = {
allowedPrefixes: [' '],
items: async ({query}) =>
@@ -23,53 +22,63 @@ export const mentionSuggestion: Suggestion = {
export function makeMentionRender(mentionList: any): Render {
return () => {
let component: ReactRenderer
let popup: ReturnType<typeof tippy>
let container: HTMLDivElement
const updatePosition = (clientRect: (() => DOMRect | null) | null | undefined) => {
if (!clientRect || !container) return
const rect = clientRect()
if (!rect) return
// Virtual element from the cursor position
const virtualEl = {
getBoundingClientRect: () => rect,
}
computePosition(virtualEl as Element, container, {
placement: 'bottom-start',
middleware: [offset(8), flip(), shift({padding: 8})],
}).then(({x, y}) => {
Object.assign(container.style, {
left: `${x}px`,
top: `${y}px`,
})
})
}
return {
onStart: (props) => {
onStart(props) {
component = new ReactRenderer(mentionList, {
props,
editor: props.editor,
})
if (!props.clientRect) {
return
}
popup = tippy('body', {
getReferenceClientRect: props.clientRect as any,
appendTo: () => document.body,
content: component?.element,
showOnCreate: true,
interactive: true,
trigger: 'manual',
placement: 'bottom-start',
})
container = document.createElement('div')
container.style.cssText = 'position:fixed;z-index:9999;'
container.appendChild(component.element)
document.body.appendChild(container)
updatePosition(props.clientRect)
},
onUpdate(props) {
component?.updateProps(props)
if (!props.clientRect) {
return
}
popup?.[0].setProps({
getReferenceClientRect: props.clientRect as any,
})
updatePosition(props.clientRect)
},
onKeyDown(props) {
if (props.event.key)
if (
props.event.key === 'Escape' ||
// Also break out of the mention if the tooltip isn't visible
(props.event.key === 'Enter' && !popup?.[0].state.isShown)
) {
popup?.[0].destroy()
component?.destroy()
return false
}
if (
props.event.key === 'Escape' ||
(props.event.key === 'Enter' && !container?.isConnected)
) {
container?.remove()
component?.destroy()
return false
}
return (component?.ref as any)?.onKeyDown(props)
},
onExit() {
popup?.[0].destroy()
container?.remove()
component?.destroy()
},
}

View File

@@ -129,7 +129,7 @@ export function CreateEventModal(props: {
}
const handleChange = (
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>,
e: React.ChangeEvent<HTMLTextAreaElement | HTMLTextAreaElement | HTMLSelectElement>,
) => {
const {name, value} = e.target
setFormData((prev) => ({...prev, [name]: value}))

View File

@@ -1,5 +1,5 @@
import {ChevronDownIcon, ChevronUpIcon} from '@heroicons/react/outline'
import {XIcon} from '@heroicons/react/solid'
import {ChevronDownIcon, ChevronUpIcon} from '@heroicons/react/24/outline'
import {XMarkIcon} from '@heroicons/react/24/solid'
import clsx from 'clsx'
import {FilterFields} from 'common/filters'
import {formatFilters, SKIPPED_FORMAT_FILTERS_KEYS} from 'common/filters-format'
@@ -177,7 +177,7 @@ function SelectedFiltersSummary(props: {
onClick={filter.onClear}
className="hover:text-gray-500 dark:hover:text-gray-50"
>
<XIcon className="h-3 w-3" />
<XMarkIcon className="h-3 w-3" />
</button>
</Row>
))}

View File

@@ -1,4 +1,4 @@
import {XIcon} from '@heroicons/react/solid'
import {XMarkIcon} from '@heroicons/react/24/solid'
import clsx from 'clsx'
import {OriginLocation} from 'common/filters'
import {formatDistance, kmToMiles, milesToKm} from 'common/measurement-utils'
@@ -115,7 +115,7 @@ export function LocationFilter(props: {
<Row className="items-center gap-1">
<Input
value={query}
onChange={(e) => setQuery(e.target.value)}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setQuery(e.target.value)}
placeholder={t('filter.location.search_city', 'Search city...')}
className="h-8 w-full rounded-none border-0 bg-transparent px-1 focus:border-b focus:ring-0 focus:ring-transparent"
autoFocus
@@ -201,7 +201,7 @@ function LocationResults(props: {
className="hover:bg-primary-200 hover:text-ink-950 cursor-pointer px-4 py-2 transition-colors"
>
<Row className="items-center gap-2">
<XIcon className="h-4 w-4 text-ink-400" aria-label={t('common.close', 'Close')} />
<XMarkIcon className="h-4 w-4 text-ink-400" aria-label={t('common.close', 'Close')} />
<span>{t('filter.location.set_any_city', 'Set to Any City')}</span>
</Row>
</button>

View File

@@ -24,7 +24,7 @@ export function MyMatchesToggle(props: {
type="checkbox"
className="border-ink-300 bg-canvas-0 dark:border-ink-500 text-primary-600 focus:ring-primary-500 h-4 w-4 rounded hover:bg-canvas-200"
checked={on}
onChange={(e) => setYourFilters(e.target.checked)}
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => setYourFilters(e.target.checked)}
/>
<label htmlFor={label} className={clsx('text-ink-600 ml-2')}>
{label}

View File

@@ -1,8 +1,8 @@
import {QuestionMarkCircleIcon} from '@heroicons/react/outline'
import {QuestionMarkCircleIcon} from '@heroicons/react/24/outline'
import {DisplayUser} from 'common/api/user-types'
import {FilterFields} from 'common/filters'
import {Profile} from 'common/profiles/profile'
import {forwardRef, useEffect, useRef, useState} from 'react'
import {forwardRef, ReactElement, useEffect, useRef, useState} from 'react'
import toast from 'react-hot-toast'
import {IoFilterSharp} from 'react-icons/io5'
import {Button} from 'web/components/buttons/button'
@@ -116,7 +116,7 @@ export const Search = forwardRef<
highlightFilters?: boolean
highlightSort?: boolean
setOpenFiltersModal?: (open: boolean) => void
filtersElement: JSX.Element
filtersElement: ReactElement
}
>((props, ref) => {
const {
@@ -218,7 +218,7 @@ export const Search = forwardRef<
value={filters.name ?? ''}
placeholder={placeholder}
className={'w-full max-w-xs'}
onChange={(e) => {
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
updateFilter({name: e.target.value})
}}
/>
@@ -226,7 +226,7 @@ export const Search = forwardRef<
<Row className="gap-2">
<Select
ref={sortSelectRef}
onChange={(e) => {
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => {
if (isOrderBy(e.target.value)) {
updateFilter({
orderBy: e.target.value,

View File

@@ -25,7 +25,9 @@ export function ShortBioToggle(props: {
type="checkbox"
className="border-ink-300 bg-canvas-0 dark:border-ink-500 text-primary-600 focus:ring-primary-500 h-4 w-4 rounded hover:bg-canvas-200 cursor-pointer"
checked={on}
onChange={(e) => updateFilter({shortBio: e.target.checked ? true : undefined})}
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) =>
updateFilter({shortBio: e.target.checked ? true : undefined})
}
/>
<label htmlFor={label} className={clsx('text-ink-600 ml-2')}>
{label}

View File

@@ -21,7 +21,9 @@ export function FontPicker(props: {className?: string} = {}) {
<select
id="font-picker"
value={font}
onChange={(e) => setFont(e.target.value as FontOption)}
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) =>
setFont(e.target.value as FontOption)
}
className={clsx(
'rounded-md border border-gray-300 px-2 py-1 text-sm bg-canvas-50',
className,

View File

@@ -12,7 +12,7 @@ export function LanguagePicker(props: {className?: string} = {}) {
<select
id="locale-picker"
value={locale}
onChange={(e) => setLocale(e.target.value)}
onChange={(e: React.ChangeEvent<HTMLSelectElement>) => setLocale(e.target.value)}
className={clsx(
'rounded-md border border-gray-300 px-2 py-1 text-sm bg-canvas-50',
className,

View File

@@ -1,8 +1,8 @@
import clsx from 'clsx'
import {forwardRef} from 'react'
import {ComponentPropsWithoutRef, forwardRef} from 'react'
export const Col = forwardRef(function Col(
props: JSX.IntrinsicElements['div'],
props: ComponentPropsWithoutRef<'div'>,
ref: React.Ref<HTMLDivElement>,
) {
const {children, className, ...rest} = props

View File

@@ -1,5 +1,5 @@
import {Dialog, Transition} from '@headlessui/react'
import {XIcon} from '@heroicons/react/outline'
import {XMarkIcon} from '@heroicons/react/24/outline'
import clsx from 'clsx'
import {Fragment, ReactNode, useEffect, useRef} from 'react'
@@ -94,7 +94,7 @@ export function Modal(props: {
position === 'top' && 'sm:-bottom-4 sm:top-auto sm:translate-y-full',
)}
>
<XIcon className="h-8 w-8" />
<XMarkIcon className="h-8 w-8" />
<div className="sr-only">Close</div>
</button>
)}

View File

@@ -1,8 +1,8 @@
import clsx from 'clsx'
import {forwardRef} from 'react'
import {ComponentPropsWithoutRef, forwardRef} from 'react'
export const Row = forwardRef(function Row(
props: JSX.IntrinsicElements['div'],
props: ComponentPropsWithoutRef<'div'>,
ref: React.Ref<HTMLDivElement>,
) {
const {children, className, ...rest} = props

View File

@@ -48,7 +48,7 @@ export function MinimalistTabs(props: TabProps & {activeIndex: number}) {
<a
href="#"
key={tab.queryString ?? tab.title}
onClick={(e) => {
onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault()
onClick?.(tab.queryString?.toLowerCase() ?? tab.title.toLowerCase(), i)
if (trackingName) {
@@ -108,7 +108,7 @@ export function ControlledTabs(props: TabProps & {activeIndex: number}) {
<a
href="#"
key={tab.queryString ?? tab.title}
onClick={(e) => {
onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault()
onClick?.(tab.queryString?.toLowerCase() ?? tab.title.toLowerCase(), i)

View File

@@ -1,4 +1,4 @@
import {HeartIcon, UserIcon} from '@heroicons/react/solid'
import {HeartIcon, UserIcon} from '@heroicons/react/24/solid'
import clsx from 'clsx'
import {Profile} from 'common/profiles/profile'
import Image from 'next/image'

View File

@@ -1,4 +1,4 @@
import {PlusIcon} from '@heroicons/react/solid'
import {PlusIcon} from '@heroicons/react/24/solid'
import clsx from 'clsx'
import {DisplayUser} from 'common/api/user-types'
import {APIError} from 'common/api/utils'

View File

@@ -118,7 +118,7 @@ export const MultiCheckbox = (props: {
<Input
value={newLabel}
placeholder={addPlaceholder ?? t('multi-checkbox.search_or_add', 'Search or add')}
onChange={(e) => {
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => {
setNewLabel(e.target.value)
setError(null)
}}

View File

@@ -1,5 +1,5 @@
import {Dialog, Transition} from '@headlessui/react'
import {MenuAlt3Icon} from '@heroicons/react/solid'
import {MenuAlt3Icon} from '@heroicons/react/24/solid'
import clsx from 'clsx'
import {User} from 'common/user'
import Link from 'next/link'

View File

@@ -1,4 +1,4 @@
import {LoginIcon, LogoutIcon} from '@heroicons/react/outline'
import {LoginIcon, LogoutIcon} from '@heroicons/react/24/outline'
import clsx from 'clsx'
import {ANDROID_APP_URL} from 'common/constants'
import {buildArray} from 'common/util/array'

View File

@@ -1,4 +1,4 @@
import {SparklesIcon} from '@heroicons/react/solid'
import {SparklesIcon} from '@heroicons/react/24/solid'
import clsx from 'clsx'
import {ENV_CONFIG} from 'common/envs/constants'
import {Notification} from 'common/notifications'
@@ -297,7 +297,7 @@ export function AvatarNotificationIcon(props: {
<Link
href={href}
target={href.startsWith('http') ? '_blank' : undefined}
onClick={(e) => e.stopPropagation}
onClick={(e: React.MouseEvent<HTMLButtonElement>) => e.stopPropagation}
>
<Avatar
username={sourceUserName}

View File

@@ -1,6 +1,6 @@
'use client'
import {BellIcon} from '@heroicons/react/outline'
import {BellIcon as SolidBellIcon} from '@heroicons/react/solid'
import {BellIcon} from '@heroicons/react/24/outline'
import {BellIcon as SolidBellIcon} from '@heroicons/react/24/solid'
import {NOTIFICATIONS_PER_PAGE} from 'common/notifications'
import {PrivateUser} from 'common/user'
import {usePathname} from 'next/navigation'

View File

@@ -1,4 +1,4 @@
import {PlusIcon, XIcon} from '@heroicons/react/solid'
import {PlusIcon, XMarkIcon} from '@heroicons/react/24/solid'
import {Editor} from '@tiptap/react'
import clsx from 'clsx'
import {
@@ -353,7 +353,7 @@ export const OptionalProfileUserForm = (props: {
min={18}
max={100}
error={!!ageError}
onChange={(e) => {
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => {
const value = e.target.value ? Number(e.target.value) : null
if (value !== null && value < 18) {
setAgeError(
@@ -378,7 +378,7 @@ export const OptionalProfileUserForm = (props: {
<Input
type="number"
data-testid="height-feet"
onChange={(e) => {
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => {
if (e.target.value === '') {
setHeightFeet(undefined)
} else {
@@ -397,7 +397,7 @@ export const OptionalProfileUserForm = (props: {
<Input
type="number"
data-testid="height-inches"
onChange={(e) => {
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => {
if (e.target.value === '') {
setHeightInches(undefined)
} else {
@@ -419,7 +419,7 @@ export const OptionalProfileUserForm = (props: {
<Input
type="number"
data-testid="height-centimeters"
onChange={(e) => {
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => {
if (e.target.value === '') {
setHeightFeet(undefined)
setHeightInches(undefined)
@@ -512,7 +512,9 @@ export const OptionalProfileUserForm = (props: {
</label>
<Textarea
data-testid="headline"
onChange={(e) => setProfile('headline', e.target.value)}
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) =>
setProfile('headline', e.target.value)
}
className={'w-full md:w-[700px] bg-canvas-0 border rounded-md p-2'}
value={profile['headline'] ?? undefined}
maxLength={250}
@@ -532,7 +534,7 @@ export const OptionalProfileUserForm = (props: {
<Input
data-testid="keywords"
type="text"
onChange={(e) => {
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => {
setKeywordsString(e.target.value)
const keywords = e.target.value
.split(',')
@@ -577,7 +579,7 @@ export const OptionalProfileUserForm = (props: {
<Select
data-testid="pref-age-min"
value={profile['pref_age_min'] ?? ''}
onChange={(e) => {
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => {
const newMin = e.target.value ? Number(e.target.value) : 18
const currentMax = profile['pref_age_max'] ?? 100
setProfile('pref_age_min', Math.min(newMin, currentMax))
@@ -597,7 +599,7 @@ export const OptionalProfileUserForm = (props: {
<Select
data-testid="pref-age-max"
value={profile['pref_age_max'] ?? ''}
onChange={(e) => {
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => {
const newMax = e.target.value ? Number(e.target.value) : 100
const currentMin = profile['pref_age_min'] ?? 18
setProfile('pref_age_max', Math.max(newMax, currentMin))
@@ -669,7 +671,7 @@ export const OptionalProfileUserForm = (props: {
<Input
data-testid="current-number-of-kids"
type="number"
onChange={(e) => {
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => {
const value = e.target.value === '' ? null : Number(e.target.value)
setProfile('has_kids', value)
}}
@@ -747,7 +749,9 @@ export const OptionalProfileUserForm = (props: {
<Input
data-testid="university"
type="text"
onChange={(e) => setProfile('university', e.target.value)}
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) =>
setProfile('university', e.target.value)
}
className={'w-52'}
value={profile['university'] ?? undefined}
/>
@@ -766,7 +770,9 @@ export const OptionalProfileUserForm = (props: {
<Input
data-testid="job-title"
type="text"
onChange={(e) => setProfile('occupation_title', e.target.value)}
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) =>
setProfile('occupation_title', e.target.value)
}
className={'w-52'}
value={profile['occupation_title'] ?? undefined}
/>
@@ -777,7 +783,9 @@ export const OptionalProfileUserForm = (props: {
<Input
data-testid="company"
type="text"
onChange={(e) => setProfile('company', e.target.value)}
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) =>
setProfile('company', e.target.value)
}
className={'w-52'}
value={profile['company'] ?? undefined}
/>
@@ -808,7 +816,9 @@ export const OptionalProfileUserForm = (props: {
<Input
data-testid="political-belief-details"
type="text"
onChange={(e) => setProfile('political_details', e.target.value)}
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) =>
setProfile('political_details', e.target.value)
}
className={'w-full sm:w-96'}
value={profile['political_details'] ?? undefined}
/>
@@ -830,7 +840,9 @@ export const OptionalProfileUserForm = (props: {
<Input
data-testid="religious-belief-details"
type="text"
onChange={(e) => setProfile('religious_beliefs', e.target.value)}
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) =>
setProfile('religious_beliefs', e.target.value)
}
className={'w-full sm:w-96'}
value={profile['religious_beliefs'] ?? undefined}
/>
@@ -928,7 +940,7 @@ export const OptionalProfileUserForm = (props: {
<Input
data-testid="alcohol-consumed-per-month"
type="number"
onChange={(e) => {
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => {
const value = e.target.value === '' ? null : Number(e.target.value)
setProfile('drinks_per_month', value)
}}
@@ -962,7 +974,7 @@ export const OptionalProfileUserForm = (props: {
<label className={clsx(labelClassName)}>Birthplace</label>
<Input
type="text"
onChange={(e) => setProfileState('born_in_location', e.target.value)}
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => setProfileState('born_in_location', e.target.value)}
className={'w-52'}
value={profile['born_in_location'] ?? undefined}
/>
@@ -996,11 +1008,13 @@ export const OptionalProfileUserForm = (props: {
<Input
type="text"
value={value!}
onChange={(e) => updateUserLink(platform, e.target.value)}
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) =>
updateUserLink(platform, e.target.value)
}
className="col-span-2 sm:col-span-1"
/>
<IconButton onClick={() => updateUserLink(platform, null)}>
<XIcon className="h-6 w-6" />
<XMarkIcon className="h-6 w-6" />
<div className="sr-only">{t('common.remove', 'Remove')}</div>
</IconButton>
</Fragment>
@@ -1022,7 +1036,9 @@ export const OptionalProfileUserForm = (props: {
: t('profile.optional.site_url', 'Site URL')
}
value={newLinkValue}
onChange={(e) => setNewLinkValue(e.target.value)}
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) =>
setNewLinkValue(e.target.value)
}
// disable password managers
autoComplete="off"
data-1p-ignore
@@ -1118,7 +1134,7 @@ const CitySearchBox = (props: {onCitySelected: (city: City | undefined) => void}
<>
<Input
value={query}
onChange={(e) => setQuery(e.target.value)}
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => setQuery(e.target.value)}
placeholder={t('profile.optional.search_city', 'Search city...')}
onFocus={() => setFocused(true)}
onBlur={(e) => {

View File

@@ -3,7 +3,7 @@ import {
HomeIcon,
NewspaperIcon,
QuestionMarkCircleIcon,
} from '@heroicons/react/outline'
} from '@heroicons/react/24/outline'
import {
CogIcon,
GlobeAltIcon,
@@ -12,7 +12,7 @@ import {
QuestionMarkCircleIcon as SolidQuestionIcon,
UserCircleIcon,
UsersIcon,
} from '@heroicons/react/solid'
} from '@heroicons/react/24/solid'
import clsx from 'clsx'
import {IS_MAINTENANCE} from 'common/constants'
import {Profile} from 'common/profiles/profile'

View File

@@ -1,4 +1,4 @@
import {ClockIcon} from '@heroicons/react/solid'
import {ClockIcon} from '@heroicons/react/24/solid'
import clsx from 'clsx'
import {
INVERTED_DIET_CHOICES,

View File

@@ -1,5 +1,5 @@
import {EyeOffIcon, FlagIcon} from '@heroicons/react/outline'
import {DotsHorizontalIcon, ReplyIcon} from '@heroicons/react/solid'
import {EyeSlashIcon, FlagIcon} from '@heroicons/react/24/outline'
import {EllipsisHorizontalIcon, ReplyIcon} from '@heroicons/react/24/solid'
import {Editor} from '@tiptap/react'
import clsx from 'clsx'
import {type Comment, MAX_COMMENT_LENGTH, ReplyToUserInfo} from 'common/comment'
@@ -273,7 +273,7 @@ function DotMenu(props: {onUser: User; comment: Comment; onHide: () => void}) {
<DropdownMenu
menuWidth={'w-36'}
closeOnClick={true}
icon={<DotsHorizontalIcon className="mt-[0.12rem] h-4 w-4" aria-hidden="true" />}
icon={<EllipsisHorizontalIcon className="mt-[0.12rem] h-4 w-4" aria-hidden="true" />}
items={buildArray(
user &&
comment.userId !== user.id && {
@@ -286,7 +286,7 @@ function DotMenu(props: {onUser: User; comment: Comment; onHide: () => void}) {
},
(isAdmin || isCurrentUser || isOwner) && {
name: comment.hidden ? 'Undelete' : 'Delete',
icon: <EyeOffIcon className="h-5 w-5 text-red-500" />,
icon: <EyeSlashIcon className="h-5 w-5 text-red-500" />,
onClick: async () => {
onHide()
await toast.promise(
@@ -326,7 +326,7 @@ function CommentActions(props: {
<Tooltip text="Reply" placement="bottom">
<IconButton
size={'xs'}
onClick={(e) => {
onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault()
e.stopPropagation()
onReplyClick(comment)

View File

@@ -123,7 +123,7 @@ function ProfilePreview(props: {
</span>
<button
className="text-primary-500 hover:text-primary-700 underline"
onClick={(e) => {
onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault()
e.stopPropagation()
onUndoHidden?.(profile.user_id)

View File

@@ -202,7 +202,9 @@ export function DeleteAccountSurveyModal() {
className="block w-full bg-canvas-0 rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
placeholder={t('delete_survey.other_placeholder', 'Please share more details')}
value={reasonFreeText}
onChange={(e) => setReasonFreeText(e.target.value)}
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) =>
setReasonFreeText(e.target.value)
}
/>
</div>
</div>

View File

@@ -1,4 +1,9 @@
import {DotsHorizontalIcon, EyeIcon, LockClosedIcon, PencilIcon} from '@heroicons/react/outline'
import {
EllipsisHorizontalIcon,
EyeIcon,
LockClosedIcon,
PencilIcon,
} from '@heroicons/react/24/outline'
import clsx from 'clsx'
import {debug} from 'common/logger'
import {Profile} from 'common/profiles/profile'
@@ -142,7 +147,7 @@ export default function ProfileHeader(props: {
>
<DropdownMenu
menuWidth={'w-52'}
icon={<DotsHorizontalIcon className="h-5 w-5" aria-hidden="true" />}
icon={<EllipsisHorizontalIcon className="h-5 w-5" aria-hidden="true" />}
items={[
{
name:

View File

@@ -93,7 +93,9 @@ export const ReportUser = (props: {user: User; closeModal: () => void}) => {
rows={2}
className={'border-ink-300 bg-canvas-0 -ml-2 rounded-md border p-2'}
value={otherReportType}
onChange={(e) => setOtherReportType(e.target.value)}
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) =>
setOtherReportType(e.target.value)
}
/>
</Col>
<Row className={'justify-between'}>

View File

@@ -1,4 +1,4 @@
import {EyeIcon, LockClosedIcon} from '@heroicons/react/outline'
import {EyeIcon, LockClosedIcon} from '@heroicons/react/24/outline'
import {Button} from 'web/components/buttons/button'
import {Col} from 'web/components/layout/col'
import {Modal} from 'web/components/layout/modal'

View File

@@ -127,7 +127,9 @@ const QuestionRow = (props: {row: rowFor<'compatibility_prompts'>; user: User})
className={'w-full max-w-xl'}
rows={3}
value={form.free_response ?? ''}
onChange={(e) => setForm({...form, free_response: e.target.value})}
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) =>
setForm({...form, free_response: e.target.value})
}
onBlur={() => submitAnswer(form)}
/>
) : answer_type === 'multiple_choice' && row.multiple_choice_options ? (
@@ -148,7 +150,9 @@ const QuestionRow = (props: {row: rowFor<'compatibility_prompts'>; user: User})
className={'w-20'}
max={1000}
min={0}
onChange={(e) => setForm({...form, integer: Number(e.target.value)})}
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) =>
setForm({...form, integer: Number(e.target.value)})
}
value={form.integer ?? undefined}
onBlur={() => submitAnswer(form)}
/>
@@ -188,7 +192,9 @@ export const IndividualQuestionRow = (props: {
className={'w-full'}
rows={3}
value={form.free_response ?? ''}
onChange={(e) => setForm({...form, free_response: e.target.value})}
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) =>
setForm({...form, free_response: e.target.value})
}
/>
) : answer_type === 'multiple_choice' && row.multiple_choice_options ? (
<RadioToggleGroup
@@ -205,7 +211,9 @@ export const IndividualQuestionRow = (props: {
className={'w-20'}
max={1000}
min={0}
onChange={(e) => setForm({...form, integer: Number(e.target.value)})}
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) =>
setForm({...form, integer: Number(e.target.value)})
}
value={form.integer ?? undefined}
/>
) : null}

View File

@@ -108,7 +108,7 @@ export const RequiredProfileUserForm = (props: {
type="text"
placeholder="Display name"
value={name}
onChange={(e) => {
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => {
updateUserState({name: e.target.value || ''})
}}
onBlur={updateDisplayName}
@@ -132,7 +132,7 @@ export const RequiredProfileUserForm = (props: {
type="text"
placeholder="Username"
value={username}
onChange={(e) => {
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => {
updateUserState({
username: e.target.value || '',
errorUsername: '',

View File

@@ -1,4 +1,4 @@
import {XIcon} from '@heroicons/react/outline'
import {XMarkIcon} from '@heroicons/react/24/outline'
import clsx from 'clsx'
import {DisplayUser} from 'common/api/user-types'
import {FilterFields} from 'common/filters'
@@ -108,7 +108,7 @@ function ButtonModal(props: {
}}
className="inline-flex items-center justify-center h-8 w-8 rounded-full text-red-600 hover:bg-red-200 focus:outline-none focus:ring-2 focus:ring-red-400"
>
<XIcon className="h-6 w-6" />
<XMarkIcon className="h-6 w-6" />
</button>
</Row>
))}
@@ -241,7 +241,7 @@ function StarModal(props: {
}}
className="inline-flex items-center justify-center h-8 w-8 rounded-full text-red-600 hover:bg-red-200 focus:outline-none focus:ring-2 focus:ring-red-400"
>
<XIcon className="h-6 w-6" />
<XMarkIcon className="h-6 w-6" />
</button>
</Row>
))}

View File

@@ -1,5 +1,5 @@
import {Menu, Transition} from '@headlessui/react'
import {XIcon} from '@heroicons/react/outline'
import {XMarkIcon} from '@heroicons/react/24/outline'
import clsx from 'clsx'
import {Fragment, useEffect, useRef, useState} from 'react'
import {Button} from 'web/components/buttons/button'
@@ -82,7 +82,7 @@ export function SelectUsers(props: {
name="user name"
id="user name"
value={query}
onChange={(e) => setQuery(e.target.value)}
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => setQuery(e.target.value)}
placeholder="Search users..."
/>
</Col>
@@ -160,7 +160,7 @@ export function SelectUsers(props: {
color={'gray-white'}
size={'xs'}
>
<XIcon className="h-5 w-5" aria-hidden="true" />
<XMarkIcon className="h-5 w-5" aria-hidden="true" />
</Button>
</Row>
))}

View File

@@ -1,4 +1,4 @@
import {MoonIcon, SunIcon} from '@heroicons/react/outline'
import {MoonIcon, SunIcon} from '@heroicons/react/24/outline'
import clsx from 'clsx'
import {Row} from 'web/components/layout/row'
import {useTheme} from 'web/hooks/use-theme'

View File

@@ -1,4 +1,4 @@
import {DocumentReportIcon, LinkIcon} from '@heroicons/react/solid'
import {DocumentReportIcon, LinkIcon} from '@heroicons/react/24/solid'
import {Site} from 'common/socials'
import {ReactNode} from 'react'
import {LuBookmark, LuHandshake, LuHeart, LuUsers} from 'react-icons/lu'

View File

@@ -1,8 +1,9 @@
import {PrivateUser} from 'common/user'
import {ReactElement} from 'react'
import {CompassLoadingIndicator} from 'web/components/widgets/loading-indicator'
import {usePrivateUser} from 'web/hooks/use-user'
export function WithPrivateUser({children}: {children: (user: PrivateUser) => JSX.Element}) {
export function WithPrivateUser({children}: {children: (user: PrivateUser) => ReactElement}) {
const privateUser = usePrivateUser()
if (!privateUser) return <CompassLoadingIndicator />
return children(privateUser)

View File

@@ -46,7 +46,9 @@ export function VoteComponent() {
<select
id="orderBy"
value={orderBy}
onChange={(e) => setOrderBy(e.target.value as OrderBy)}
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) =>
setOrderBy(e.target.value as OrderBy)
}
className="rounded-md border border-gray-300 px-2 py-1 text-sm bg-canvas-50"
>
{ORDER_BY.map((key) => (
@@ -77,7 +79,7 @@ export function VoteComponent() {
value={title}
placeholder={t('vote.form.title_placeholder', 'Title')}
className={'w-full mb-2'}
onChange={(e) => {
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => {
setTitle(e.target.value)
}}
/>
@@ -87,7 +89,9 @@ export function VoteComponent() {
type="checkbox"
id="anonymous"
checked={isAnonymous}
onChange={(e) => setIsAnonymous(e.target.checked)}
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) =>
setIsAnonymous(e.target.checked)
}
className="h-4 w-4 rounded-md border-gray-300 text-blue-600 focus:ring-blue-500"
/>
<label htmlFor="anonymous">{t('vote.form.anonymous', 'Anonymous?')}</label>

View File

@@ -1,5 +1,5 @@
import {CheckCircleIcon} from '@heroicons/react/outline'
import {PlusIcon, XIcon} from '@heroicons/react/solid'
import {CheckCircleIcon} from '@heroicons/react/24/outline'
import {PlusIcon, XMarkIcon} from '@heroicons/react/24/solid'
import clsx from 'clsx'
import {User} from 'common/user'
import {buildArray} from 'common/util/array'
@@ -101,7 +101,7 @@ export const AddPhotosWidget = (props: {
</div>
)}
<Button
onClick={(e) => {
onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
e.stopPropagation()
const newUrls = (photo_urls ?? []).filter((u) => u !== url)
if (isPinned) setPinnedUrl(newUrls[0] ?? '')
@@ -111,7 +111,7 @@ export const AddPhotosWidget = (props: {
size={'2xs'}
className={clsx('bg-canvas-0 absolute right-0 top-0 !rounded-full !px-1 py-1')}
>
<XIcon className={'h-4 w-4'} />
<XMarkIcon className={'h-4 w-4'} />
</Button>
<Image
src={url}
@@ -123,11 +123,11 @@ export const AddPhotosWidget = (props: {
<textarea
// stop click bubbling so clicking/focusing the input doesn't pin the image
onClick={(e) => e.stopPropagation()}
onClick={(e: React.MouseEvent<HTMLTextAreaElement>) => e.stopPropagation()}
aria-label={`description for image ${index}`}
placeholder={t('add_photos.add_description', 'Add description')}
value={image_descriptions?.[url] ?? ''}
onChange={(e) => {
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => {
e.stopPropagation()
const v = e.target.value
setDescription(url, v)

View File

@@ -1,4 +1,4 @@
import {ExclamationIcon} from '@heroicons/react/solid'
import {ExclamationTriangleIcon} from '@heroicons/react/24/solid'
import clsx from 'clsx'
import {ReactNode} from 'react'
@@ -10,7 +10,7 @@ export function AlertBox(props: {title: string; className?: string; children?: R
return (
<Col className={clsx('border-warning bg-warning/10 w-full rounded-md border p-4', className)}>
<Row className="flex-shrink-0">
<ExclamationIcon className="fill-warning h-5 w-5" aria-hidden />
<ExclamationTriangleIcon className="fill-warning h-5 w-5" aria-hidden />
<div className="ml-3">
<h3 className="text-ink-800 text-sm font-medium">{title}</h3>

View File

@@ -1,4 +1,4 @@
import {UserIcon, UsersIcon} from '@heroicons/react/solid'
import {UserIcon, UsersIcon} from '@heroicons/react/24/solid'
import clsx from 'clsx'
import {floor} from 'lodash'
import Image from 'next/image'

View File

@@ -1,8 +1,8 @@
import clsx from 'clsx'
import {forwardRef} from 'react'
import {ComponentPropsWithoutRef, forwardRef} from 'react'
export const Card = forwardRef(function Card(
props: JSX.IntrinsicElements['div'],
props: ComponentPropsWithoutRef<'div'>,
ref: React.Ref<HTMLDivElement>,
) {
const {children, className, ...rest} = props

View File

@@ -1,4 +1,4 @@
import {ChevronLeftIcon, ChevronRightIcon} from '@heroicons/react/solid'
import {ChevronLeftIcon, ChevronRightIcon} from '@heroicons/react/24/solid'
import clsx from 'clsx'
import {throttle} from 'lodash'
import {forwardRef, ReactNode, Ref, useEffect, useRef, useState} from 'react'

View File

@@ -22,7 +22,7 @@ export function Checkbox(props: {
type="checkbox"
className="border-ink-300 bg-canvas-0 dark:border-ink-500 text-primary-600 focus:ring-primary-500 h-5 w-5 rounded hover:bg-canvas-300"
checked={checked}
onChange={(e) => toggle(e.target.checked)}
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => toggle(e.target.checked)}
disabled={disabled}
/>
<span

View File

@@ -28,7 +28,7 @@ export const ClickFrame = forwardRef(
// we put pointer-events:auto on links, buttons, and elements with class stop-prop,
// so they get caught by the stopPropagation below
className="pointer-events-none contents [&_.stop-prop]:pointer-events-auto [&_a]:pointer-events-auto [&_button]:pointer-events-auto"
onClick={(e) => e.stopPropagation()}
onClick={(e: React.MouseEvent<HTMLDivElement>) => e.stopPropagation()}
>
{children}
</div>

View File

@@ -1,4 +1,4 @@
import {ChevronDownIcon, ChevronUpIcon} from '@heroicons/react/solid'
import {ChevronDownIcon, ChevronUpIcon} from '@heroicons/react/24/solid'
import {JSONContent} from '@tiptap/react'
import clsx from 'clsx'
import {MouseEventHandler, useEffect, useRef, useState} from 'react'

View File

@@ -1,4 +1,4 @@
import {BadgeCheckIcon} from '@heroicons/react/solid'
import {CheckBadgeIcon} from '@heroicons/react/24/solid'
import clsx from 'clsx'
import {CompatibilityScore} from 'common/profiles/compatibility-score'
import {formatPercent} from 'common/util/format'
@@ -12,7 +12,7 @@ export const CompatibleBadge = (props: {compatibility: CompatibilityScore; class
return (
<Tooltip text={t('compatibility.tooltip', 'Compatibility score between you two')}>
<Row className={clsx('items-center gap-1 text-sm font-semibold', className)}>
<BadgeCheckIcon className="h-4 w-4" />
<CheckBadgeIcon className="h-4 w-4" />
{formatPercent(compatibility.score ?? 0)}{' '}
</Row>
</Tooltip>

View File

@@ -1,7 +1,6 @@
import {Popover} from '@headlessui/react'
import {flip, offset, shift, useFloating} from '@floating-ui/react'
import {Popover, PopoverButton, PopoverPanel} from '@headlessui/react'
import clsx from 'clsx'
import {useState} from 'react'
import {usePopper} from 'react-popper'
import {NewBadge} from 'web/components/new-badge'
import {AnimationOrNothing} from '../comments/dropdown-menu'
@@ -16,9 +15,7 @@ export function CustomizeableDropdown(props: {
closeOnClick?: boolean
withinOverflowContainer?: boolean
popoverClassName?: string
// When true, shows a tiny "new" badge at the top-left of the button
showNewBadge?: boolean
// Optional extra classes for the badge container (to tweak position/size)
newBadgeClassName?: string
}) {
const {
@@ -33,32 +30,30 @@ export function CustomizeableDropdown(props: {
showNewBadge,
newBadgeClassName,
} = props
const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>()
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>()
const {styles, attributes} = usePopper(referenceElement, popperElement, {
const {refs, floatingStyles} = useFloating({
strategy: withinOverflowContainer ? 'fixed' : 'absolute',
middleware: [offset(8), flip(), shift({padding: 8})],
})
return (
<Popover className={clsx('relative inline-block text-left', className)}>
{({open, close}) => (
<>
<Popover.Button
ref={setReferenceElement}
<PopoverButton
ref={refs.setReference}
className={clsx('flex items-center relative hover-bold', buttonClass)}
onClick={(e: any) => {
e.stopPropagation()
}}
onClick={(e: React.MouseEvent<HTMLButtonElement>) => e.stopPropagation()}
disabled={buttonDisabled}
>
{showNewBadge && <NewBadge classes={newBadgeClassName} />}
{buttonContent(open)}
</Popover.Button>
</PopoverButton>
<AnimationOrNothing show={open} animate={!withinOverflowContainer}>
<Popover.Panel
ref={setPopperElement}
style={styles.popper}
{...attributes.popper}
<PopoverPanel
ref={refs.setFloating}
style={floatingStyles}
className={clsx(
'bg-canvas-0 ring-ink-1000 z-30 rounded-md px-2 py-2 shadow-lg ring-1 ring-opacity-5 focus:outline-none',
menuWidth ?? 'w-36',
@@ -68,7 +63,7 @@ export function CustomizeableDropdown(props: {
{typeof dropdownMenuContent === 'function'
? dropdownMenuContent(close)
: dropdownMenuContent}
</Popover.Panel>
</PopoverPanel>
</AnimationOrNothing>
</>
)}

View File

@@ -23,7 +23,7 @@ export const EditInPlaceInput = (props: {
<ExpandingInput
className={className}
value={value}
onChange={(e) => setValue(e.target.value)}
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => setValue(e.target.value)}
onBlur={save}
onKeyDown={(e) => e.key === 'Enter' && save()}
autoFocus

View File

@@ -1,4 +1,4 @@
import {XIcon} from '@heroicons/react/solid'
import {XMarkIcon} from '@heroicons/react/24/solid'
import clsx from 'clsx'
import Image from 'next/image'
import {useState} from 'react'
@@ -165,13 +165,13 @@ const PhotoItem = ({
/>
{onDelete && (
<button
onClick={(e) => {
onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
e.stopPropagation()
onDelete(url)
}}
className="bg-canvas-0 hover:bg-ink-100 absolute right-2 top-2 rounded-full p-1"
>
<XIcon className="h-5 w-5" />
<XMarkIcon className="h-5 w-5" />
</button>
)}
</div>

View File

@@ -1,4 +1,4 @@
import {DocumentTextIcon} from '@heroicons/react/outline'
import {DocumentTextIcon} from '@heroicons/react/24/outline'
import {JSONContent} from '@tiptap/react'
import clsx from 'clsx'
import {MouseEventHandler, ReactNode, useRef, useState} from 'react'

View File

@@ -1,4 +1,4 @@
import {EyeIcon, EyeOffIcon} from '@heroicons/react/outline'
import {EyeIcon, EyeSlashIcon} from '@heroicons/react/24/outline'
import clsx from 'clsx'
import {useMemo, useState} from 'react'
import {Tooltip} from 'web/components/widgets/tooltip'
@@ -88,7 +88,7 @@ export function HideProfileButton(props: HideProfileButtonProps) {
}
>
{hidden || eyeOff ? (
<EyeOffIcon className={clsx('h-5 w-5 guidance', iconClassName)} />
<EyeSlashIcon className={clsx('h-5 w-5 guidance', iconClassName)} />
) : (
<EyeIcon className={clsx('h-5 w-5 guidance', iconClassName)} />
)}

View File

@@ -1,4 +1,4 @@
import {InformationCircleIcon} from '@heroicons/react/solid'
import {InformationCircleIcon} from '@heroicons/react/24/solid'
import clsx from 'clsx'
import {ReactNode} from 'react'

View File

@@ -1,5 +1,5 @@
import {Placement} from '@floating-ui/react'
import {InformationCircleIcon} from '@heroicons/react/outline'
import {InformationCircleIcon} from '@heroicons/react/24/outline'
import clsx from 'clsx'
import {Tooltip} from './tooltip'

View File

@@ -1,12 +1,12 @@
import clsx from 'clsx'
import {forwardRef, Ref} from 'react'
import {ComponentPropsWithoutRef, forwardRef, Ref} from 'react'
/** Text input. Wraps html `<input>` */
export const Input = forwardRef(
(
props: {
error?: boolean
} & JSX.IntrinsicElements['input'],
} & ComponentPropsWithoutRef<'input'>,
ref: Ref<HTMLInputElement>,
) => {
const {error, className, ...rest} = props

View File

@@ -1,4 +1,4 @@
import {HeartIcon} from '@heroicons/react/outline'
import {HeartIcon} from '@heroicons/react/24/outline'
import clsx from 'clsx'
import {Profile} from 'common/profiles/profile'
import {useState} from 'react'

View File

@@ -1,4 +1,4 @@
import {UserIcon} from '@heroicons/react/solid'
import {UserIcon} from '@heroicons/react/24/solid'
import {LikeData, ShipData} from 'common/api/profile-types'
import {Profile} from 'common/profiles/profile'
import {keyBy, orderBy} from 'lodash'

View File

@@ -1,4 +1,4 @@
import {ChevronLeftIcon, ChevronRightIcon} from '@heroicons/react/solid'
import {ChevronLeftIcon, ChevronRightIcon} from '@heroicons/react/24/solid'
import clsx from 'clsx'
import {range} from 'lodash'
import {usePathname, useRouter} from 'next/navigation'

Some files were not shown because too many files have changed in this diff Show More