Initial layout for buzz marketplace

This commit is contained in:
manuelurenah
2025-07-08 14:50:00 -04:00
parent 9381d09be3
commit 474a675a06
10 changed files with 8988 additions and 6353 deletions
+4
View File
@@ -155,3 +155,7 @@ NOW_PAYMENTS_IPN_KEY=key
COINBASE_API_URL=http://localhost
COINBASE_API_KEY=key
COINBASE_WEBHOOK_SECRET=secret
EMERCHANTPAY_WPF_URL=
EMERCHANTPAY_USERNAME=
EMERCHANTPAY_PASSWORD=
+8715 -6349
View File
File diff suppressed because it is too large Load Diff
+1 -4
View File
@@ -72,7 +72,7 @@
"@emotion/react": "^11.10.4",
"@essentials/one-key-map": "^1.2.0",
"@google-cloud/recaptcha-enterprise": "^5.1.1",
"@headlessui/react": "2.2",
"@headlessui/react": "^2.2.4",
"@hookform/resolvers": "^3.3.4",
"@mantine/core": "^7.17.7",
"@mantine/dates": "^7.17.7",
@@ -283,8 +283,5 @@
},
"ct3aMetadata": {
"initVersion": "6.2.1"
},
"overrides": {
"@react-aria/interactions": "3.16.0"
}
}
+58
View File
@@ -0,0 +1,58 @@
import { Anchor, Button, Card, Paper, Text, Title } from '@mantine/core';
import { z } from 'zod';
import { useMarketplaceContext } from '~/components/Marketplace/MarketplaceProvider';
import { Form, InputNumber, InputSelect, useForm } from '~/libs/form';
import { MarketplacePaymentMethod } from '~/server/common/enums';
import { formatCurrencyForDisplay } from '~/utils/number-helpers';
const minBuzzAmount = 1000; // Minimum amount of Buzz to list for sale
const availablePaymentMethods = Object.values(MarketplacePaymentMethod);
const schema = z.object({
amount: z.number().min(minBuzzAmount, 'Minimum amount is 1000 Buzz'),
paymentMethod: z.nativeEnum(MarketplacePaymentMethod),
currency: z.enum(['USD', 'EUR', 'GBP']),
});
export function BuyBuzz() {
const { currency } = useMarketplaceContext();
const form = useForm({ schema });
const handleSubmit = (data: z.input<typeof schema>) => {
console.log(data, currency);
};
const [amount = 0] = form.watch(['amount']);
// const totalValue = (amount / 10) * price; // Total value in the selected currency
// const totalPrice = formatCurrencyForDisplay(totalValue, currency, { decimals: true });
return (
<Card className="flex flex-col" radius="md" withBorder>
<Title order={2} className="mb-2 text-lg font-semibold">
Buy Buzz
</Title>
<Form className="flex flex-col gap-4" form={form} onSubmit={handleSubmit}>
<InputNumber
name="amount"
label="Amount to purchase"
step={minBuzzAmount}
min={minBuzzAmount}
/>
<InputSelect name="paymentMethod" label="Payment Method" data={availablePaymentMethods} />
<Paper className="flex h-12 items-center justify-center" withBorder>
Best match goes here
</Paper>
<Button type="submit" disabled={!amount} fullWidth>
Proceed to Purchase
</Button>
<Text size="xs" c="dimmed">
Payments handled via{' '}
<Anchor href="https://zkp2p.xyz" target="_blank" rel="nofollow noreferrer">
ZKP2P
</Anchor>{' '}
secure escrow. Learn More
</Text>
</Form>
</Card>
);
}
@@ -0,0 +1,50 @@
import { Button, Card, Text, Title } from '@mantine/core';
import { z } from 'zod';
import { useMarketplaceContext } from '~/components/Marketplace/MarketplaceProvider';
import { Form, InputNumber, useForm } from '~/libs/form';
import { formatCurrencyForDisplay } from '~/utils/number-helpers';
const minBuzzAmount = 1000; // Minimum amount of Buzz to list for sale
const schema = z.object({
amount: z.number().min(minBuzzAmount, 'Minimum amount is 1000 Buzz'),
price: z.number().min(0.01, 'Price must be greater than 0'),
currency: z.enum(['USD', 'EUR', 'GBP']),
});
export function ListBuzzSale() {
const { currency } = useMarketplaceContext();
const form = useForm({ schema });
const handleSubmit = (data: z.input<typeof schema>) => {
console.log(data);
};
const [amount = 0, price = 0] = form.watch(['amount', 'price']);
const totalValue = (amount / 10) * price; // Total value in the selected currency
const totalPrice = formatCurrencyForDisplay(totalValue, currency, { decimals: true });
return (
<Card className="flex flex-col" aria-label="List Buzz for Sale" radius="md" withBorder>
<Title order={2} className="mb-2 text-lg font-semibold">
List Buzz for Sale
</Title>
<Form className="flex flex-col gap-4" form={form} onSubmit={handleSubmit}>
<InputNumber name="amount" label="Amount" step={minBuzzAmount} min={minBuzzAmount} />
<InputNumber name="price" label="Price per thousand" step={0.01} min={0.01} />
<Text size="sm">
Total value:{' '}
<Text fw="bold" span>
${totalPrice}
</Text>
</Text>
<Button type="submit" disabled={!totalValue} fullWidth>
Post Listing - ${totalPrice}
</Button>
<Text size="xs" c="dimmed">
Buzz is deducted immediately. Canceling listings costs 100 Buzz
</Text>
</Form>
</Card>
);
}
@@ -0,0 +1,53 @@
import { Card, Stack, Group, Button, Title, Select, Paper, Text, Table } from '@mantine/core';
import { useMarketplaceContext } from '~/components/Marketplace/MarketplaceProvider';
import { useCurrentUser } from '~/hooks/useCurrentUser';
import { Currency } from '~/shared/utils/prisma/enums';
const currencies = Object.values(Currency);
export function MarketplaceOverview() {
const { currency, setCurrency } = useMarketplaceContext();
const currentUser = useCurrentUser();
return (
<Card className="flex flex-col gap-5" radius="md" withBorder>
<Group gap="xs" justify="space-between" align="center">
<Stack gap={0}>
<Title>Market Overview</Title>
<Text>Buy Red Buzz privately through secure escrow</Text>
</Stack>
<Group gap="sm">
<Select
w={100}
data={currencies}
defaultValue={currency}
onChange={(value) => setCurrency((value as Currency | null) ?? Currency.USD)}
/>
{currentUser ? <Button variant="default">Seller Settings</Button> : null}
</Group>
</Group>
<Paper radius="md">
<div className="flex h-96 items-center justify-center">
<Title order={3} c="dimmed">
Coming Soon! Marketplace overview is under development.
</Title>
</div>
</Paper>
<Stack>
<Title order={2}>Current Listings</Title>
<Table>
<Table.Tbody>
<Table.Tr>
<Table.Td>$0.85 per 1K</Table.Td>
<Table.Td>1,000,000 Buzz</Table.Td>
</Table.Tr>
<Table.Tr>
<Table.Td>$0.85 per 1K</Table.Td>
<Table.Td>1,000,000 Buzz</Table.Td>
</Table.Tr>
</Table.Tbody>
</Table>
</Stack>
</Card>
);
}
@@ -0,0 +1,26 @@
import { createContext, useContext, useState } from 'react';
import { Currency } from '~/shared/utils/prisma/enums';
type ProviderState = {
currency: Currency;
setCurrency: (currency: Currency) => void;
};
const MarketplaceContext = createContext<ProviderState | undefined>(undefined);
export const useMarketplaceContext = () => {
const context = useContext(MarketplaceContext);
if (!context) {
throw new Error('useMarketplaceContext must be used within a MarketPlaceProvider');
}
return context;
};
export function MarketplaceProvider({ children }: { children: React.ReactNode }) {
const [currency, setCurrency] = useState<Currency>(Currency.USD);
return (
<MarketplaceContext.Provider value={{ currency, setCurrency }}>
{children}
</MarketplaceContext.Provider>
);
}
@@ -0,0 +1,27 @@
import { Card, Tabs, Text, Title } from '@mantine/core';
export function SellerListings() {
return (
<Card className="flex flex-col" aria-label="Your Listings" radius="md" withBorder>
<Title order={2} className="mb-2 text-lg font-semibold">
Your Listings
</Title>
<Tabs>
<Tabs.List>
<Tabs.Tab value="open">Open</Tabs.Tab>
<Tabs.Tab value="closed">Closed</Tabs.Tab>
<Tabs.Tab value="canceled">Canceled</Tabs.Tab>
</Tabs.List>
<Tabs.Panel value="open">
<Text>View and manage your active Buzz listings.</Text>
</Tabs.Panel>
<Tabs.Panel value="closed">
<Text>View your completed listings.</Text>
</Tabs.Panel>
<Tabs.Panel value="canceled">
<Text>View your canceled listings.</Text>
</Tabs.Panel>
</Tabs>
</Card>
);
}
+46
View File
@@ -0,0 +1,46 @@
import { Card, Container, Stack, Text, Title } from '@mantine/core';
import { BuyBuzz } from '~/components/Marketplace/BuyBuzz';
import { ListBuzzSale } from '~/components/Marketplace/ListBuzzSale';
import { MarketplaceOverview } from '~/components/Marketplace/MarketplaceOverview';
import { MarketplaceProvider } from '~/components/Marketplace/MarketplaceProvider';
import { SellerListings } from '~/components/Marketplace/SellerListings';
import { Meta } from '~/components/Meta/Meta';
import { env } from '~/env/client';
import { createServerSideProps } from '~/server/utils/server-side-helpers';
export const getServerSideProps = createServerSideProps({
resolver: async () => {
// Do something server-side if needed
return { props: {} };
},
});
export default function MarketplacePage() {
return (
<MarketplaceProvider>
<Meta
title="Buzz Marketplace | Civitai"
description="Explore the latest marketplace trends and insights on Civitai."
links={
env.NEXT_PUBLIC_BASE_URL
? [{ rel: 'canonical', href: `${env.NEXT_PUBLIC_BASE_URL}/buzz/marketplace` }]
: undefined
}
/>
<Container size="xl" py="lg">
<Stack gap="xl">
<MarketplaceOverview />
<div
className="grid grid-cols-1 gap-6 md:grid-cols-3"
role="region"
aria-label="Marketplace actions"
>
<ListBuzzSale />
<BuyBuzz />
<SellerListings />
</div>
</Stack>
</Container>
</MarketplaceProvider>
);
}
+8
View File
@@ -393,3 +393,11 @@ export enum ModReviewType {
Appeals = 'appeal',
RuleViolations = 'modRule',
}
export enum MarketplacePaymentMethod {
CashApp = 'CashApp',
Revolut = 'Revolut',
Venmo = 'Venmo',
Wise = 'Wise',
Zelle = 'Zelle',
}