add pix qr code to deposit order

This commit is contained in:
João Geonizeli
2021-09-06 00:38:48 -03:00
parent c7a799af1e
commit 3529773c61
23 changed files with 843 additions and 221 deletions

View File

@@ -5,5 +5,11 @@ module Types
has_nodes_field(false)
edges_nullable(false)
edge_nullable(false)
field :total_count, Integer, null: false
def total_count
object.items.count
end
end
end

View File

@@ -10,6 +10,7 @@ module Types
field :status, ProcessStatusEnum, null: false
field :received_amount_cents, Integer, null: false
field :paid_amount_cents, Integer, null: false
field :transaction_id, String, null: false
field :created_at, GraphQL::Types::ISO8601DateTime, null: false
field :updated_at, GraphQL::Types::ISO8601DateTime, null: false
end

View File

@@ -25,6 +25,7 @@ type BuyCryptoOrderConnection {
Information to aid in pagination.
"""
pageInfo: PageInfo!
totalCount: Int!
}
"""
@@ -205,6 +206,7 @@ type DepositOrder implements Node {
paidAmountCents: Int!
receivedAmountCents: Int!
status: ProcessStatus!
transactionId: String!
updatedAt: ISO8601DateTime!
}
@@ -221,6 +223,7 @@ type DepositOrderConnection {
Information to aid in pagination.
"""
pageInfo: PageInfo!
totalCount: Int!
}
"""
@@ -478,6 +481,7 @@ type SellCryptoOrderConnection {
Information to aid in pagination.
"""
pageInfo: PageInfo!
totalCount: Int!
}
"""
@@ -517,6 +521,7 @@ type StakeOrderConnection {
Information to aid in pagination.
"""
pageInfo: PageInfo!
totalCount: Int!
}
"""

View File

@@ -1,10 +1,11 @@
import type { FC } from "react";
import React, { Fragment } from "react";
import { Dialog, Transition } from "@headlessui/react";
import cs from "classnames";
type Props = {
isOpen: boolean;
setIsOpen: (state: boolean) => void;
setIsOpen?: (state: boolean) => void;
title: string;
className?: string;
};
@@ -17,7 +18,9 @@ export const Modal: FC<Props> = ({
className = "",
}) => {
const closeModal = () => {
setIsOpen(false);
if (setIsOpen) {
setIsOpen(false);
}
};
return (
@@ -25,7 +28,7 @@ export const Modal: FC<Props> = ({
<Dialog
open={isOpen}
as="div"
className={`fixed inset-0 z-10 overflow-y-auto ${className}`}
className="fixed inset-0 z-10 overflow-y-auto"
onClose={closeModal}
>
<div className="min-h-screen px-4 text-center">
@@ -56,7 +59,12 @@ export const Modal: FC<Props> = ({
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<div className="inline-block w-full max-w-md p-6 my-8 overflow-hidden text-left align-middle transition-all transform bg-white shadow-xl rounded">
<div
className={cs(
"inline-block w-full max-w-md p-6 my-8 overflow-hidden text-left align-middle transition-all transform bg-white shadow-xl rounded",
className
)}
>
<Dialog.Title
as="h3"
className="text-lg font-medium leading-6 text-gray-900 mb-4"

View File

@@ -3,11 +3,19 @@ import React from "react";
type Props = {
items?: Array<ReactNode | string>;
id?: string;
onClick?: (itemId: string) => void;
};
export const TableRow: FC<Props> = ({ items }) => {
export const TableRow: FC<Props> = ({ items, id, onClick }) => {
const handleClick = () => {
if (onClick && id) {
onClick(id);
}
};
return (
<tr>
<tr onClick={handleClick}>
{items?.map((item, index) => (
<td
key={index}

View File

@@ -2,35 +2,25 @@ import { graphql } from "babel-plugin-relay/macro";
import type { FC } from "react";
import React from "react";
import { useLazyLoadQuery } from "react-relay";
import cs from "classnames";
import { Table, TableRow } from "../../../components";
import type { DepositQuery } from "./__generated__/DepositQuery.graphql";
import { getStatusTextAndColors } from "../utils/processStatus";
import { centsToUnit } from "../../../utils/fiatMoney";
import { Messages } from "../../../messages";
import { History } from "./History";
export const Deposit: FC = () => {
const { depositOrders } = useLazyLoadQuery<DepositQuery>(
graphql`
query DepositQuery {
depositOrders {
edges {
node {
id
status
createdAt
paidAmountCents
receivedAmountCents
}
}
totalCount
...History_depositOrders
}
}
`,
{}
);
if (!depositOrders.edges.length)
if (!depositOrders.totalCount)
return <Messages.NoHistory historyName="depósito" />;
return (
@@ -38,50 +28,7 @@ export const Deposit: FC = () => {
<div className="py-8">
<div className="-mx-4 sm:-mx-8 px-4 sm:px-8 py-4 overflow-x-auto">
<div className="inline-block min-w-full shadow rounded-lg overflow-hidden">
<Table
columns={[
"Montante pago",
"Montante recebido",
"Criado em",
"Status",
]}
>
{depositOrders.edges.map(({ node }) => {
const [label, textStyles, bgStyles] = getStatusTextAndColors(
node.status
);
const status = (
<span
className={cs(
"relative inline-block px-3 py-1 font-semibold text-red-900 leading-tight",
textStyles
)}
>
<span
aria-hidden="true"
className={cs(
"absolute inset-0 opacity-50 rounded-full",
bgStyles
)}
/>
<span className="relative">{label}</span>
</span>
);
return (
<TableRow
key={node.id}
items={[
`${centsToUnit(node.paidAmountCents)} BRL`,
`${centsToUnit(node.receivedAmountCents)} BRL`,
new Date(node.createdAt as string).toLocaleTimeString(),
status,
]}
/>
);
})}
</Table>
<History ordersRef={depositOrders} />
</div>
</div>
</div>

View File

@@ -0,0 +1,87 @@
import { graphql } from "babel-plugin-relay/macro";
import type { FC } from "react";
import React, { useState } from "react";
import cs from "classnames";
import { useFragment } from "react-relay";
import { Table, TableRow } from "../../../../components";
import { getStatusTextAndColors } from "../../utils/processStatus";
import { centsToUnit } from "../../../../utils/fiatMoney";
import type { History_depositOrders$key } from "./__generated__/History_depositOrders.graphql";
import { Show } from "../Show";
type Props = {
ordersRef: History_depositOrders$key;
};
export const History: FC<Props> = ({ ordersRef }) => {
const [openOrderId, setOpenOrderId] = useState<string | null>(null);
const { edges } = useFragment<History_depositOrders$key>(
graphql`
fragment History_depositOrders on DepositOrderConnection {
edges {
node {
id
status
createdAt
paidAmountCents
receivedAmountCents
...Show_deposit_order
}
}
}
`,
ordersRef
);
const openOrder = edges.find(({ node }) => node.id === openOrderId);
const onClose = () => setOpenOrderId(null);
return (
<>
{openOrder && <Show orderRef={openOrder.node} onClose={onClose} />}
<Table
columns={["Montante pago", "Montante recebido", "Criado em", "Status"]}
>
{edges.map(({ node }) => {
const [label, textStyles, bgStyles] = getStatusTextAndColors(
node.status
);
const status = (
<span
className={cs(
"relative inline-block px-3 py-1 font-semibold text-red-900 leading-tight",
textStyles
)}
>
<span
aria-hidden="true"
className={cs(
"absolute inset-0 opacity-50 rounded-full",
bgStyles
)}
/>
<span className="relative">{label}</span>
</span>
);
return (
<TableRow
key={node.id}
onClick={(orderId) => setOpenOrderId(orderId)}
id={node.id}
items={[
`${centsToUnit(node.paidAmountCents)} BRL`,
`${centsToUnit(node.receivedAmountCents)} BRL`,
new Date(node.createdAt as string).toLocaleTimeString(),
status,
]}
/>
);
})}
</Table>
</>
);
};

View File

@@ -0,0 +1,102 @@
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ReaderFragment } from "relay-runtime";
import { FragmentRefs } from "relay-runtime";
export type ProcessStatus = "CANCELED" | "COMPLETED" | "PROCESSING" | "%future added value";
export type History_depositOrders = {
readonly edges: ReadonlyArray<{
readonly node: {
readonly id: string;
readonly status: ProcessStatus;
readonly createdAt: unknown;
readonly paidAmountCents: number;
readonly receivedAmountCents: number;
readonly " $fragmentRefs": FragmentRefs<"Show_deposit_order">;
};
}>;
readonly " $refType": "History_depositOrders";
};
export type History_depositOrders$data = History_depositOrders;
export type History_depositOrders$key = {
readonly " $data"?: History_depositOrders$data;
readonly " $fragmentRefs": FragmentRefs<"History_depositOrders">;
};
const node: ReaderFragment = {
"argumentDefinitions": [],
"kind": "Fragment",
"metadata": null,
"name": "History_depositOrders",
"selections": [
{
"alias": null,
"args": null,
"concreteType": "DepositOrderEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "DepositOrder",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "status",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "createdAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "paidAmountCents",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "receivedAmountCents",
"storageKey": null
},
{
"args": null,
"kind": "FragmentSpread",
"name": "Show_deposit_order"
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"type": "DepositOrderConnection",
"abstractKey": null
};
(node as any).hash = 'f810eed214d3beb7c443588e670d8f39';
export default node;

View File

@@ -0,0 +1 @@
export * from "./History";

View File

@@ -0,0 +1,94 @@
import { graphql } from "babel-plugin-relay/macro";
import type { FC } from "react";
import React from "react";
import { useFragment } from "react-relay";
import copy from "copy-to-clipboard";
import { Button, Modal } from "../../../../components";
import { usePixQr } from "./hooks/usePixQr";
import type { Show_deposit_order$key } from "./__generated__/Show_deposit_order.graphql";
import { centsToUnit } from "../../../../utils/fiatMoney";
import { getStatusTextAndColors } from "../../utils/processStatus";
type Props = {
orderRef: Show_deposit_order$key;
onClose: () => void;
};
export const Show: FC<Props> = ({ orderRef, onClose }) => {
const order = useFragment<Show_deposit_order$key>(
graphql`
fragment Show_deposit_order on DepositOrder {
transactionId
paidAmountCents
receivedAmountCents
status
createdAt
}
`,
orderRef
);
const { qr, payload } = usePixQr({
value: order.paidAmountCents / 100,
transactionId: order.transactionId,
});
const handleClose = (_value: boolean) => {
onClose();
};
const handleCopy = () => {
copy(payload);
};
const [statusLabel] = getStatusTextAndColors(order.status);
return (
<Modal
title="Pedido de deposito"
isOpen
setIsOpen={handleClose}
className="w-full md:max-w-xl"
>
<div className="flex flex-col md:flex-row justify-between">
<div className="md:pt-2">
<ul>
<li>
Montante pago:{" "}
<span className="font-bold">
{centsToUnit(order.paidAmountCents)} BRL
</span>
</li>
<li>
Montante recebido:{" "}
<span className="font-bold">
{centsToUnit(order.receivedAmountCents)}
</span>
</li>
<li>
Pedido feito em:{" "}
<span className="font-bold">
{new Date(order.createdAt as string).toLocaleTimeString()}
</span>
</li>
<li>
Metodo de pagamento: <span className="font-bold">PIX</span>
</li>
<li>
Status: <span className="font-bold">{statusLabel}</span>
</li>
</ul>
</div>
<div>
<img
className="w-full m-auto"
src={qr}
alt="QR code para o PIX de deposito"
/>
<Button onClick={handleCopy}>Copiar codigo</Button>
</div>
</div>
</Modal>
);
};

View File

@@ -0,0 +1,70 @@
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ReaderFragment } from "relay-runtime";
import { FragmentRefs } from "relay-runtime";
export type ProcessStatus = "CANCELED" | "COMPLETED" | "PROCESSING" | "%future added value";
export type Show_deposit_order = {
readonly transactionId: string;
readonly paidAmountCents: number;
readonly receivedAmountCents: number;
readonly status: ProcessStatus;
readonly createdAt: unknown;
readonly " $refType": "Show_deposit_order";
};
export type Show_deposit_order$data = Show_deposit_order;
export type Show_deposit_order$key = {
readonly " $data"?: Show_deposit_order$data;
readonly " $fragmentRefs": FragmentRefs<"Show_deposit_order">;
};
const node: ReaderFragment = {
"argumentDefinitions": [],
"kind": "Fragment",
"metadata": null,
"name": "Show_deposit_order",
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "transactionId",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "paidAmountCents",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "receivedAmountCents",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "status",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "createdAt",
"storageKey": null
}
],
"type": "DepositOrder",
"abstractKey": null
};
(node as any).hash = '73e84cef63c17faa3087f19ba2c73e69';
export default node;

View File

@@ -0,0 +1,33 @@
import { useEffect, useState } from "react";
import { QrCodePix } from "qrcode-pix";
type Props = {
value: number;
transactionId: string;
};
export const usePixQr = ({ value, transactionId }: Props) => {
const [qr, setQr] = useState<string>();
const qrCodePix = QrCodePix({
version: "01",
key: "joao.geonizeli@gmail.com",
name: "X Stake",
city: "TERESOPOLIS",
transactionId,
value,
notRepeatPayment: true,
});
useEffect(() => {
qrCodePix.base64().then((result) => {
setQr(result);
});
}, []);
return {
payload: qrCodePix.payload(),
loading: !qr,
qr,
};
};

View File

@@ -0,0 +1 @@
export * from "./Show";

View File

@@ -3,19 +3,12 @@
// @ts-nocheck
import { ConcreteRequest } from "relay-runtime";
export type ProcessStatus = "CANCELED" | "COMPLETED" | "PROCESSING" | "%future added value";
import { FragmentRefs } from "relay-runtime";
export type DepositQueryVariables = {};
export type DepositQueryResponse = {
readonly depositOrders: {
readonly edges: ReadonlyArray<{
readonly node: {
readonly id: string;
readonly status: ProcessStatus;
readonly createdAt: unknown;
readonly paidAmountCents: number;
readonly receivedAmountCents: number;
};
}>;
readonly totalCount: number;
readonly " $fragmentRefs": FragmentRefs<"History_depositOrders">;
};
};
export type DepositQuery = {
@@ -28,97 +21,66 @@ export type DepositQuery = {
/*
query DepositQuery {
depositOrders {
edges {
node {
id
status
createdAt
paidAmountCents
receivedAmountCents
}
totalCount
...History_depositOrders
}
}
fragment History_depositOrders on DepositOrderConnection {
edges {
node {
id
status
createdAt
paidAmountCents
receivedAmountCents
...Show_deposit_order
}
}
}
fragment Show_deposit_order on DepositOrder {
transactionId
paidAmountCents
receivedAmountCents
status
createdAt
}
*/
const node: ConcreteRequest = (function(){
var v0 = [
{
"alias": null,
"args": null,
"concreteType": "DepositOrderConnection",
"kind": "LinkedField",
"name": "depositOrders",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "DepositOrderEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "DepositOrder",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "status",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "createdAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "paidAmountCents",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "receivedAmountCents",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
];
var v0 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "totalCount",
"storageKey": null
};
return {
"fragment": {
"argumentDefinitions": [],
"kind": "Fragment",
"metadata": null,
"name": "DepositQuery",
"selections": (v0/*: any*/),
"selections": [
{
"alias": null,
"args": null,
"concreteType": "DepositOrderConnection",
"kind": "LinkedField",
"name": "depositOrders",
"plural": false,
"selections": [
(v0/*: any*/),
{
"args": null,
"kind": "FragmentSpread",
"name": "History_depositOrders"
}
],
"storageKey": null
}
],
"type": "Query",
"abstractKey": null
},
@@ -127,17 +89,94 @@ return {
"argumentDefinitions": [],
"kind": "Operation",
"name": "DepositQuery",
"selections": (v0/*: any*/)
"selections": [
{
"alias": null,
"args": null,
"concreteType": "DepositOrderConnection",
"kind": "LinkedField",
"name": "depositOrders",
"plural": false,
"selections": [
(v0/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "DepositOrderEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "DepositOrder",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "status",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "createdAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "paidAmountCents",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "receivedAmountCents",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "transactionId",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
]
},
"params": {
"cacheID": "50b4e177048e536e83735990104fce02",
"cacheID": "99f3fbbd023ef8a38b0490275cb58aa6",
"id": null,
"metadata": {},
"name": "DepositQuery",
"operationKind": "query",
"text": "query DepositQuery {\n depositOrders {\n edges {\n node {\n id\n status\n createdAt\n paidAmountCents\n receivedAmountCents\n }\n }\n }\n}\n"
"text": "query DepositQuery {\n depositOrders {\n totalCount\n ...History_depositOrders\n }\n}\n\nfragment History_depositOrders on DepositOrderConnection {\n edges {\n node {\n id\n status\n createdAt\n paidAmountCents\n receivedAmountCents\n ...Show_deposit_order\n }\n }\n}\n\nfragment Show_deposit_order on DepositOrder {\n transactionId\n paidAmountCents\n receivedAmountCents\n status\n createdAt\n}\n"
}
};
})();
(node as any).hash = 'f4d2d75a8903d262de47e02f44136a65';
(node as any).hash = '8394525008fabe782ee41126e50d63b1';
export default node;

View File

@@ -10,6 +10,7 @@
# status :string not null
# created_at :datetime not null
# updated_at :datetime not null
# transaction_id :uuid not null
# user_id :bigint not null
#
# Indexes