Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions clients/apps/web/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { nextJsConfig } from '@polar-sh/eslint-config/next-js'
import pluginQuery from '@tanstack/eslint-plugin-query'

// Elements replaced by Orbit primitives — ban raw JSX usage as a warning.
// const orbitElementRule = (element, replacement) => ({
Expand All @@ -16,6 +17,7 @@ import { nextJsConfig } from '@polar-sh/eslint-config/next-js'
/** @type {import("eslint").Linter.Config} */
export default [
...nextJsConfig,
...pluginQuery.configs['flat/recommended'],
{
rules: {
'react-hooks/set-state-in-effect': 'warn',
Expand All @@ -24,6 +26,8 @@ export default [
'react-hooks/preserve-manual-memoization': 'warn',
'react-hooks/immutability': 'warn',
'react-hooks/purity': 'warn',
'react-hooks/no-deriving-state-in-effects': 'warn',
'react-hooks/memoized-effect-dependencies': 'warn',
},
},
{
Expand All @@ -32,6 +36,8 @@ export default [
'react/no-danger': 'error',
'react/self-closing-comp': 'warn',
'react/jsx-no-useless-fragment': 'warn',
'react/jsx-no-constructed-context-values': 'warn',
'react/no-object-type-as-default-prop': 'warn',
'no-restricted-syntax': [
'error',
{
Expand All @@ -51,6 +57,18 @@ export default [
message:
'Do not use style on <Box />. Use design system props instead.',
},
{
selector:
'CallExpression[callee.name="useEffect"] CallExpression[callee.name="fetch"]',
message:
'Do not fetch data inside useEffect. Use TanStack Query (useQuery/useMutation) instead.',
},
{
selector:
'CallExpression[callee.name="useEffect"] CallExpression[callee.object.name="api"]',
message:
'Do not call the API inside useEffect. Use TanStack Query (useQuery/useMutation) instead.',
},
],
'no-restricted-imports': [
'error',
Expand Down
1 change: 1 addition & 0 deletions clients/apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@
"@polar-sh/typescript-config": "workspace:*",
"@stylexjs/babel-plugin": "^0.18.1",
"@stylexjs/postcss-plugin": "^0.18.1",
"@tanstack/eslint-plugin-query": "^5.94.5",
"@types/big.js": "^6.2.2",
"@types/dom-to-image": "^2.6.7",
"@types/mdx": "^2.0.13",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,33 +57,34 @@ export default function ClientPage({
maxWaitingTimeMs: 15000,
})

const claimMutation = useMutation({
mutationFn: async () => {
if (!invitationToken) {
throw new Error('No invitation token')
}

const response = await fetch(
`${CONFIG.BASE_URL}/v1/customer-seats/claim`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
const { mutateAsync: claimMutateAsync, error: claimMutationError } =
useMutation({
mutationFn: async () => {
if (!invitationToken) {
throw new Error('No invitation token')
}

const response = await fetch(
`${CONFIG.BASE_URL}/v1/customer-seats/claim`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
invitation_token: invitationToken,
}),
},
body: JSON.stringify({
invitation_token: invitationToken,
}),
},
)
)

if (!response.ok) {
const error = await response.json()
throw new Error(error.detail || 'Failed to claim seat')
}
if (!response.ok) {
const error = await response.json()
throw new Error(error.detail || 'Failed to claim seat')
}

return await response.json()
},
})
return await response.json()
},
})

// Establish SSE connection early to prevent race conditions
const sseReadyRef = useRef(false)
Expand Down Expand Up @@ -123,7 +124,7 @@ export default function ClientPage({
sseReadyRef.current = true
}

const result = await claimMutation.mutateAsync()
const result = await claimMutateAsync()

await fulfillmentPromiseRef.current

Expand All @@ -138,7 +139,7 @@ export default function ClientPage({
error instanceof Error ? error.message : 'Failed to claim seat'
setClaimError(errorMessage)
}
}, [claimInfo?.product_id, claimMutation, organization.slug, router])
}, [claimInfo?.product_id, claimMutateAsync, organization.slug, router])

