Skip to content

Commit

Permalink
UI Completion
Browse files Browse the repository at this point in the history
  • Loading branch information
d4mr committed Dec 7, 2024
1 parent c4fc088 commit e87dc60
Show file tree
Hide file tree
Showing 12 changed files with 463 additions and 31 deletions.
3 changes: 3 additions & 0 deletions packages/intent-aggregator/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,9 @@ const INTENT_NOT_FOUND_ERROR = {
api.openapi(getIntent, async (c) => {
const intent = await c.var.db.query.intents.findFirst({
where: (intents, { eq }) => eq(intents.id, c.req.param("id")),
with: {
winningSolution: true,
},
});

if (!intent) {
Expand Down
1 change: 1 addition & 0 deletions packages/upi-maker/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"next-themes": "^0.4.4",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-qr-code": "^2.0.15",
"sonner": "^1.7.0",
"tailwind-merge": "^2.5.5",
"tailwindcss-animate": "^1.0.7",
Expand Down
62 changes: 50 additions & 12 deletions packages/upi-maker/src/components/intent-details.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ import { useMutation, useQuery } from '@tanstack/react-query'
import { Skeleton } from './ui/skeleton'
import { useActiveAccount } from 'thirdweb/react'
import { keccak256, toBytes, toUnits, toWei, toTokens } from 'thirdweb'
import { baseSepolia } from 'thirdweb/chains'
import { baseSepolia } from 'thirdweb/chains';
import QRCode from "react-qr-code";
import { intentAggregatorApi } from '@/queries/conts'
import { solutionsQuery, solutionsQueryOptions } from '@/queries/solutions'
import { solutionsQueryOptions } from '@/queries/solutions'

// EIP-712 Type Definitions
const DOMAIN = {
Expand Down Expand Up @@ -53,8 +54,8 @@ type IntentMutationArgs = {

export function IntentDetails({ intentId }: { intentId: string }) {
const [quoteAmount, setQuoteAmount] = useState('')
const intentQuery = useQuery(intentQueryOptions(intentId));
const solutionsQuery = useQuery(solutionsQueryOptions(intentId));
const intentQuery = useQuery({ ...intentQueryOptions(intentId), refetchInterval: 5000 });
const solutionsQuery = useQuery({ ...solutionsQueryOptions(intentId), refetchInterval: 5000 });

const account = useActiveAccount();

Expand All @@ -73,6 +74,7 @@ export function IntentDetails({ intentId }: { intentId: string }) {
})

intentQuery.refetch();
solutionsQuery.refetch();
}
})

Expand Down Expand Up @@ -130,9 +132,26 @@ export function IntentDetails({ intentId }: { intentId: string }) {
)
}

const handlePaymentConfirmation = () => {
toast.success("Payment confirmation received. Awaiting settlement.")
}
const paymentClaimMutation = useMutation({
mutationFn: async () => {
if (!mySolution?.id) throw new Error("Solution not found");

await intentAggregatorApi.post(`solutions/${mySolution.id}/claim`, {
json: {
paymentMetadata: {
"transactionId": "UPI/123/456",
"timestamp": new Date().toISOString(),
"railSpecificData": {}
}
}
})

toast.success("Payment claimed successfully");

intentQuery.refetch();
solutionsQuery.refetch();
}
})

if (loading) {
return (
Expand Down Expand Up @@ -182,8 +201,10 @@ export function IntentDetails({ intentId }: { intentId: string }) {
<CardContent className="space-y-6">
{intent.state === "SOLUTION_COMMITTED" && (
<div className="flex justify-center">
<div className="w-48 h-48 bg-white flex items-center justify-center text-black rounded-lg">
QR Code Stub
<div className="w-48 h-48 p-4 bg-white flex items-center justify-center text-black rounded-lg">
<QRCode value={
`upi://pay?pa=${intent.recipientAddress}&am=${parseFloat(intent.railAmount) / 100}&cu=INR`
} />
</div>
</div>
)}
Expand Down Expand Up @@ -214,8 +235,12 @@ export function IntentDetails({ intentId }: { intentId: string }) {
</div>
</div>
<div className="flex items-center space-x-2 p-2 rounded-lg bg-accent/50">
<img src="https://cryptologos.cc/logos/polygon-matic-logo.svg?v=025" alt="Polygon Logo" width={20} height={20} />
<span className="text-sm text-muted-foreground">Payment will be received on Polygon</span>
{/* https://cryptologos.cc/logos/polygon-matic-logo.svg?v=025 */}
<img src="https://avatars.githubusercontent.com/u/108554348?v=4" alt="Base Sepolia Logo" width={20} height={20} />
<span className="text-sm text-muted-foreground">Payment {
intent.state === "SETTLED" || intent.state === "RESOLVED" ? "has been " : "will be "
}
received on Base Sepolia</span>
</div>
{intent.state === "CREATED" && (
<Button
Expand All @@ -240,12 +265,25 @@ export function IntentDetails({ intentId }: { intentId: string }) {
<Button
variant="secondary"
className="w-full"
onClick={handlePaymentConfirmation}
onClick={() => paymentClaimMutation.mutate()}
>
{paymentClaimMutation.isPending && <Loader2 className="animate-spin" />}
I have made the payment
</Button>
</div>
)}
{
intent.state === "SETTLED" && (
<div>
<div className="p-4 bg-green-500/10 border border-green-500/20 rounded-lg">
<p className="text-sm text-green-500">Taker has settled the payment. Your bond has been refuned.</p>
<p className="text-xs text-muted-foreground mt-2">
Transaction Hash: <span className="font-mono">{intent.winningSolution?.settlementTxHash}</span>
</p>
</div>
</div>
)
}
</CardContent>
</Card>
<Card>
Expand Down
2 changes: 1 addition & 1 deletion packages/upi-maker/src/components/intents-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export function IntentsList({
{/* {intent.state === '' && (
<Loader2 className="h-5 w-5 text-muted-foreground animate-spin" />
)} */}
{intent.state === 'RESOLVED' && (
{intent.state === 'SETTLED' || intent.state === 'RESOLVED' && (
<CheckCircle className="h-5 w-5 text-green-500" />
)}
</button>
Expand Down
1 change: 1 addition & 0 deletions packages/upi-taker/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"vite": "^6.0.3"
},
"dependencies": {
"@radix-ui/react-dialog": "^1.1.2",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-tooltip": "^1.1.4",
"@tanstack/react-query": "^5.62.3",
Expand Down
37 changes: 37 additions & 0 deletions packages/upi-taker/src/components/transaction-timeline.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Check, Loader2 } from 'lucide-react'

interface Step {
label: string
status: 'pending' | 'active' | 'completed'
}

interface TransactionTimelineProps {
steps: Step[]
}

export function TransactionTimeline({ steps }: TransactionTimelineProps) {
return (
<div className="border border-border rounded-lg p-4 space-y-4">
{steps.map((step, index) => (
<div key={index} className="flex items-center space-x-3">
<div className={`w-6 h-6 rounded-full flex items-center justify-center ${
step.status === 'completed' ? 'bg-green-500' :
step.status === 'active' ? 'bg-primary' : 'bg-muted'
}`}>
{step.status === 'completed' ? (
<Check className="w-4 h-4 text-primary-foreground" />
) : step.status === 'active' ? (
<Loader2 className="w-4 h-4 text-primary-foreground animate-spin" />
) : null}
</div>
<span className={`${
step.status === 'pending' ? 'text-muted-foreground' : 'text-foreground'
}`}>
{step.label}
</span>
</div>
))}
</div>
)
}

120 changes: 120 additions & 0 deletions packages/upi-taker/src/components/ui/dialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import * as React from "react"
import * as DialogPrimitive from "@radix-ui/react-dialog"
import { X } from "lucide-react"

import { cn } from "@/lib/utils"

const Dialog = DialogPrimitive.Root

const DialogTrigger = DialogPrimitive.Trigger

const DialogPortal = DialogPrimitive.Portal

const DialogClose = DialogPrimitive.Close

const DialogOverlay = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Overlay
ref={ref}
className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
)}
{...props}
/>
))
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName

const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
className
)}
{...props}
>
{children}
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogPortal>
))
DialogContent.displayName = DialogPrimitive.Content.displayName

const DialogHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col space-y-1.5 text-center sm:text-left",
className
)}
{...props}
/>
)
DialogHeader.displayName = "DialogHeader"

const DialogFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
)}
{...props}
/>
)
DialogFooter.displayName = "DialogFooter"

const DialogTitle = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Title
ref={ref}
className={cn(
"text-lg font-semibold leading-none tracking-tight",
className
)}
{...props}
/>
))
DialogTitle.displayName = DialogPrimitive.Title.displayName

const DialogDescription = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Description
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
DialogDescription.displayName = DialogPrimitive.Description.displayName

export {
Dialog,
DialogPortal,
DialogOverlay,
DialogTrigger,
DialogClose,
DialogContent,
DialogHeader,
DialogFooter,
DialogTitle,
DialogDescription,
}
Loading

0 comments on commit e87dc60

Please sign in to comment.