mirror of
https://github.com/civitai/civitai.git
synced 2026-09-20 22:08:18 +08:00
New USDC available redesign, NEXT_PUBLIC_ZKP2P_IFRAME_HOST env. variable
This commit is contained in:
@@ -157,3 +157,6 @@ NOW_PAYMENTS_IPN_KEY=key
|
||||
COINBASE_API_URL=http://localhost
|
||||
COINBASE_API_KEY=key
|
||||
COINBASE_WEBHOOK_SECRET=secret
|
||||
|
||||
ZKP2P_BASE_URL=https://zkp2p.xyz
|
||||
NEXT_PUBLIC_ZKP2P_IFRAME_HOST=http://localhost:3001
|
||||
|
||||
@@ -29,7 +29,10 @@ import { useBuzzTransactions, useTransactionsReport } from '~/components/Buzz/us
|
||||
import { DaysFromNow } from '~/components/Dates/DaysFromNow';
|
||||
import { UserBuzz } from '~/components/User/UserBuzz';
|
||||
import { BuzzTopUpCard } from '~/components/Buzz/BuzzTopUpCard';
|
||||
import { USDCPurchasePrompt } from '~/components/Buzz/USDCPurchasePrompt';
|
||||
import {
|
||||
USDCPurchasePrompt,
|
||||
useUSDCPurchasePromptVisibility,
|
||||
} from '~/components/Buzz/USDCPurchasePrompt';
|
||||
import type { GetTransactionsReportSchema } from '~/server/schema/buzz.schema';
|
||||
import { TransactionType } from '~/server/schema/buzz.schema';
|
||||
import { formatDate } from '~/utils/date-helpers';
|
||||
@@ -56,6 +59,7 @@ const INCLUDE_DESCRIPTION = [TransactionType.Reward, TransactionType.Purchase];
|
||||
export const BuzzDashboardOverview = ({ accountId }: { accountId: number }) => {
|
||||
const theme = useMantineTheme();
|
||||
const features = useFeatureFlags();
|
||||
const { shouldShow: shouldShowUSDCPrompt } = useUSDCPurchasePromptVisibility(accountId);
|
||||
// Right now, sadly, we neeed to use two separate queries for user and generation transactions.
|
||||
// If this ever changes, that'd be awesome. But for now, we need to do this.
|
||||
const [transactionType, setTransactionType] = React.useState<'user' | 'generation'>('user');
|
||||
@@ -228,14 +232,16 @@ export const BuzzDashboardOverview = ({ accountId }: { accountId: number }) => {
|
||||
{/* USDC Purchase Prompt - Show when user has USDC available */}
|
||||
<USDCPurchasePrompt userId={accountId} />
|
||||
|
||||
{/* Top Up Card - Show when buzz is low */}
|
||||
<BuzzTopUpCard
|
||||
accountId={accountId}
|
||||
variant="banner"
|
||||
message="Need more Buzz?"
|
||||
showBalance={false}
|
||||
btnLabel="Top up"
|
||||
/>
|
||||
{/* Top Up Card - Show when buzz is low and no USDC prompt */}
|
||||
{!shouldShowUSDCPrompt && (
|
||||
<BuzzTopUpCard
|
||||
accountId={accountId}
|
||||
variant="banner"
|
||||
message="Need more Buzz?"
|
||||
showBalance={false}
|
||||
btnLabel="Top up"
|
||||
/>
|
||||
)}
|
||||
|
||||
<SegmentedControl
|
||||
value={reportFilters.window}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
.usdcCard {
|
||||
background: light-dark(
|
||||
var(--mantine-color-teal-0),
|
||||
var(--mantine-color-dark-6)
|
||||
);
|
||||
border: 1px solid light-dark(var(--mantine-color-teal-2), var(--mantine-color-teal-9));
|
||||
border-left: 4px solid var(--mantine-color-teal-5);
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
box-shadow: var(--mantine-shadow-sm);
|
||||
}
|
||||
}
|
||||
|
||||
.loadingCard {
|
||||
background: light-dark(
|
||||
var(--mantine-color-teal-0),
|
||||
var(--mantine-color-dark-6)
|
||||
);
|
||||
border: 1px solid light-dark(var(--mantine-color-teal-2), var(--mantine-color-teal-9));
|
||||
border-left: 4px solid var(--mantine-color-teal-5);
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
box-shadow: var(--mantine-shadow-sm);
|
||||
}
|
||||
}
|
||||
|
||||
.usdcIcon {
|
||||
box-shadow: var(--mantine-shadow-sm);
|
||||
}
|
||||
|
||||
.usdcTitle {
|
||||
color: light-dark(var(--mantine-color-dark-9), var(--mantine-color-gray-0));
|
||||
}
|
||||
|
||||
/* Responsive adjustments */
|
||||
@media (max-width: 768px) {
|
||||
.usdcCard {
|
||||
padding: var(--mantine-spacing-sm);
|
||||
}
|
||||
|
||||
.loadingCard {
|
||||
padding: var(--mantine-spacing-sm);
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,21 @@
|
||||
import { Button, Group, Text, Loader } from '@mantine/core';
|
||||
import { IconBolt } from '@tabler/icons-react';
|
||||
import { Button, Card, Group, Stack, Text, Loader, ThemeIcon, Badge, Center } from '@mantine/core';
|
||||
import { IconBolt, IconWallet, IconArrowRight, IconCurrencyDollar } from '@tabler/icons-react';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useCoinbaseOnrampBalance, useMutateCoinbase } from '~/components/Coinbase/util';
|
||||
import { usdcToBuzz } from '~/utils/buzz';
|
||||
import { numberWithCommas } from '~/utils/number-helpers';
|
||||
import { trpc } from '~/utils/trpc';
|
||||
import { CryptoTransactionStatus } from '~/shared/utils/prisma/enums';
|
||||
import classes from './USDCPurchasePrompt.module.scss';
|
||||
|
||||
interface USDCPurchasePromptProps {
|
||||
userId?: number;
|
||||
}
|
||||
|
||||
export const USDCPurchasePrompt = ({ userId }: USDCPurchasePromptProps) => {
|
||||
const currentUserId = userId;
|
||||
// Hook to check if USDC prompt should show (for other components to use)
|
||||
export const useUSDCPurchasePromptVisibility = (userId?: number) => {
|
||||
const [shouldShow, setShouldShow] = useState(false);
|
||||
|
||||
// First check if user has pending transactions in database
|
||||
|
||||
const { data: transactions, isLoading: isLoadingTransactions } =
|
||||
trpc.coinbase.getPaginatedUserTransactions.useQuery(
|
||||
{
|
||||
@@ -23,19 +23,14 @@ export const USDCPurchasePrompt = ({ userId }: USDCPurchasePromptProps) => {
|
||||
page: 1,
|
||||
},
|
||||
{
|
||||
enabled: !!currentUserId,
|
||||
enabled: !!userId,
|
||||
}
|
||||
);
|
||||
|
||||
// Only check wallet balance if we found pending transactions
|
||||
const { data: balanceData, isLoading: isLoadingBalance } = useCoinbaseOnrampBalance();
|
||||
const { processUserPendingTransactions, processingUserPendingTransactions } = useMutateCoinbase();
|
||||
|
||||
const balance = balanceData?.balance ?? 0;
|
||||
const buzzAmount = usdcToBuzz(balance);
|
||||
|
||||
useEffect(() => {
|
||||
// Check if user has any crypto transactions that indicate they might have USDC
|
||||
if (transactions?.items?.length) {
|
||||
const hasPendingOrSuccess = transactions.items.some(
|
||||
(t) =>
|
||||
@@ -49,6 +44,20 @@ export const USDCPurchasePrompt = ({ userId }: USDCPurchasePromptProps) => {
|
||||
}
|
||||
}, [transactions, balance]);
|
||||
|
||||
return {
|
||||
shouldShow: shouldShow && !isLoadingTransactions,
|
||||
isLoading: isLoadingTransactions || isLoadingBalance,
|
||||
};
|
||||
};
|
||||
|
||||
export const USDCPurchasePrompt = ({ userId }: USDCPurchasePromptProps) => {
|
||||
const { shouldShow, isLoading } = useUSDCPurchasePromptVisibility(userId);
|
||||
const { data: balanceData, isLoading: isLoadingBalance } = useCoinbaseOnrampBalance();
|
||||
const { processUserPendingTransactions, processingUserPendingTransactions } = useMutateCoinbase();
|
||||
|
||||
const balance = balanceData?.balance ?? 0;
|
||||
const buzzAmount = usdcToBuzz(balance);
|
||||
|
||||
const handlePurchase = async () => {
|
||||
if (processingUserPendingTransactions || isLoadingBalance || balance <= 0) {
|
||||
return;
|
||||
@@ -62,54 +71,62 @@ export const USDCPurchasePrompt = ({ userId }: USDCPurchasePromptProps) => {
|
||||
};
|
||||
|
||||
// Don't render anything during initial load or if conditions aren't met
|
||||
if (!shouldShow || isLoadingTransactions) {
|
||||
if (!shouldShow || isLoading) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isLoadingBalance) {
|
||||
return (
|
||||
<Group
|
||||
gap="sm"
|
||||
p="md"
|
||||
style={{
|
||||
backgroundColor: 'var(--mantine-color-teal-1)',
|
||||
borderRadius: 'var(--mantine-radius-md)',
|
||||
border: '2px solid var(--mantine-color-teal-4)',
|
||||
}}
|
||||
>
|
||||
<Loader size="sm" />
|
||||
<Text size="sm" c="teal.8">
|
||||
Checking USDC balance...
|
||||
</Text>
|
||||
</Group>
|
||||
<Card className={classes.loadingCard} padding="md" radius="md" withBorder>
|
||||
<Group gap="sm" justify="center">
|
||||
<Loader size="sm" color="teal" />
|
||||
<Text size="sm" c="teal.8" fw={500}>
|
||||
Checking USDC balance...
|
||||
</Text>
|
||||
</Group>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Group
|
||||
justify="space-between"
|
||||
wrap="wrap"
|
||||
p="md"
|
||||
gap="md"
|
||||
style={{
|
||||
backgroundColor: 'var(--mantine-color-teal-1)',
|
||||
borderRadius: 'var(--mantine-radius-md)',
|
||||
border: '2px solid var(--mantine-color-teal-4)',
|
||||
}}
|
||||
<Card
|
||||
className={classes.usdcCard}
|
||||
padding="md"
|
||||
radius="md"
|
||||
withBorder
|
||||
>
|
||||
<Text size="sm" fw={600} c="teal.8">
|
||||
💰 You have ${balance.toFixed(2)} USDC available
|
||||
</Text>
|
||||
<Button
|
||||
loading={processingUserPendingTransactions}
|
||||
onClick={handlePurchase}
|
||||
color="teal"
|
||||
size="sm"
|
||||
fw={500}
|
||||
leftSection={<IconBolt size={16} fill="currentColor" />}
|
||||
>
|
||||
Purchase {numberWithCommas(buzzAmount)} Buzz
|
||||
</Button>
|
||||
</Group>
|
||||
<Group justify="space-between" wrap="nowrap">
|
||||
<Group gap="sm" wrap="nowrap">
|
||||
<ThemeIcon
|
||||
size="lg"
|
||||
variant="gradient"
|
||||
gradient={{ from: 'teal.4', to: 'green.5' }}
|
||||
radius="md"
|
||||
className={classes.usdcIcon}
|
||||
>
|
||||
<IconWallet size={24} />
|
||||
</ThemeIcon>
|
||||
<div style={{ flex: 1 }}>
|
||||
<Text size="sm" fw={600} className={classes.usdcTitle}>
|
||||
${balance.toFixed(2)} USDC Available
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed">
|
||||
Purchase {numberWithCommas(buzzAmount)} Buzz instantly
|
||||
</Text>
|
||||
</div>
|
||||
</Group>
|
||||
<Button
|
||||
loading={processingUserPendingTransactions}
|
||||
onClick={handlePurchase}
|
||||
variant="gradient"
|
||||
gradient={{ from: 'teal.4', to: 'green.5' }}
|
||||
size="sm"
|
||||
leftSection={<IconBolt size={16} fill="currentColor" />}
|
||||
radius="md"
|
||||
>
|
||||
Purchase Buzz
|
||||
</Button>
|
||||
</Group>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
Vendored
+2
@@ -38,6 +38,7 @@ export const clientSchema = z.object({
|
||||
NEXT_PUBLIC_CLOUDFLARE_TURNSTILE_SITEKEY: z.string().optional(),
|
||||
NEXT_PUBLIC_CF_INVISIBLE_TURNSTILE_SITEKEY: z.string().optional(),
|
||||
NEXT_PUBLIC_CF_MANAGED_TURNSTILE_SITEKEY: z.string().optional(),
|
||||
NEXT_PUBLIC_ZKP2P_IFRAME_HOST: z.string().optional(),
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -80,4 +81,5 @@ export const clientEnv = {
|
||||
NEXT_PUBLIC_CF_INVISIBLE_TURNSTILE_SITEKEY:
|
||||
process.env.NEXT_PUBLIC_CF_INVISIBLE_TURNSTILE_SITEKEY,
|
||||
NEXT_PUBLIC_CF_MANAGED_TURNSTILE_SITEKEY: process.env.NEXT_PUBLIC_CF_MANAGED_TURNSTILE_SITEKEY,
|
||||
NEXT_PUBLIC_ZKP2P_IFRAME_HOST: process.env.NEXT_PUBLIC_ZKP2P_IFRAME_HOST,
|
||||
};
|
||||
|
||||
Vendored
+1
@@ -246,4 +246,5 @@ export const serverSchema = z.object({
|
||||
|
||||
// ZKP2P Related:
|
||||
ZKP2P_BASE_URL: z.string().optional(),
|
||||
ZKP2P_IFRAME_HOST: z.string().optional(),
|
||||
});
|
||||
|
||||
@@ -6,9 +6,9 @@ import { createServerSideProps } from '~/server/utils/server-side-helpers';
|
||||
import { Meta } from '~/components/Meta/Meta';
|
||||
import { trackZkp2pEvent, generateZkp2pSessionId } from '~/utils/zkp2p-tracking';
|
||||
import Script from 'next/script';
|
||||
import { env } from '~/env/client';
|
||||
|
||||
// const ZKP2P_IFRAME_HOST = 'https://zkp2p.civitai.com';
|
||||
const ZKP2P_IFRAME_HOST = 'http://localhost:3001';
|
||||
const ZKP2P_IFRAME_HOST = env.NEXT_PUBLIC_ZKP2P_IFRAME_HOST || 'http://localhost:3001';
|
||||
|
||||
export default function Zkp2pPurchasePage() {
|
||||
const router = useRouter();
|
||||
|
||||
Reference in New Issue
Block a user