if (!invitationToken) {
return (
Expand Down Expand Up @@ -242,9 +243,9 @@ export default function ClientPage({
{claimingState !== 'idle' ? 'Claiming...' : 'Claim seat'}
</Button>

{(claimError || claimMutation.error) && (
{(claimError || claimMutationError) && (
<div className="rounded-lg bg-red-50 p-4 text-sm text-red-600 dark:bg-red-900/20 dark:text-red-400">
{claimError || claimMutation.error?.message}
{claimError || claimMutationError?.message}
</div>
)}
</div>
Expand Down
1 change: 1 addition & 0 deletions clients/apps/web/src/components/Checkout/Checkout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ const Checkout = ({
)
const distinctId = distinctIdCookie?.split('=')[1]?.trim()

// eslint-disable-next-line no-restricted-syntax
fetch(
getServerURL(`/v1/checkouts/client/${checkout.client_secret}/opened`),
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { schemas } from '@polar-sh/client'
import { PropsWithChildren, createContext } from 'react'
import { PropsWithChildren, createContext, useMemo } from 'react'

// eslint-disable-next-line @typescript-eslint/no-empty-object-type
interface DashboardContextValue {}
Expand All @@ -15,7 +15,11 @@ export const DashboardProvider = ({
}: PropsWithChildren<{
organization: schemas['Organization'] | undefined
}>) => {
const value = useMemo(() => ({}), [])

return (
<DashboardContext.Provider value={{}}>{children}</DashboardContext.Provider>
<DashboardContext.Provider value={value}>
{children}
</DashboardContext.Provider>
)
}
42 changes: 28 additions & 14 deletions clients/apps/web/src/components/Payouts/PayoutContext.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import { schemas } from '@polar-sh/client'
import React, { createContext, ReactNode, useContext, useState } from 'react'
import React, {
createContext,
ReactNode,
useCallback,
useContext,
useMemo,
useState,
} from 'react'

interface PayoutContextType {
selectedPayout: schemas['Payout'] | null
Expand All @@ -19,21 +26,28 @@ export const PayoutProvider: React.FC<{ children: ReactNode }> = ({
>(null)
const [isInvoiceModalOpen, setIsInvoiceModalOpen] = useState(false)

const openInvoiceModal = () => setIsInvoiceModalOpen(true)
const closeInvoiceModal = () => setIsInvoiceModalOpen(false)
const openInvoiceModal = useCallback(() => setIsInvoiceModalOpen(true), [])
const closeInvoiceModal = useCallback(() => setIsInvoiceModalOpen(false), [])

const value = useMemo(
() => ({
selectedPayout,
setSelectedPayout,
isInvoiceModalOpen,
openInvoiceModal,
closeInvoiceModal,
}),
[
selectedPayout,
setSelectedPayout,
isInvoiceModalOpen,
openInvoiceModal,
closeInvoiceModal,
],
)

return (
<PayoutContext.Provider
value={{
selectedPayout,
setSelectedPayout,
isInvoiceModalOpen,
openInvoiceModal,
closeInvoiceModal,
}}
>
{children}
</PayoutContext.Provider>
<PayoutContext.Provider value={value}>{children}</PayoutContext.Provider>
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export const BenefitSearchComplex = ({
}, [searchQuery])

useEffect(() => {
// eslint-disable-next-line react-hooks/no-deriving-state-in-effects
setIsDropdownOpen(debouncedQuery.trim().length > 0)
}, [debouncedQuery])

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
'use client'

import React, { useCallback, useContext, useEffect, useState } from 'react'
import React, {
useCallback,
useContext,
useEffect,
useMemo,
useState,
} from 'react'
import { BundledLanguage } from 'shiki'
import { createHighlighterCore, HighlighterCore } from 'shiki/core'
import { createOnigurumaEngine } from 'shiki/engine/oniguruma'
Expand Down Expand Up @@ -94,14 +100,17 @@ export const SyntaxHighlighterProvider = ({
[highlighter],
)

const value = useMemo(
() => ({
highlighter,
loadedLanguages,
loadLanguage: _loadLanguage,
}),
[highlighter, loadedLanguages, _loadLanguage],
)

return (
<SyntaxHighlighterContext.Provider
value={{
highlighter,
loadedLanguages,
loadLanguage: _loadLanguage,
}}
>
<SyntaxHighlighterContext.Provider value={value}>
{children}
</SyntaxHighlighterContext.Provider>
)
Expand Down
2 changes: 2 additions & 0 deletions clients/apps/web/src/hooks/queries/customerPortal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { Client, operations, schemas, unwrap } from '@polar-sh/client'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { defaultRetry } from './retry'

/* eslint-disable @tanstack/query/exhaustive-deps */

export function useCustomerPortalCustomer(options?: {
initialData?: schemas['CustomerPortalCustomer']
}) {
Expand Down
8 changes: 7 additions & 1 deletion clients/apps/web/src/hooks/queries/files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,13 @@ export const useFiles = (
options?: { limit?: number },
) =>
useQuery({
queryKey: ['user', 'files', JSON.stringify(fileIds)],
queryKey: [
'user',
'files',
JSON.stringify(fileIds),
organizationId,
options?.limit,
],
queryFn: () =>
unwrap(
api.GET('/v1/files/', {
Expand Down
2 changes: 1 addition & 1 deletion clients/apps/web/src/hooks/queries/oauth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export const useOAuth2Clients = (
options?: operations['oauth2:clients:list']['parameters']['query'],
) =>
useQuery({
queryKey: ['oauth2Clients'],
queryKey: ['oauth2Clients', options],
queryFn: async () =>
unwrap(api.GET('/v1/oauth2/', { params: { query: options } })),
})
Expand Down
2 changes: 1 addition & 1 deletion clients/apps/web/src/hooks/queries/products.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export const useProducts = (

export const useSelectedProducts = (id: string[], includeArchived = false) =>
useQuery({
queryKey: ['products', { id }],
queryKey: ['products', { id, includeArchived }],
queryFn: async () => {
const products: schemas['Product'][] = []
let page = 1
Expand Down
17 changes: 10 additions & 7 deletions clients/apps/web/src/providers/maintainerOrganization.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use client'

import { schemas } from '@polar-sh/client'
import React from 'react'
import React, { useMemo } from 'react'

const stub = (): never => {
throw new Error(
Expand All @@ -27,13 +27,16 @@ export const OrganizationContextProvider = ({
organizations: schemas['Organization'][]
children: React.ReactNode
}) => {
const value = useMemo(
() => ({
organization,
organizations,
}),
[organization, organizations],
)

return (
<OrganizationContext.Provider
value={{
organization,
organizations,
}}
>
<OrganizationContext.Provider value={value}>
{children}
</OrganizationContext.Provider>
)
Expand Down
9 changes: 6 additions & 3 deletions clients/apps/web/src/providers/navigationHistory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,13 @@ export const NavigationHistoryProvider = ({ children }: PropsWithChildren) => {
[previousURL],
)

const value = useMemo(
() => ({ currentURL, previousURL, withPotentialPreviousParams }),
[currentURL, previousURL, withPotentialPreviousParams],
)

return (
<NavigationHistoryContext.Provider
value={{ currentURL, previousURL, withPotentialPreviousParams }}
>
<NavigationHistoryContext.Provider value={value}>
{children}
</NavigationHistoryContext.Provider>
)
Expand Down
Loading
Loading