Fix linting

This commit is contained in:
MartinBraquet
2026-02-19 16:34:39 +01:00
parent d89bf2d92a
commit 2c90fc6cc8
20 changed files with 133 additions and 89 deletions

View File

@@ -4,6 +4,7 @@
"semi": false,
"trailingComma": "es5",
"singleQuote": true,
"bracketSpacing": false,
"plugins": ["prettier-plugin-sql"],
"overrides": [
{

View File

@@ -1,10 +1,10 @@
import pgPromise, {IDatabase, ITask} from 'pg-promise'
import {log, metrics} from '../utils'
import {IClient, type IConnectionParameters} from 'pg-promise/typescript/pg-subset'
import {IClient, type IConnectionParameters,} from 'pg-promise/typescript/pg-subset'
import {HOUR_MS} from 'common/util/time'
import {METRICS_INTERVAL_MS} from 'shared/monitoring/metric-writer'
import {getMonitoringContext} from 'shared/monitoring/context'
import {ENV_CONFIG} from "common/envs/constants";
import {ENV_CONFIG} from 'common/envs/constants'
export {SupabaseClient} from 'common/supabase/utils'
@@ -32,8 +32,10 @@ export const pgp = pgPromise({
pgp.pg.types.setTypeParser(20, (value: any) => parseInt(value, 10))
pgp.pg.types.setTypeParser(1700, parseFloat) // Type Id 1700 = NUMERIC
export type SupabaseTransaction = ITask<{}>
export type SupabaseDirectClient = IDatabase<{}, IClient> | SupabaseTransaction
export type SupabaseTransaction = ITask<object>
export type SupabaseDirectClient =
| IDatabase<object, IClient>
| SupabaseTransaction
export function getInstanceId() {
return ENV_CONFIG.supabaseInstanceId
@@ -87,7 +89,7 @@ const newClient = (
}
// Use one connection to avoid WARNING: Creating a duplicate database object for the same connection.
let pgpDirect: IDatabase<{}, IClient> | null = null
let pgpDirect: IDatabase<object, IClient> | null = null
export function createSupabaseDirectClient(
instanceId?: string,
@@ -128,7 +130,7 @@ export function createSupabaseDirectClient(
return (pgpDirect = client)
}
let shortTimeoutPgpClient: IDatabase<{}, IClient> | null = null
let shortTimeoutPgpClient: IDatabase<object, IClient> | null = null
export const createShortTimeoutDirectClient = () => {
if (shortTimeoutPgpClient) return shortTimeoutPgpClient
shortTimeoutPgpClient = newClient({

View File

@@ -7,7 +7,7 @@ export const isProd = () => {
return process.env.NEXT_PUBLIC_FIREBASE_ENV?.toUpperCase() == 'PROD'
} else {
// For local scripts and cloud functions
// eslint-disable-next-line @typescript-eslint/no-var-requires
// eslint-disable-next-line @typescript-eslint/no-require-imports
const admin = require('firebase-admin')
return admin.app().options.projectId === 'compass-130ba'
}

View File

@@ -1,7 +1,7 @@
export const safeJsonParse = (json: string | undefined | null) => {
try {
return JSON.parse(json ?? '')
} catch (e) {
return JSON.parse(json ?? '')
} catch {
return null
}
}

View File

@@ -45,7 +45,19 @@
"prepare": "npx husky"
},
"lint-staged": {
"*.{ts,tsx,js,jsx}": [
"web/**/*.{ts,tsx,js,jsx}": [
"next lint --fix --file",
"next lint --max-warnings 0 --file"
],
"common/**/*.{ts,tsx,js,jsx}": [
"eslint --fix",
"eslint --max-warnings 0"
],
"backend/api/**/*.{ts,tsx,js,jsx}": [
"eslint --fix",
"eslint --max-warnings 0"
],
"backend/shared/**/*.{ts,tsx,js,jsx}": [
"eslint --fix",
"eslint --max-warnings 0"
]

View File

@@ -15,6 +15,7 @@ module.exports = {
'prettier',
],
rules: {
"react/prop-types": "off",
'react/display-name': 'off',
'react/no-unescaped-entities': 'off',
'react/jsx-no-target-blank': 'off',

View File

@@ -29,11 +29,7 @@ export function AddCompatibilityQuestionButton(props: {
if (!user) return null
return (
<>
<button
type="button"
onClick={() => setOpen(true)}
className="text-sm"
>
<button type="button" onClick={() => setOpen(true)} className="text-sm">
{t('answers.add.submit_own', 'submit your own!')}
</button>
<AddCompatibilityQuestionModal
@@ -55,9 +51,8 @@ function AddCompatibilityQuestionModal(props: {
onClose?: () => void
}) {
const {open, setOpen, user, onClose} = props
const [dbQuestion, setDbQuestion] = useState<rowFor<'compatibility_prompts'> | null>(
null
)
const [dbQuestion, setDbQuestion] =
useState<rowFor<'compatibility_prompts'> | null>(null)
const afterAddQuestion = (newQuestion: rowFor<'compatibility_prompts'>) => {
setDbQuestion(newQuestion)
console.debug('setDbQuestion', newQuestion)
@@ -125,12 +120,15 @@ function CreateCompatibilityModalContent(props: {
const generateJson = () => {
// Note the change in the generic type
return options.reduce((obj, item, index) => {
if (item.trim() !== '') {
obj[item] = index // Mapping each option to its index
}
return obj
}, {} as Record<string, number>)
return options.reduce(
(obj, item, index) => {
if (item.trim() !== '') {
obj[item] = index // Mapping each option to its index
}
return obj
},
{} as Record<string, number>
)
}
const onAddQuestion = useEvent(async () => {
@@ -138,7 +136,7 @@ function CreateCompatibilityModalContent(props: {
const data = {
question: question,
options: generateJson(),
};
}
const newQuestion = await api('create-compatibility-question', data)
console.debug('create-compatibility-question', newQuestion, data)
const q = newQuestion?.question
@@ -146,8 +144,13 @@ function CreateCompatibilityModalContent(props: {
afterAddQuestion(q as rowFor<'compatibility_prompts'>)
}
track('create compatibility question')
} catch (e) {
toast.error(t('answers.add.error_create', 'Error creating compatibility question. Try again?'))
} catch (_e) {
toast.error(
t(
'answers.add.error_create',
'Error creating compatibility question. Try again?'
)
)
}
})
@@ -176,7 +179,9 @@ function CreateCompatibilityModalContent(props: {
value={options[index]}
onChange={(e) => onOptionChange(index, e.target.value)}
className="w-full"
placeholder={t('answers.add.option_placeholder', 'Option {n}', {n: String(index + 1)})}
placeholder={t('answers.add.option_placeholder', 'Option {n}', {
n: String(index + 1),
})}
rows={1}
maxLength={MAX_ANSWER_LENGTH}
/>

View File

@@ -67,7 +67,7 @@ export const submitCompatibilityAnswer = async (
explanation: input.explanation ?? null,
})
// Track only if the upsert succeeds
// Track only if upsert succeeds
track('answer compatibility question', {
...newAnswer,
})
@@ -179,7 +179,7 @@ export function AnswerCompatibilityQuestionContent(props: {
{shortenedPopularity && (
<Row className="text-ink-500 select-none items-center text-sm">
<Tooltip
text={t('answers.content.people_answered', '{count} people have answered this question', { count: String(shortenedPopularity) })}
text={t('answers.content.people_answered', '{count} people have answered this question', {count: String(shortenedPopularity)})}
>
{shortenedPopularity}
</Tooltip>
@@ -204,7 +204,8 @@ export function AnswerCompatibilityQuestionContent(props: {
/>
</Col>
<Col className="gap-2">
<span className="text-ink-500 text-sm">{t('answers.content.answers_you_accept', "Answers you'll accept")}</span>
<span
className="text-ink-500 text-sm">{t('answers.content.answers_you_accept', "Answers you'll accept")}</span>
<MultiSelectAnswers
values={answer.pref_choices ?? []}
setValue={(choice) =>

View File

@@ -1,12 +1,10 @@
import { LinkIcon, CheckIcon, TrashIcon } from '@heroicons/react/solid'
import { Editor } from '@tiptap/core'
import { BubbleMenu } from '@tiptap/react'
import {CheckIcon, LinkIcon, TrashIcon} from '@heroicons/react/solid'
import {Editor} from '@tiptap/core'
import {BubbleMenu} from '@tiptap/react'
import clsx from 'clsx'
import { getUrl } from 'common/util/parse'
import { useState } from 'react'
import BoldIcon from 'web/lib/icons/bold-icon.svg'
import ItalicIcon from 'web/lib/icons/italic-icon.svg'
import TypeIcon from 'web/lib/icons/type-icon.svg'
import {getUrl} from 'common/util/parse'
import {useState} from 'react'
import {Bold, Italic, Type} from 'lucide-react'
// see https://tiptap.dev/guide/menus
@@ -40,14 +38,14 @@ export function FloatingFormatMenu(props: {
{advanced && (
<>
<IconButton
icon={TypeIcon}
icon={Type}
onClick={() =>
editor.chain().focus().toggleHeading({ level: 1 }).run()
}
isActive={editor.isActive('heading', { level: 1 })}
/>
<IconButton
icon={TypeIcon}
icon={Type}
onClick={() =>
editor.chain().focus().toggleHeading({ level: 2 }).run()
}
@@ -58,12 +56,12 @@ export function FloatingFormatMenu(props: {
</>
)}
<IconButton
icon={BoldIcon}
icon={Bold}
onClick={() => editor.chain().focus().toggleBold().run()}
isActive={editor.isActive('bold')}
/>
<IconButton
icon={ItalicIcon}
icon={Italic}
onClick={() => editor.chain().focus().toggleItalic().run()}
isActive={editor.isActive('italic')}
/>

View File

@@ -4,8 +4,8 @@ import {Input} from 'web/components/widgets/input'
import {Button} from 'web/components/buttons/button'
import clsx from 'clsx'
import {useEffect, useMemo, useState} from 'react'
import {useT} from "web/lib/locale";
import {toKey} from "common/parsing";
import {useT} from 'web/lib/locale'
import {toKey} from 'common/parsing'
export const MultiCheckbox = (props: {
// Map of label -> value
@@ -20,14 +20,27 @@ export const MultiCheckbox = (props: {
// - string: the stored value for the new option; label will be the input text
// - { key, value }: explicit label (key) and stored value
// - null/undefined to indicate failure/cancellation
addOption?: (label: string) => string | { key: string; value: string } | null | undefined
addOption?: (
label: string
) => string | { key: string; value: string } | null | undefined
addPlaceholder?: string
translationPrefix?: string
}) => {
const {choices, selected, onChange, className, optionsClassName, addOption, addPlaceholder, translationPrefix} = props
const {
choices,
selected,
onChange,
className,
optionsClassName,
addOption,
addPlaceholder,
translationPrefix,
} = props
// Keep a local merged copy to allow optimistic adds while remaining in sync with props
const [localChoices, setLocalChoices] = useState<{ [key: string]: string }>(choices)
const [localChoices, setLocalChoices] = useState<{ [key: string]: string }>(
choices
)
useEffect(() => {
setLocalChoices((prev) => {
// If incoming choices changed, merge them with any locally added that still don't collide
@@ -56,7 +69,9 @@ export const MultiCheckbox = (props: {
let q = newLabel.trim()
q = translateOption(q, q).toLowerCase()
if (!q) return entries
return entries.filter(([key, value]) => translateOption(key, value).toLowerCase().includes(q))
return entries.filter(([key, value]) =>
translateOption(key, value).toLowerCase().includes(q)
)
}, [addOption, entries, newLabel])
const submitAdd = async () => {
@@ -68,8 +83,10 @@ export const MultiCheckbox = (props: {
return
}
// prevent duplicate by label or by value already selected
const existingEntry = Object.entries(localChoices).find(([key, value]) =>
translateOption(key, value).toLowerCase() === translateOption(label, label).toLowerCase()
const existingEntry = Object.entries(localChoices).find(
([key, value]) =>
translateOption(key, value).toLowerCase() ===
translateOption(label, label).toLowerCase()
)
if (existingEntry) {
@@ -88,12 +105,13 @@ export const MultiCheckbox = (props: {
setAdding(false)
return
}
const {key, value} = typeof result === 'string' ? {key: label, value: result} : result
const {key, value} =
typeof result === 'string' ? {key: label, value: result} : result
setLocalChoices((prev) => ({...prev, [key]: value}))
// auto-select newly added option if not already selected
if (!selected.includes(value)) onChange([...selected, value])
setNewLabel('')
} catch (e) {
} catch (_e) {
setError(t('multi-checkbox.add_failed', 'Failed to add option.'))
} finally {
setAdding(false)
@@ -106,7 +124,10 @@ export const MultiCheckbox = (props: {
<Row className="items-center gap-2">
<Input
value={newLabel}
placeholder={addPlaceholder ?? t('multi-checkbox.search_or_add', 'Search or add')}
placeholder={
addPlaceholder ??
t('multi-checkbox.search_or_add', 'Search or add')
}
onChange={(e) => {
setNewLabel(e.target.value)
setError(null)
@@ -119,7 +140,12 @@ export const MultiCheckbox = (props: {
}}
className="h-10"
/>
<Button size="sm" onClick={submitAdd} loading={adding} disabled={adding}>
<Button
size="sm"
onClick={submitAdd}
loading={adding}
disabled={adding}
>
{t('common.add', 'Add')}
</Button>
{error && <span className="text-sm text-error">{error}</span>}
@@ -144,9 +170,12 @@ export const MultiCheckbox = (props: {
</Row>
{addOption && newLabel.trim() && filteredEntries.length === 0 && (
<div className="px-2 text-sm text-ink-500">
{t('multi-checkbox.no_matching_options', 'No matching options, feel free to add it.')}
{t(
'multi-checkbox.no_matching_options',
'No matching options, feel free to add it.'
)}
</div>
)}
</div>
)
}
}

View File

@@ -201,7 +201,9 @@ export const OptionalProfileUserForm = (props: {
return
}
}
onSubmit && (await onSubmit())
if (onSubmit) {
await onSubmit()
}
track('submit optional profile')
if (user) {
let profile

View File

@@ -60,7 +60,7 @@ export function PageBase(props: {
)
// eslint-disable-next-line react-hooks/rules-of-hooks
trackPageView && useTracking(`view ${trackPageView}`, trackPageProps)
if (trackPageView) useTracking(`view ${trackPageView}`, trackPageProps)
useOnline()
const [_, setIsAddFundsModalOpen] = useState(false)

View File

@@ -72,10 +72,10 @@ export const RequiredProfileUserForm = (props: {
} = userInfo
useEffect(() => {
props.setEditUsername && props.setEditUsername(username)
if (props.setEditUsername) props.setEditUsername(username)
}, [username])
useEffect(() => {
props.setEditDisplayName && props.setEditDisplayName(name)
if (props.setEditDisplayName) props.setEditDisplayName(name)
}, [name])
const canContinue = true

View File

@@ -12,22 +12,10 @@ export const Input = forwardRef(
const { error, className, ...rest } = props
return (
<>
<style jsx>{`
input::-webkit-inner-spin-button,
input::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}
input {
-moz-appearance: textfield;
appearance: textfield;
}
`}</style>
<input
<input
ref={ref}
className={clsx(
'invalid:border-error invalid:text-error disabled:bg-canvas-50 disabled:border-ink-200 disabled:text-ink-500 bg-canvas-0 h-12 rounded-md border px-4 shadow-sm transition-colors invalid:placeholder-rose-700 focus:outline-none focus:ring-1 disabled:cursor-not-allowed md:text-sm',
'invalid:border-error invalid:text-error disabled:bg-canvas-50 disabled:border-ink-200 disabled:text-ink-500 bg-canvas-0 h-12 rounded-md border px-4 shadow-sm transition-colors invalid:placeholder-rose-700 focus:outline-none focus:ring-1 disabled:cursor-not-allowed md:text-sm [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:m-0 [&::-webkit-outer-spin-button]:m-0',
error
? 'border-error text-error focus:border-error focus:ring-error placeholder-rose-700' // matches invalid: styles
: 'border-ink-300 placeholder-ink-400 focus:ring-primary-500 focus:border-primary-500',
@@ -36,7 +24,6 @@ export const Input = forwardRef(
step={0.001} // default to 3 decimal places
{...rest}
/>
</>
)
}
)

View File

@@ -28,7 +28,7 @@ export function updateSupabaseAuth(token?: string) {
if (currentToken != token) {
currentToken = token
if (token == null) {
db['rest'].headers['Authorization']
// db['rest'].headers['Authorization']
db['realtime'].setAuth(null)
} else {
db['rest'].headers['Authorization'] = `Bearer ${token}`

View File

@@ -2,8 +2,8 @@ import {db} from './db'
import {run} from 'common/supabase/utils'
import {api} from 'web/lib/api'
import type {DisplayUser} from 'common/api/user-types'
import {MONTH_MS} from "common/util/time";
import {APIError} from "common/api/utils";
import {MONTH_MS} from 'common/util/time'
import {APIError} from 'common/api/utils'
export type {DisplayUser}
@@ -21,7 +21,7 @@ export async function getUserSafe(userId: string) {
export async function getPrivateUserSafe() {
try {
return await api('me/private')
} catch (e) {
} catch (_e) {
return null
}
}
@@ -59,9 +59,7 @@ export async function getDisplayUsers(userIds: string[]) {
export async function getProfilesCreations() {
const {data} = await run(
db.from('profiles')
.select(`id, created_time`)
.order('created_time')
db.from('profiles').select(`id, created_time`).order('created_time')
)
return data
}
@@ -89,14 +87,12 @@ export async function getCount(table: string) {
.select('*', {count: 'exact', head: true})
.gt('last_online_time', new Date(Date.now() - MONTH_MS).toISOString()) // last month
)
return count;
return count
}
const {count} = await run(
db
.from(table)
.select('*', {count: 'exact', head: true})
db.from(table).select('*', {count: 'exact', head: true})
)
return count;
return count
}
// export async function getNumberProfiles() {

View File

@@ -18,7 +18,7 @@ function getStorageProxy(store: Storage): Store | undefined {
setItem: (key: string, value: string) => {
try {
store.setItem(key, value)
} catch (e) {
} catch {
store.clear()
// try again
store.setItem(key, value)

View File

@@ -49,6 +49,7 @@
"heic2any": "0.0.4",
"link-preview-js": "3.0.4",
"lodash": "4.17.23",
"lucide-react": "0.575.0",
"nanoid": "5.0.9",
"next": "14.1.0",
"posthog-js": "1.234.1",

View File

@@ -14,6 +14,10 @@
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"typeRoots": [
"./types",
"./node_modules/@types"
],
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,

View File

@@ -11242,6 +11242,11 @@ lsofi@1.0.0:
is-number "^2.1.0"
through2 "^2.0.1"
lucide-react@0.575.0:
version "0.575.0"
resolved "https://registry.yarnpkg.com/lucide-react/-/lucide-react-0.575.0.tgz#90feaa4c140e9693e4ee9426d9927a6b833267ac"
integrity sha512-VuXgKZrk0uiDlWjGGXmKV6MSk9Yy4l10qgVvzGn2AWBx1Ylt0iBexKOAoA6I7JO3m+M9oeovJd3yYENfkUbOeg==
lz-string@^1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.5.0.tgz#c1ab50f77887b712621201ba9fd4e3a6ed099941"