Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(frontend): manage incoming auth tokens #2935

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
3 changes: 3 additions & 0 deletions localenv/mock-account-servicing-entity/generated/graphql.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 20 additions & 0 deletions packages/backend/src/graphql/generated/graphql.schema.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions packages/backend/src/graphql/generated/graphql.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions packages/backend/src/graphql/resolvers/peer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,9 @@ export const peerToGraphql = (peer: Peer): SchemaPeer => ({
id: peer.id,
maxPacketAmount: peer.maxPacketAmount,
http: peer.http,
incomingTokens: peer.incomingTokens?.map(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think incomingTokens would probably benefit from a separate resolver kind of like liquidity is done. Check packages/backend/src/graphql/resolvers/index.ts. This will require a new function in the HttpTokenService instead of doing .withGraphFetched('incomingTokens') in the getter in peer service.

(incomingToken) => incomingToken.token
),
asset: assetToGraphql(peer.asset),
staticIlpAddress: peer.staticIlpAddress,
name: peer.name,
Expand Down
2 changes: 2 additions & 0 deletions packages/backend/src/graphql/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -619,6 +619,8 @@ type Peer implements Model {
maxPacketAmount: UInt64
"Peering connection details"
http: Http!
"Incoming tokens"
incomingTokens: [String!]
furkan-bilgin marked this conversation as resolved.
Show resolved Hide resolved
"Asset of peering relationship"
asset: Asset!
"Peer's ILP address"
Expand Down
5 changes: 4 additions & 1 deletion packages/backend/src/payment-method/ilp/peer/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,10 @@ async function getPeer(
deps: ServiceDependencies,
id: string
): Promise<Peer | undefined> {
return Peer.query(deps.knex).findById(id).withGraphFetched('asset')
return Peer.query(deps.knex)
.findById(id)
.withGraphFetched('asset')
.withGraphFetched('incomingTokens')
}

async function createPeer(
Expand Down
154 changes: 154 additions & 0 deletions packages/frontend/app/components/ui/EditableTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import type { ReactNode } from 'react'
import { useEffect, useState } from 'react'
import { Input } from './Input'
import { Table } from './Table'
import { FieldError } from './FieldError'
import { Button } from './Button'

type EditableTableProps = {
name: string
label: string
options: EditableTableOption[]
error?: string | string[]
description?: ReactNode
valueFormatter?: (values: string[]) => string
required?: boolean
}

type EditableTableOption = {
label: string
value: string
canDelete?: boolean
canEdit?: boolean
showInput?: boolean
}

export const EditableTable = ({
name,
label,
options,
error,
description = undefined,
valueFormatter = (values) => values.join(','),
required = false
}: EditableTableProps) => {
const [optionsList, setOptionsList] = useState<EditableTableOption[]>(options)
const [value, setValue] = useState<string>('')

const toggleEditInput = (index: number) => {
setOptionsList(
optionsList.map((option, i) => {
if (i === index) {
return {
...option,
showInput: true
}
}
return option
})
)
}

const editOption = (index: number, value: string) => {
if (!value) {
deleteOption(index)
return
}
setOptionsList(
optionsList.map((option, i) => {
if (i === index) {
return {
...option,
showInput: false,
value
}
}
return option
})
)
}

const deleteOption = (index: number) => {
setOptionsList(optionsList.filter((_, i) => i !== index))
}

const addOption = () => {
setOptionsList([
...optionsList,
{ label: '', value: '', canDelete: true, canEdit: true, showInput: true }
])
}

useEffect(() => {
setValue(getValue())
}, [optionsList])

const getValue = () => {
return valueFormatter(optionsList.map((option) => option.value))
}
furkan-bilgin marked this conversation as resolved.
Show resolved Hide resolved

return (
<>
<Input
type='hidden'
name={name}
value={value}
required={required}
label={label}
/>
<Table>
<Table.Head columns={['Token', 'Action']} />
<Table.Body>
{(optionsList || []).map((option, index) => (
<Table.Row key={index}>
<Table.Cell key={0}>
{option.showInput ? (
<Input
type='text'
onKeyDown={(e) =>
e.key === 'Enter' &&
(e.preventDefault(),
editOption(index, e.currentTarget.value))
}
defaultValue={option.value}
required={required}
/>
) : (
<span>{option.value}</span>
)}
</Table.Cell>
<Table.Cell key={1}>
{option.canEdit && !option.showInput ? (
<Button
aria-label='edit'
onClick={() => toggleEditInput(index)}
>
Edit
</Button>
) : null}
{option.canDelete ? (
<Button
className='ml-2'
aria-label='delete'
onClick={() => deleteOption(index)}
>
Delete
</Button>
) : null}
</Table.Cell>
</Table.Row>
))}
</Table.Body>
</Table>
<div className='flex justify-end mt-2'>
<Button aria-label='add' onClick={() => addOption()}>
Add
</Button>
</div>
{description ? (
<div className='font-medium text-sm'>{description}</div>
) : null}
<FieldError error={error} />
</>
)
}
5 changes: 4 additions & 1 deletion packages/frontend/app/generated/graphql.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/frontend/app/lib/api/peer.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export const getPeer = async (args: QueryPeerArgs) => {
authToken
}
}
incomingTokens
}
}
`,
Expand Down
11 changes: 8 additions & 3 deletions packages/frontend/app/routes/peers.$peerId.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
import type { ZodFieldErrors } from '~/shared/types'
import { formatAmount } from '~/shared/utils'
import { checkAuthAndRedirect } from '../lib/kratos_checks.server'
import { EditableTable } from '~/components/ui/EditableTable'

export async function loader({ request, params }: LoaderFunctionArgs) {
const cookies = request.headers.get('cookie')
Expand Down Expand Up @@ -204,10 +205,15 @@ export default function ViewPeerPage() {
<fieldset disabled={currentPageAction}>
<div className='w-full p-4 space-y-3'>
<Input type='hidden' name='id' value={peer.id} />
<Input
<EditableTable
name='incomingAuthTokens'
label='Incoming Auth Tokens'
placeholder='Accepts a comma separated list of tokens'
options={(peer.incomingTokens || []).map((token) => ({
label: token,
value: token,
canDelete: true,
canEdit: true
}))}
error={response?.errors.http.fieldErrors.incomingAuthTokens}
description={
<>
Expand Down Expand Up @@ -435,7 +441,6 @@ export async function action({ request }: ActionFunctionArgs) {
result.error.flatten().fieldErrors
return json({ ...actionResponse }, { status: 400 })
}

const response = await updatePeer({
id: result.data.id,
http: {
Expand Down
3 changes: 3 additions & 0 deletions packages/mock-account-service-lib/src/generated/graphql.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions test/integration/lib/generated/graphql.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading