Skip to content

Commit

Permalink
chore: ui for multichain payments
Browse files Browse the repository at this point in the history
  • Loading branch information
Oleksandr Raspopov committed Jan 7, 2025
1 parent c6cb193 commit 34d0900
Show file tree
Hide file tree
Showing 37 changed files with 1,580 additions and 122 deletions.
210 changes: 210 additions & 0 deletions ui/src/adapters/api/payments.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
import axios from "axios";
import { z } from "zod";

import { Response, buildErrorResponse, buildSuccessResponse } from "src/adapters";
import { ID, IDParser, Message, buildAuthorizationHeader, messageParser } from "src/adapters/api";
import { datetimeParser, getResourceParser, getStrictParser } from "src/adapters/parsers";
import { Env, PaymentConfigurations, PaymentOption } from "src/domain";
import { API_VERSION } from "src/utils/constants";
import { Resource } from "src/utils/types";

type PaymentOptionInput = Omit<PaymentOption, "modifiedAt" | "createdAt"> & {
createdAt: string;
modifiedAt: string;
};

export const paymentOptionParser = getStrictParser<PaymentOptionInput, PaymentOption>()(
z.object({
config: z.array(
z.object({
amount: z.string(),
paymentOptionID: z.number(),
recipient: z.string(),
signingKeyID: z.string(),
})
),
createdAt: datetimeParser,
description: z.string(),
id: z.string(),
issuerDID: z.string(),
modifiedAt: datetimeParser,
name: z.string(),
})
);

export const paymentConfigurationsParser = getStrictParser<PaymentConfigurations>()(
z.record(
z.object({
ChainID: z.number(),
PaymentOption: z.object({
ContractAddress: z.string(),
Name: z.string(),
Type: z.string(),
}),
PaymentRails: z.string(),
})
)
);

export async function getPaymentOptions({
env,
identifier,
params: { maxResults, page },
signal,
}: {
env: Env;
identifier: string;
params: {
maxResults?: number;
page?: number;
};
signal?: AbortSignal;
}): Promise<Response<Resource<PaymentOption>>> {
try {
const response = await axios({
baseURL: env.api.url,
headers: {
Authorization: buildAuthorizationHeader(env),
},
method: "GET",
params: new URLSearchParams({
...(maxResults !== undefined ? { max_results: maxResults.toString() } : {}),
...(page !== undefined ? { page: page.toString() } : {}),
}),
signal,
url: `${API_VERSION}/identities/${identifier}/payment/options`,
});
return buildSuccessResponse(getResourceParser(paymentOptionParser).parse(response.data));
} catch (error) {
return buildErrorResponse(error);
}
}

export type UpsertPaymentOption = Pick<PaymentOption, "name" | "description" | "config">;

export async function createPaymentOption({
env,
identifier,
payload,
}: {
env: Env;
identifier: string;
payload: UpsertPaymentOption;
}): Promise<Response<ID>> {
try {
const response = await axios({
baseURL: env.api.url,
data: payload,
headers: {
Authorization: buildAuthorizationHeader(env),
},
method: "POST",
url: `${API_VERSION}/identities/${identifier}/payment/options`,
});
return buildSuccessResponse(IDParser.parse(response.data));
} catch (error) {
return buildErrorResponse(error);
}
}

export async function updatePaymentOption({
env,
identifier,
payload,
paymentOptionID,
}: {
env: Env;
identifier: string;
payload: UpsertPaymentOption;
paymentOptionID: string;
}) {
try {
await axios({
baseURL: env.api.url,
data: payload,
headers: {
Authorization: buildAuthorizationHeader(env),
},
method: "PATCH",
url: `${API_VERSION}/identities/${identifier}/payment/options/${paymentOptionID}`,
});

return buildSuccessResponse(undefined);
} catch (error) {
return buildErrorResponse(error);
}
}

export async function getPaymentOption({
env,
identifier,
paymentOptionID,
signal,
}: {
env: Env;
identifier: string;
paymentOptionID: string;
signal?: AbortSignal;
}): Promise<Response<PaymentOption>> {
try {
const response = await axios({
baseURL: env.api.url,
headers: {
Authorization: buildAuthorizationHeader(env),
},
method: "GET",
signal,
url: `${API_VERSION}/identities/${identifier}/payment/options/${paymentOptionID}`,
});
return buildSuccessResponse(paymentOptionParser.parse(response.data));
} catch (error) {
return buildErrorResponse(error);
}
}

export async function deletePaymentOption({
env,
identifier,
paymentOptionID,
}: {
env: Env;
identifier: string;
paymentOptionID: string;
}): Promise<Response<Message>> {
try {
const response = await axios({
baseURL: env.api.url,
headers: {
Authorization: buildAuthorizationHeader(env),
},
method: "DELETE",
url: `${API_VERSION}/identities/${identifier}/payment/options/${paymentOptionID}`,
});
return buildSuccessResponse(messageParser.parse(response.data));
} catch (error) {
return buildErrorResponse(error);
}
}

export async function getPaymentConfigurations({
env,
signal,
}: {
env: Env;
signal?: AbortSignal;
}): Promise<Response<PaymentConfigurations>> {
try {
const response = await axios({
baseURL: env.api.url,
headers: {
Authorization: buildAuthorizationHeader(env),
},
method: "GET",
signal,
url: `${API_VERSION}/payment/settings`,
});
return buildSuccessResponse(paymentConfigurationsParser.parse(response.data));
} catch (error) {
return buildErrorResponse(error);
}
}
2 changes: 1 addition & 1 deletion ui/src/adapters/parsers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export function getListParser<Input, Output = Input>(

const resourceMetaParser = getStrictParser<ResourceMeta>()(
z.object({
max_results: z.number().int().min(1),
max_results: z.number().int().min(0),
page: z.number().int().min(1),
total: z.number().int().min(0),
})
Expand Down
41 changes: 41 additions & 0 deletions ui/src/adapters/parsers/view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { z } from "zod";

import { Sorter } from "src/adapters/api";
import { CreateCredential, CreateLink } from "src/adapters/api/credentials";
import { UpsertPaymentOption } from "src/adapters/api/payments";
import { jsonParser } from "src/adapters/json";
import { getStrictParser } from "src/adapters/parsers";
import { getAttributeValueParser } from "src/adapters/parsers/jsonSchemas";
Expand Down Expand Up @@ -299,6 +300,46 @@ export const credentialFormParser = getStrictParser<
})
);

export type PaymentConfigFormData = {
amount: string;
paymentOptionID: string;
recipient: string;
signingKeyID: string;
};

export type PaymentOptionFormData = Omit<UpsertPaymentOption, "config"> & {
config: Array<PaymentConfigFormData>;
};

export const paymentOptionFormParser = getStrictParser<
PaymentOptionFormData,
UpsertPaymentOption
>()(
z
.object({
config: z.array(
z.object({
amount: z.string(),
paymentOptionID: z
.string()
.refine((value) => !isNaN(Number(value)), { message: "Must be a valid number" }),
recipient: z.string(),
signingKeyID: z.string(),
})
),
description: z.string(),
name: z.string(),
})
.transform(({ config, description, name }) => ({
config: config.map(({ paymentOptionID, ...other }) => ({
...other,
paymentOptionID: parseInt(paymentOptionID),
})),
description,
name,
}))
);

// Serializers

function serializeDate(date: dayjs.Dayjs | Date, format: "date" | "date-time" | "time") {
Expand Down
3 changes: 3 additions & 0 deletions ui/src/assets/icons/display-method.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions ui/src/assets/icons/keys.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions ui/src/assets/icons/payment-options.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 1 addition & 8 deletions ui/src/components/connections/ConnectionsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -285,14 +285,7 @@ export function ConnectionsTable() {
}
table={
<Table
columns={tableColumns.map(({ title, ...column }) => ({
title: (
<Typography.Text type="secondary">
<>{title}</>
</Typography.Text>
),
...column,
}))}
columns={tableColumns}
dataSource={connectionsList}
locale={{
emptyText:
Expand Down
9 changes: 1 addition & 8 deletions ui/src/components/connections/CredentialsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -260,14 +260,7 @@ export function CredentialsTable({ userID }: { userID: string }) {
showDefaultContents={showDefaultContent}
table={
<Table
columns={tableColumns.map(({ title, ...column }) => ({
title: (
<Typography.Text type="secondary">
<>{title}</>
</Typography.Text>
),
...column,
}))}
columns={tableColumns}
dataSource={credentialsList}
locale={{
emptyText:
Expand Down
4 changes: 2 additions & 2 deletions ui/src/components/credentials/CreateAuthCredential.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import {
isAsyncTaskStarting,
} from "src/utils/async";
import { isAbortedError, makeRequestAbortable } from "src/utils/browser";
import { VALUE_REQUIRED } from "src/utils/constants";
import { SAVE, VALUE_REQUIRED } from "src/utils/constants";

export function CreateAuthCredential() {
const env = useEnvContext();
Expand Down Expand Up @@ -196,7 +196,7 @@ export function CreateAuthCredential() {

<Flex justify="flex-end">
<Button htmlType="submit" type="primary">
Submit
{SAVE}
</Button>
</Flex>
</Form>
Expand Down
9 changes: 1 addition & 8 deletions ui/src/components/credentials/CredentialsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -368,14 +368,7 @@ export function CredentialsTable() {
showDefaultContents={showDefaultContent}
table={
<Table
columns={tableColumns.map(({ title, ...column }) => ({
title: (
<Typography.Text type="secondary">
<>{title}</>
</Typography.Text>
),
...column,
}))}
columns={tableColumns}
dataSource={credentialsList}
loading={credentials.status === "reloading"}
locale={{
Expand Down
9 changes: 1 addition & 8 deletions ui/src/components/credentials/LinksTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -364,14 +364,7 @@ export function LinksTable() {
showDefaultContents={showDefaultContent}
table={
<Table
columns={tableColumns.map(({ title, ...column }) => ({
title: (
<Typography.Text type="secondary">
<>{title}</>
</Typography.Text>
),
...column,
}))}
columns={tableColumns}
dataSource={linksList}
locale={{
emptyText:
Expand Down
Loading

0 comments on commit 34d0900

Please sign in to comment.