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
728 changes: 445 additions & 283 deletions dapp/package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions dapp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"lint": "next lint"
},
"dependencies": {
"@radix-ui/react-dialog": "^1.1.11",
"@starknet-react/chains": "^3.1.2",
"@starknet-react/core": "^3.7.2",
"lucide-react": "^0.503.0",
Expand Down
232 changes: 232 additions & 0 deletions dapp/src/components/ViewUnlistedAssetDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
import React, { useState } from 'react';
import * as Dialog from '@radix-ui/react-dialog';
import Image from 'next/image';
import { X } from 'lucide-react';

export interface Asset {
id: string;
name: string;
collection: string;
imageUrl: string;
floorPrice: string;
}

interface ViewUnlistedAssetDialogProps {
asset: Asset;
open?: boolean;
onOpenChange?: (open: boolean) => void;
children?: React.ReactNode;
}

const currencies = ['STKR', 'USD', 'ETH'];
const durations = ['7 days', '14 days', '30 days'];

export const ViewUnlistedAssetDialog: React.FC<ViewUnlistedAssetDialogProps> = ({
asset,
open,
onOpenChange,
children,
}) => {
const [step, setStep] = useState<'view' | 'terms' | 'review' | 'success'>('view');
const [currency, setCurrency] = useState(currencies[0]);
const [amount, setAmount] = useState('');
const [duration, setDuration] = useState(durations[0]);
const [apr, setApr] = useState('');

const closeAll = () => {
setStep('view');
onOpenChange && onOpenChange(false);
};

return (
<Dialog.Root open={open} onOpenChange={onOpenChange}>
<Dialog.Trigger asChild>{children}</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Overlay className="fixed inset-0 bg-black/50 backdrop-blur-sm" />
<Dialog.Content className="fixed top-1/2 left-1/2 w-[90vw] max-w-lg -translate-x-1/2 -translate-y-1/2 bg-[#0D0E29] p-6 rounded-2xl">
<Dialog.Close asChild>
<button className="absolute top-4 right-4 text-gray-400 hover:text-white">
<X size={20} />
</button>
</Dialog.Close>

{/* View Step */}
{step === 'view' && (
<>
<div className="w-full h-[200px] relative rounded-lg overflow-hidden">
<Image src={asset.imageUrl} alt={asset.name} fill className="object-cover" />
</div>
<div className="mt-4">
<h2 className="text-xl font-semibold text-white">{asset.name}</h2>
<p className="text-xs text-gray-400">{asset.collection}</p>
<span className="inline-block text-xs uppercase bg-amber-400 text-gray-900 px-2 py-0.5 rounded mt-2">
Unlisted
</span>
</div>
<div className="mt-6">
<div className="flex items-center text-white text-sm font-semibold mb-2">
<span className="w-4 h-4 block mr-1">💰</span>
Price Information
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<p className="text-xs uppercase text-gray-400">Collection Floor</p>
<p className="text-sm text-white">{asset.floorPrice}</p>
</div>
<div>
<p className="text-xs uppercase text-gray-400">Collection Floor</p>
<p className="text-sm text-white">{asset.floorPrice}</p>
</div>
</div>
<p className="mt-2 text-xs text-gray-500">
The above data is for informational purposes only. Please conduct your own research before making decisions.
</p>
</div>
<div className="mt-6">
<button
className="w-full h-10 bg-purple-600 text-white rounded-md hover:bg-purple-700"
onClick={() => setStep('terms')}
>
List As Collateral
</button>
</div>
</>
)}

{/* Terms Step */}
{step === 'terms' && (
<>
<h3 className="text-xl font-semibold text-white mb-4">List As Collateral</h3>
{/* Tabs */}
<div className="flex space-x-2 mb-4">
{currencies.map((cur) => (
<button
key={cur}
onClick={() => setCurrency(cur)}
className={`px-4 py-1 rounded-full text-sm font-medium ${
currency === cur
? 'bg-purple-600 text-white'
: 'bg-[#1c1f3f] text-gray-400'
}`}
>
{cur}
</button>
))}
</div>
<div className="space-y-4">
<div>
<label className="block text-xs text-gray-400 mb-1">Amount</label>
<input
type="number"
value={amount}
onChange={(e) => setAmount(e.target.value)}
className="w-full p-2 bg-[#1c1f3f] rounded-md text-white text-sm"
/>
</div>
<div>
<label className="block text-xs text-gray-400 mb-1">Duration</label>
<select
value={duration}
onChange={(e) => setDuration(e.target.value)}
className="w-full p-2 bg-[#1c1f3f] rounded-md text-white text-sm"
>
{durations.map((d) => (
<option key={d} value={d} className="bg-[#0D0E29]">
{d}
</option>
))}
</select>
</div>
<div>
<label className="block text-xs text-gray-400 mb-1">APR (%)</label>
<input
type="number"
value={apr}
onChange={(e) => setApr(e.target.value)}
className="w-full p-2 bg-[#1c1f3f] rounded-md text-white text-sm"
/>
</div>
</div>
<div className="mt-6 flex justify-between">
<button
className="px-4 py-2 h-10 border border-gray-500 rounded-md text-sm text-white"
onClick={() => setStep('view')}
>
Go Back
</button>
<button
className="px-4 py-2 h-10 bg-purple-600 text-white rounded-md text-sm hover:bg-purple-700"
onClick={() => setStep('review')}
>
Continue
</button>
</div>
</>
)}

{/* Review Step */}
{step === 'review' && (
<>
<h3 className="text-xl font-semibold text-white mb-4">List As Collateral</h3>
<div className="flex flex-col lg:flex-row gap-6">
<div className="flex-1 bg-[#1c1f3f] p-4 rounded-lg">
<div className="w-full h-36 relative rounded-lg overflow-hidden">
<Image src={asset.imageUrl} alt={asset.name} fill className="object-cover" />
</div>
<h4 className="mt-2 text-sm font-semibold text-white">{asset.name}</h4>
<p className="text-xs text-gray-400">{asset.collection}</p>
<span className="inline-block text-xs uppercase bg-amber-400 text-gray-900 px-2 py-0.5 rounded mt-1">
Unlisted
</span>
</div>
<div className="w-full lg:w-1/3 bg-[#1c1f3f] p-4 rounded-lg text-white text-sm">
<p className="uppercase text-xs text-gray-400">Currency</p>
<p className="mb-2">{currency}</p>
<p className="uppercase text-xs text-gray-400">Acceptable Amount</p>
<p className="mb-2">{amount} {currency}</p>
<p className="uppercase text-xs text-gray-400">Loan Duration</p>
<p className="mb-2">{duration}</p>
<p className="uppercase text-xs text-gray-400">APR</p>
<p className="mb-4">{apr}%</p>
<p className="text-xs text-gray-500">You can cancel your loan at any time.</p>
</div>
</div>
<div className="mt-6 flex justify-between">
<button
className="px-4 py-2 h-10 border border-gray-500 rounded-md text-sm text-white"
onClick={() => setStep('terms')}
>
Go Back
</button>
<button
className="px-4 py-2 h-10 bg-purple-600 text-white rounded-md text-sm hover:bg-purple-700"
onClick={() => setStep('success')}
>
Confirm
</button>
</div>
</>
)}

{/* Success Step */}
{step === 'success' && (
<div className="text-center">
<div className="w-full h-[150px] relative rounded-lg overflow-hidden mb-4">
<Image src={asset.imageUrl} alt={asset.name} fill className="object-cover" />
</div>
<h4 className="text-lg font-semibold text-white mb-2">Your Asset has been Listed!</h4>
<button
className="w-full py-2 h-10 bg-purple-600 text-white rounded-md hover:bg-purple-700"
onClick={closeAll}
>
Check Offers
</button>
</div>
)}
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
);
};

export default ViewUnlistedAssetDialog;
4 changes: 2 additions & 2 deletions dapp/src/components/connectwallet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ const Connectwallet = () => {
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
fillRule="evenodd"
clipRule="evenodd"
d="M11.6075 6.14966C11.801 5.97766 12.0972 5.99509 12.2692 6.18859L15.3804 9.68857C15.5382 9.86619 15.5382 10.1338 15.3804 10.3114L12.2692 13.8114C12.0972 14.0049 11.801 14.0223 11.6075 13.8503C11.414 13.6784 11.3966 13.3821 11.5686 13.1886L13.9862 10.4688H5.96753C5.70864 10.4688 5.49878 10.2589 5.49878 10C5.49878 9.74113 5.70864 9.53126 5.96753 9.53126H13.9862L11.5686 6.81143C11.3966 6.61794 11.414 6.32165 11.6075 6.14966Z"
fill="white"
/>
Expand Down
127 changes: 84 additions & 43 deletions dapp/src/components/myassets/NFTGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,57 +3,98 @@ import Image from "next/image";
import React from "react";
import { NFT } from "@/app/types";
import Link from "next/link";
import ViewUnlistedAssetDialog from '@/components/ViewUnlistedAssetDialog';

interface NFTGridProps {
nfts: NFT[];
}

export const NFTGrid = ({ nfts }: NFTGridProps) => (
<div className="grid grid-cols-4 gap-3">
{nfts.map((nft) => (
<Link
href={{
pathname: "/escrow",
query: {
image: typeof nft.image === "string" ? nft.image : nft.image.src,
name: nft.name,
status: nft.status,
collectionName: nft.collectionName,
},
}}
key={nft.id}
>
<div className="bg-[#0D0E29] rounded-2xl overflow-hidden w-full max-w-fit px-1 py-1 cursor-pointer">
<div className="w-full relative">
<Image
src={nft.image}
alt={nft.name}
width={900}
height={900}
className="object-fill"
/>
</div>
<div className="p-2">
<div className="flex justify-between items-center">
<h4 className="text-xs font-medium">{nft.name}</h4>
<span
className={`px-2 py-1 text-xs rounded-full ${
nft.status === "Listed"
? "bg-green-900 text-[#42CC7E]"
: nft.status === "Unlisted"
? "bg-amber-900 text-[#fb9224]"
: "bg-purple-900 text-[#b19ee4]"
}`}
>
{nft.status}
</span>
{nfts.map((nft) => {
// Unlisted assets open the List-as-Collateral dialog
if (nft.status === 'Unlisted') {
const imageUrl = typeof nft.image === 'string' ? nft.image : nft.image.src;
return (
<ViewUnlistedAssetDialog
key={nft.id}
asset={{
id: nft.id,
name: nft.name,
collection: nft.collectionName,
imageUrl,
floorPrice: `0.00 ${nft.collectionTag}`,
}}
>
<div className="bg-[#0D0E29] rounded-2xl overflow-hidden w-full max-w-fit px-1 py-1 cursor-pointer">
<div className="w-full relative">
<Image
src={nft.image}
alt={nft.name}
width={900}
height={900}
className="object-fill"
/>
</div>
<div className="p-2">
<div className="flex justify-between items-center">
<h4 className="text-xs font-medium">{nft.name}</h4>
<span className="px-2 py-1 text-xs rounded-full bg-amber-900 text-[#fb9224]">
{nft.status}
</span>
</div>
<p className="text-xs text-gray-400">
{nft.collectionName} ({nft.collectionTag})
</p>
</div>
</div>
</ViewUnlistedAssetDialog>
);
}
// Other statuses navigate to escrow
return (
<Link
href={{
pathname: "/escrow",
query: {
image: typeof nft.image === "string" ? nft.image : nft.image.src,
name: nft.name,
status: nft.status,
collectionName: nft.collectionName,
},
}}
key={nft.id}
>
<div className="bg-[#0D0E29] rounded-2xl overflow-hidden w-full max-w-fit px-1 py-1 cursor-pointer">
<div className="w-full relative">
<Image
src={nft.image}
alt={nft.name}
width={900}
height={900}
className="object-fill"
/>
</div>
<div className="p-2">
<div className="flex justify-between items-center">
<h4 className="text-xs font-medium">{nft.name}</h4>
<span
className={`px-2 py-1 text-xs rounded-full ${
nft.status === "Listed"
? "bg-green-900 text-[#42CC7E]"
: "bg-purple-900 text-[#b19ee4]"
}`}
>
{nft.status}
</span>
</div>
<p className="text-xs text-gray-400">
{nft.collectionName} ({nft.collectionTag})
</p>
</div>
<p className="text-xs text-gray-400">
{nft.collectionName} ({nft.collectionTag})
</p>
</div>
</div>
</Link>
))}
</Link>
);
})}
</div>
);
Loading