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