Add more linting to web
package (#343)
* Import React a lot * Fix misc. linting concerns * Turn on many recommended lints for `web` package
This commit is contained in:
parent
5217270073
commit
420ea9e90e
|
@ -1,10 +1,21 @@
|
|||
module.exports = {
|
||||
parser: '@typescript-eslint/parser',
|
||||
plugins: ['lodash'],
|
||||
extends: ['plugin:react-hooks/recommended', 'plugin:@next/next/recommended'],
|
||||
extends: [
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:react-hooks/recommended',
|
||||
'plugin:@next/next/recommended',
|
||||
],
|
||||
rules: {
|
||||
'@typescript-eslint/no-empty-function': 'off',
|
||||
'@typescript-eslint/no-unused-vars': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@next/next/no-img-element': 'off',
|
||||
'@next/next/no-typos': 'off',
|
||||
'lodash/import-scope': [2, 'member'],
|
||||
},
|
||||
env: {
|
||||
browser: true,
|
||||
node: true,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import clsx from 'clsx'
|
||||
import React from 'react'
|
||||
import { useUser } from 'web/hooks/use-user'
|
||||
import { formatMoney } from 'common/util/format'
|
||||
import { Col } from './layout/col'
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// Adapted from https://stackoverflow.com/a/50884055/1222351
|
||||
import { useEffect, useState } from 'react'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
|
||||
export function ClientRender(props: { children: React.ReactNode }) {
|
||||
const { children } = props
|
||||
|
|
|
@ -229,12 +229,13 @@ function QuickOutcomeView(props: {
|
|||
case 'NUMERIC':
|
||||
display = formatLargeNumber(getExpectedValue(contract as NumericContract))
|
||||
break
|
||||
case 'FREE_RESPONSE':
|
||||
case 'FREE_RESPONSE': {
|
||||
const topAnswer = getTopAnswer(contract as FreeResponseContract)
|
||||
display =
|
||||
topAnswer &&
|
||||
formatPercent(getOutcomeProbability(contract, topAnswer.id))
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import React from 'react'
|
||||
import dayjs from 'dayjs'
|
||||
import utc from 'dayjs/plugin/utc'
|
||||
import timezone from 'dayjs/plugin/timezone'
|
||||
|
|
|
@ -343,7 +343,7 @@ function groupBetsAndComments(
|
|||
|
||||
// iterate through the bets and comment activity items and add them to the items in order of comment creation time:
|
||||
const unorderedBetsAndComments = [...commentsWithoutBets, ...groupedBets]
|
||||
let sortedBetsAndComments = sortBy(unorderedBetsAndComments, (item) => {
|
||||
const sortedBetsAndComments = sortBy(unorderedBetsAndComments, (item) => {
|
||||
if (item.type === 'comment') {
|
||||
return item.comment.createdTime
|
||||
} else if (item.type === 'bet') {
|
||||
|
@ -540,7 +540,7 @@ export function getSpecificContractActivityItems(
|
|||
}
|
||||
) {
|
||||
const { mode } = options
|
||||
let items = [] as ActivityItem[]
|
||||
const items = [] as ActivityItem[]
|
||||
|
||||
switch (mode) {
|
||||
case 'bets':
|
||||
|
@ -559,7 +559,7 @@ export function getSpecificContractActivityItems(
|
|||
)
|
||||
break
|
||||
|
||||
case 'comments':
|
||||
case 'comments': {
|
||||
const nonFreeResponseComments = comments.filter((comment) =>
|
||||
commentIsGeneralComment(comment, contract)
|
||||
)
|
||||
|
@ -585,6 +585,7 @@ export function getSpecificContractActivityItems(
|
|||
),
|
||||
})
|
||||
break
|
||||
}
|
||||
case 'free-response-comment-answer-groups':
|
||||
items.push(
|
||||
...getAnswerAndCommentInputGroups(
|
||||
|
|
|
@ -21,7 +21,7 @@ export function CopyLinkDateTimeComponent(props: {
|
|||
event: React.MouseEvent<HTMLAnchorElement, MouseEvent>
|
||||
) {
|
||||
event.preventDefault()
|
||||
let elementLocation = `https://${ENV_CONFIG.domain}${contractPath(
|
||||
const elementLocation = `https://${ENV_CONFIG.domain}${contractPath(
|
||||
contract
|
||||
)}#${elementId}`
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import { ReactNode } from 'react'
|
||||
|
||||
export const JoinSpans = (props: {
|
||||
children: any[]
|
||||
separator?: JSX.Element | string
|
||||
separator?: ReactNode
|
||||
}) => {
|
||||
const { separator } = props
|
||||
const children = props.children.filter((x) => !!x)
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { Fragment } from 'react'
|
||||
import { Fragment, ReactNode } from 'react'
|
||||
import { Dialog, Transition } from '@headlessui/react'
|
||||
|
||||
// From https://tailwindui.com/components/application-ui/overlays/modals
|
||||
export function Modal(props: {
|
||||
children: React.ReactNode
|
||||
children: ReactNode
|
||||
open: boolean
|
||||
setOpen: (open: boolean) => void
|
||||
}) {
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import clsx from 'clsx'
|
||||
import Link from 'next/link'
|
||||
import { useState } from 'react'
|
||||
import { ReactNode, useState } from 'react'
|
||||
import { Row } from './row'
|
||||
|
||||
type Tab = {
|
||||
title: string
|
||||
tabIcon?: JSX.Element
|
||||
content: JSX.Element
|
||||
tabIcon?: ReactNode
|
||||
content: ReactNode
|
||||
// If set, change the url to this href when the tab is selected
|
||||
href?: string
|
||||
}
|
||||
|
|
|
@ -4,14 +4,14 @@ import { SiteLink } from './site-link'
|
|||
// Return a JSX span, linkifying @username, #hashtags, and https://...
|
||||
// TODO: Use a markdown parser instead of rolling our own here.
|
||||
export function Linkify(props: { text: string; gray?: boolean }) {
|
||||
let { text, gray } = props
|
||||
const { text, gray } = props
|
||||
// Replace "m1234" with "ϻ1234"
|
||||
// const mRegex = /(\W|^)m(\d+)/g
|
||||
// text = text.replace(mRegex, (_, pre, num) => `${pre}ϻ${num}`)
|
||||
|
||||
// Find instances of @username, #hashtag, and https://...
|
||||
const regex =
|
||||
/(?:^|\s)(?:[@#][a-z0-9_]+|https?:\/\/[-A-Za-z0-9+&@#\/%?=~_()|!:,.;]*[-A-Za-z0-9+&@#\/%=~_|])/gi
|
||||
/(?:^|\s)(?:[@#][a-z0-9_]+|https?:\/\/[-A-Za-z0-9+&@#/%?=~_()|!:,.;]*[-A-Za-z0-9+&@#/%=~_|])/gi
|
||||
const matches = text.match(regex) || []
|
||||
const links = matches.map((match) => {
|
||||
// Matches are in the form: " @username" or "https://example.com"
|
||||
|
|
|
@ -17,7 +17,7 @@ import { Avatar } from '../avatar'
|
|||
import clsx from 'clsx'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
function getNavigation(username: String) {
|
||||
function getNavigation(username: string) {
|
||||
return [
|
||||
{ name: 'Home', href: '/home', icon: HomeIcon },
|
||||
{ name: 'Activity', href: '/activity', icon: ChatAltIcon },
|
||||
|
|
|
@ -25,7 +25,7 @@ import {
|
|||
useHasCreatedContractToday,
|
||||
} from 'web/hooks/use-has-created-contract-today'
|
||||
import { Row } from '../layout/row'
|
||||
import { useEffect, useState } from 'react'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
|
||||
// Create an icon from the url of an image
|
||||
function IconFromUrl(url: string): React.ComponentType<{ className?: string }> {
|
||||
|
@ -130,7 +130,7 @@ export default function Sidebar(props: { className?: string }) {
|
|||
const nextUtcResetTime = getUtcFreeMarketResetTime(false)
|
||||
const interval = setInterval(() => {
|
||||
const now = new Date().getTime()
|
||||
let timeUntil = nextUtcResetTime - now
|
||||
const timeUntil = nextUtcResetTime - now
|
||||
const hoursUntil = timeUntil / 1000 / 60 / 60
|
||||
const minutesUntil = Math.floor((hoursUntil * 60) % 60)
|
||||
const secondsUntil = Math.floor((hoursUntil * 60 * 60) % 60)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import clsx from 'clsx'
|
||||
|
||||
import React from 'react'
|
||||
import { Col } from './layout/col'
|
||||
import { Spacer } from './layout/spacer'
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import clsx from 'clsx'
|
||||
import { ReactNode } from 'react'
|
||||
import { Answer } from 'common/answer'
|
||||
import { getProbability } from 'common/calculate'
|
||||
import { getValueFromBucket } from 'common/calculate-dpm'
|
||||
|
@ -156,7 +157,7 @@ export function AnswerLabel(props: {
|
|||
|
||||
function FreeResponseAnswerToolTip(props: {
|
||||
text: string
|
||||
children?: React.ReactNode
|
||||
children?: ReactNode
|
||||
}) {
|
||||
const { text } = props
|
||||
return (
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import clsx from 'clsx'
|
||||
import { ReactNode } from 'react'
|
||||
import { BottomNavBar } from './nav/nav-bar'
|
||||
import Sidebar from './nav/sidebar'
|
||||
import { Toaster } from 'react-hot-toast'
|
||||
|
@ -6,7 +7,7 @@ import { Toaster } from 'react-hot-toast'
|
|||
export function Page(props: {
|
||||
margin?: boolean
|
||||
assertUser?: 'signed-in' | 'signed-out'
|
||||
rightSidebar?: React.ReactNode
|
||||
rightSidebar?: ReactNode
|
||||
suspend?: boolean
|
||||
children?: any
|
||||
}) {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import clsx from 'clsx'
|
||||
import { ReactNode } from 'react'
|
||||
import Link from 'next/link'
|
||||
|
||||
export const SiteLink = (props: {
|
||||
|
@ -30,7 +31,7 @@ export const SiteLink = (props: {
|
|||
)
|
||||
}
|
||||
|
||||
function MaybeLink(props: { href: string; children: React.ReactNode }) {
|
||||
function MaybeLink(props: { href: string; children: ReactNode }) {
|
||||
const { href, children } = props
|
||||
return href.startsWith('http') ? (
|
||||
<>{children}</>
|
||||
|
|
|
@ -6,10 +6,10 @@ import fetch, { Headers, Response } from 'node-fetch'
|
|||
|
||||
function getProxiedRequestHeaders(req: NextApiRequest, whitelist: string[]) {
|
||||
const result = new Headers()
|
||||
for (let name of whitelist) {
|
||||
for (const name of whitelist) {
|
||||
const v = req.headers[name.toLowerCase()]
|
||||
if (Array.isArray(v)) {
|
||||
for (let vv of v) {
|
||||
for (const vv of v) {
|
||||
result.append(name, vv)
|
||||
}
|
||||
} else if (v != null) {
|
||||
|
@ -23,7 +23,7 @@ function getProxiedRequestHeaders(req: NextApiRequest, whitelist: string[]) {
|
|||
|
||||
function getProxiedResponseHeaders(res: Response, whitelist: string[]) {
|
||||
const result: { [k: string]: string } = {}
|
||||
for (let name of whitelist) {
|
||||
for (const name of whitelist) {
|
||||
const v = res.headers.get(name)
|
||||
if (v != null) {
|
||||
result[name] = v
|
||||
|
|
|
@ -9,13 +9,15 @@ export const app = getApps().length ? getApp() : initializeApp(FIREBASE_CONFIG)
|
|||
export const db = getFirestore()
|
||||
export const functions = getFunctions()
|
||||
|
||||
const EMULATORS_STARTED = 'EMULATORS_STARTED'
|
||||
declare global {
|
||||
/* eslint-disable-next-line no-var */
|
||||
var EMULATORS_STARTED: boolean
|
||||
}
|
||||
|
||||
function startEmulators() {
|
||||
// I don't like this but this is the only way to reconnect to the emulators without error, see: https://stackoverflow.com/questions/65066963/firebase-firestore-emulator-error-host-has-been-set-in-both-settings-and-usee
|
||||
// @ts-ignore
|
||||
if (!global[EMULATORS_STARTED]) {
|
||||
// @ts-ignore
|
||||
global[EMULATORS_STARTED] = true
|
||||
if (!global.EMULATORS_STARTED) {
|
||||
global.EMULATORS_STARTED = true
|
||||
connectFirestoreEmulator(db, 'localhost', 8080)
|
||||
connectFunctionsEmulator(functions, 'localhost', 5001)
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ export function copyToClipboard(text: string) {
|
|||
document.queryCommandSupported('copy')
|
||||
) {
|
||||
console.log('copy 3')
|
||||
var textarea = document.createElement('textarea')
|
||||
const textarea = document.createElement('textarea')
|
||||
textarea.textContent = text
|
||||
textarea.style.position = 'fixed' // Prevent scrolling to bottom of page in Microsoft Edge.
|
||||
document.body.appendChild(textarea)
|
||||
|
|
|
@ -13,12 +13,12 @@ function firstLine(msg: string) {
|
|||
function printBuildInfo() {
|
||||
// These are undefined if e.g. dev server
|
||||
if (process.env.NEXT_PUBLIC_VERCEL_ENV) {
|
||||
let env = process.env.NEXT_PUBLIC_VERCEL_ENV
|
||||
let msg = process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_MESSAGE
|
||||
let owner = process.env.NEXT_PUBLIC_VERCEL_GIT_REPO_OWNER
|
||||
let repo = process.env.NEXT_PUBLIC_VERCEL_GIT_REPO_SLUG
|
||||
let sha = process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA
|
||||
let url = `https://github.com/${owner}/${repo}/commit/${sha}`
|
||||
const env = process.env.NEXT_PUBLIC_VERCEL_ENV
|
||||
const msg = process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_MESSAGE
|
||||
const owner = process.env.NEXT_PUBLIC_VERCEL_GIT_REPO_OWNER
|
||||
const repo = process.env.NEXT_PUBLIC_VERCEL_GIT_REPO_SLUG
|
||||
const sha = process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA
|
||||
const url = `https://github.com/${owner}/${repo}/commit/${sha}`
|
||||
console.info(`Build: ${env} / ${firstLine(msg || '???')} / ${url}`)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,28 +19,22 @@ function avatarHtml(avatarUrl: string) {
|
|||
}
|
||||
|
||||
function UsersTable() {
|
||||
let users = useUsers()
|
||||
let privateUsers = usePrivateUsers()
|
||||
const users = useUsers()
|
||||
const privateUsers = usePrivateUsers()
|
||||
|
||||
// Map private users by user id
|
||||
const privateUsersById = mapKeys(privateUsers, 'id')
|
||||
console.log('private users by id', privateUsersById)
|
||||
|
||||
// For each user, set their email from the PrivateUser
|
||||
users = users.map((user) => {
|
||||
// @ts-ignore
|
||||
user.email = privateUsersById[user.id]?.email
|
||||
return user
|
||||
})
|
||||
|
||||
// Sort users by createdTime descending, by default
|
||||
users = users.sort((a, b) => b.createdTime - a.createdTime)
|
||||
const fullUsers = users
|
||||
.map((user) => {
|
||||
return { email: privateUsersById[user.id]?.email, ...user }
|
||||
})
|
||||
.sort((a, b) => b.createdTime - a.createdTime)
|
||||
|
||||
function exportCsv() {
|
||||
const csv = users
|
||||
// @ts-ignore
|
||||
.map((u) => [u.email, u.name].join(', '))
|
||||
.join('\n')
|
||||
const csv = fullUsers.map((u) => [u.email, u.name].join(', ')).join('\n')
|
||||
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
|
@ -108,27 +102,29 @@ function UsersTable() {
|
|||
}
|
||||
|
||||
function ContractsTable() {
|
||||
let contracts = useContracts() ?? []
|
||||
const contracts = useContracts() ?? []
|
||||
|
||||
// Sort users by createdTime descending, by default
|
||||
contracts.sort((a, b) => b.createdTime - a.createdTime)
|
||||
// Render a clickable question. See https://gridjs.io/docs/examples/react-cells for docs
|
||||
contracts.map((contract) => {
|
||||
// @ts-ignore
|
||||
contract.questionLink = r(
|
||||
<div className="w-60">
|
||||
<a
|
||||
className="hover:underline hover:decoration-indigo-400 hover:decoration-2"
|
||||
href={contractPath(contract)}
|
||||
>
|
||||
{contract.question}
|
||||
</a>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
const displayContracts = contracts
|
||||
.sort((a, b) => b.createdTime - a.createdTime)
|
||||
.map((contract) => {
|
||||
// Render a clickable question. See https://gridjs.io/docs/examples/react-cells for docs
|
||||
const questionLink = r(
|
||||
<div className="w-60">
|
||||
<a
|
||||
className="hover:underline hover:decoration-indigo-400 hover:decoration-2"
|
||||
href={contractPath(contract)}
|
||||
>
|
||||
{contract.question}
|
||||
</a>
|
||||
</div>
|
||||
)
|
||||
return { questionLink, ...contract }
|
||||
})
|
||||
|
||||
return (
|
||||
<Grid
|
||||
data={contracts}
|
||||
data={displayContracts}
|
||||
columns={[
|
||||
{
|
||||
id: 'creatorUsername',
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { sortBy, sumBy, uniqBy } from 'lodash'
|
||||
import clsx from 'clsx'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import { Col } from 'web/components/layout/col'
|
||||
import { Row } from 'web/components/layout/row'
|
||||
import { Page } from 'web/components/page'
|
||||
|
|
|
@ -63,7 +63,7 @@ export default function Folds(props: {
|
|||
const [query, setQuery] = useState('')
|
||||
// Copied from contracts-list.tsx; extract if we copy this again
|
||||
const queryWords = query.toLowerCase().split(' ')
|
||||
function check(corpus: String) {
|
||||
function check(corpus: string) {
|
||||
return queryWords.every((word) => corpus.toLowerCase().includes(word))
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { useEffect, useState } from 'react'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { RefreshIcon } from '@heroicons/react/outline'
|
||||
import Router from 'next/router'
|
||||
|
||||
|
@ -21,7 +21,7 @@ import Textarea from 'react-expanding-textarea'
|
|||
|
||||
function EditUserField(props: {
|
||||
user: User
|
||||
field: 'bio' | 'bannerUrl' | 'twitterHandle' | 'discordHandle'
|
||||
field: 'bio' | 'website' | 'bannerUrl' | 'twitterHandle' | 'discordHandle'
|
||||
label: string
|
||||
}) {
|
||||
const { user, field, label } = props
|
||||
|
@ -220,18 +220,15 @@ export default function ProfilePage() {
|
|||
}}
|
||||
/>
|
||||
|
||||
{[
|
||||
['bio', 'Bio'],
|
||||
['website', 'Website URL'],
|
||||
['twitterHandle', 'Twitter'],
|
||||
['discordHandle', 'Discord'],
|
||||
].map(([field, label]) => (
|
||||
<EditUserField
|
||||
user={user}
|
||||
// @ts-ignore
|
||||
field={field}
|
||||
label={label}
|
||||
/>
|
||||
{(
|
||||
[
|
||||
['bio', 'Bio'],
|
||||
['website', 'Website URL'],
|
||||
['twitterHandle', 'Twitter'],
|
||||
['discordHandle', 'Discord'],
|
||||
] as const
|
||||
).map(([field, label]) => (
|
||||
<EditUserField user={user} field={field} label={label} />
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
|
|
Loading…
Reference in New Issue
Block a user