Skip to content
Merged
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
1 change: 1 addition & 0 deletions libs/burn-waitlist/src/lib/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ export * from './status-messages';
export * from './wallet-connection-warning';
export * from './network-warning';
export * from './waitlist-balances';
export * from './price-chart';
51 changes: 50 additions & 1 deletion libs/burn-waitlist/src/lib/components/participation-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,21 @@
import { useMemo } from 'react';
import { Button } from '@haqq/shell-ui-kit';
import { ModalInput } from '@haqq/shell-ui-kit';
import { formatUnits } from 'viem';

Check notice

Code scanning / CodeQL

Unused variable, import, function or class Note

Unused import formatUnits.

Copilot Autofix

AI 8 days ago

In general, the correct fix for an unused import is to remove that import line (or just the unused specifier from a grouped import) so that the code only imports what it actually uses. This avoids confusion and keeps the dependency surface minimal without affecting runtime behavior.

For this specific case in libs/burn-waitlist/src/lib/components/participation-form.tsx, the best fix is to delete the line import { formatUnits } from 'viem'; at line 6. No other parts of the file need to change, since formatUnits is not referenced in the props, component body, or other visible code, and we are already using formatEthDecimal and formatNumberWithSuffix for formatting. No new methods, imports, or definitions are required.

Suggested changeset 1
libs/burn-waitlist/src/lib/components/participation-form.tsx

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/libs/burn-waitlist/src/lib/components/participation-form.tsx b/libs/burn-waitlist/src/lib/components/participation-form.tsx
--- a/libs/burn-waitlist/src/lib/components/participation-form.tsx
+++ b/libs/burn-waitlist/src/lib/components/participation-form.tsx
@@ -3,7 +3,6 @@
 import { useMemo } from 'react';
 import { Button } from '@haqq/shell-ui-kit';
 import { ModalInput } from '@haqq/shell-ui-kit';
-import { formatUnits } from 'viem';
 import { FundsSource } from '../constants/waitlist-config';
 import { WaitlistBalances } from './waitlist-balances';
 import type { WaitlistBalancesResponse } from '../hooks/use-waitlist-balances';
EOF
@@ -3,7 +3,6 @@
import { useMemo } from 'react';
import { Button } from '@haqq/shell-ui-kit';
import { ModalInput } from '@haqq/shell-ui-kit';
import { formatUnits } from 'viem';
import { FundsSource } from '../constants/waitlist-config';
import { WaitlistBalances } from './waitlist-balances';
import type { WaitlistBalancesResponse } from '../hooks/use-waitlist-balances';
Copilot is powered by AI and may make mistakes. Always verify output.
import { FundsSource } from '../constants/waitlist-config';
import { WaitlistBalances } from './waitlist-balances';
import type { WaitlistBalancesResponse } from '../hooks/use-waitlist-balances';
import { formatEthDecimal } from '@haqq/shell-shared';
import { formatEthDecimal, formatNumberWithSuffix } from '@haqq/shell-shared';

Check notice

Code scanning / CodeQL

Unused variable, import, function or class Note

Unused import formatNumberWithSuffix.

Copilot Autofix

AI 8 days ago

In general, unused imports should be removed so that only symbols actually referenced in the file are imported. This improves readability and avoids confusion about whether an imported utility is supposed to be used.

For this file, the best fix is to update the import on line 10 of libs/burn-waitlist/src/lib/components/participation-form.tsx to remove formatNumberWithSuffix while keeping formatEthDecimal, which is used in the formattedBalance computation. No other code changes are necessary because no code references formatNumberWithSuffix in the provided snippet.

Concretely: edit the import statement starting with import { formatEthDecimal, formatNumberWithSuffix } from '@haqq/shell-shared'; so that it only imports formatEthDecimal. No new imports, methods, or definitions are required.

Suggested changeset 1
libs/burn-waitlist/src/lib/components/participation-form.tsx

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/libs/burn-waitlist/src/lib/components/participation-form.tsx b/libs/burn-waitlist/src/lib/components/participation-form.tsx
--- a/libs/burn-waitlist/src/lib/components/participation-form.tsx
+++ b/libs/burn-waitlist/src/lib/components/participation-form.tsx
@@ -7,7 +7,7 @@
 import { FundsSource } from '../constants/waitlist-config';
 import { WaitlistBalances } from './waitlist-balances';
 import type { WaitlistBalancesResponse } from '../hooks/use-waitlist-balances';
-import { formatEthDecimal, formatNumberWithSuffix } from '@haqq/shell-shared';
+import { formatEthDecimal } from '@haqq/shell-shared';
 
 export interface ParticipationFormProps {
   amount: string;
EOF
@@ -7,7 +7,7 @@
import { FundsSource } from '../constants/waitlist-config';
import { WaitlistBalances } from './waitlist-balances';
import type { WaitlistBalancesResponse } from '../hooks/use-waitlist-balances';
import { formatEthDecimal, formatNumberWithSuffix } from '@haqq/shell-shared';
import { formatEthDecimal } from '@haqq/shell-shared';

export interface ParticipationFormProps {
amount: string;
Copilot is powered by AI and may make mistakes. Always verify output.

export interface ParticipationFormProps {
amount: string;
source: FundsSource;
availableBalance?: bigint;
balances?: WaitlistBalancesResponse;
/** Current price per token (atto) from API - used to show price at submission and estimated receive */
currentPriceAtto?: string;
/** User amount in wei - used with currentPriceAtto to compute estimated receive */
formattedAmount?: bigint;
onAmountChange: (amount: string) => void;
onSourceChange: (source: FundsSource) => void;
onMaxClick: () => void;
Expand All @@ -29,6 +34,8 @@
source,
availableBalance,
balances,
currentPriceAtto,
formattedAmount,
onAmountChange,
onSourceChange,
onMaxClick,
Expand All @@ -50,9 +57,42 @@
return formatEthDecimal(availableBalance, 4);
}, [availableBalance]);

const priceDisplay = useMemo(() => {
if (!currentPriceAtto) return null;
try {
const priceWei = BigInt(currentPriceAtto);
if (priceWei === 0n) return null;
return formatEthDecimal(priceWei, 4, 0);
} catch {
return null;
}
}, [currentPriceAtto]);

const estimatedReceiveDisplay = useMemo(() => {
if (!formattedAmount || !currentPriceAtto) return null;
try {
const priceWei = BigInt(currentPriceAtto);
if (priceWei === 0n) return null;
// Use BigInt division to avoid Number precision loss for large values
const tokensWei = formattedAmount / priceWei;

return formatEthDecimal(tokensWei, 4, 18);
} catch {
return null;
}
}, [formattedAmount, currentPriceAtto]);

return (
<div className="space-y-[20px]">
<WaitlistBalances balances={balances} />
{priceDisplay !== null && (
<div className="rounded-[8px] bg-[#F3F4F6] p-[12px]">
<div className="text-[12px] text-[#6B7280]">Price at submission</div>
<div className="text-[14px] font-[500] text-[#0D0D0E]">
{priceDisplay} ISLM per token
</div>
</div>
)}
<div>
<label className="mb-[8px] block text-[14px] font-medium text-[#0D0D0E]">
Amount
Expand Down Expand Up @@ -89,6 +129,15 @@
}
disabled={disabled}
/>
{estimatedReceiveDisplay !== null && (
<div className="mt-[8px] text-[13px] text-[#6B7280]">
Estimated receive:{' '}
<span className="font-[500] text-[#0D0D0E]">
{estimatedReceiveDisplay}
</span>{' '}
tokens
</div>
)}
</div>

{/* Only show Funds Source selection if ucDAO balance is greater than 0 */}
Expand Down
251 changes: 251 additions & 0 deletions libs/burn-waitlist/src/lib/components/price-chart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
'use client';

import { useId, useMemo } from 'react';
import {
Area,
AreaChart,
CartesianGrid,
ResponsiveContainer,
Tooltip,
XAxis,
YAxis,
} from 'recharts';
import type { PriceChartPoint } from '../hooks/use-waitlist-price-chart';
import { formatEthDecimal } from '@haqq/shell-shared';

export interface PriceChartProps {
data: PriceChartPoint[];
/** Chart height in pixels */
height?: number;
/** Whether price values are in atto (wei) - will format for display */
priceInAtto?: boolean;
isLoading?: boolean;
error?: Error | null;
}

const DEFAULT_HEIGHT = 240;
const ONE_HOUR = 3600;

/**
* Expands chart data by adding a point every hour from the last point up to now.
* So a single API point becomes a line from that time to current time (same price).
*/
function expandDataWithHourlyPoints(
data: Array<{ timestamp: number; price: number }>,
): Array<{ timestamp: number; price: number }> {
if (!data.length) return [];
const now = Math.floor(Date.now() / 1000);
const last = data[data.length - 1]!;
if (last.timestamp >= now) return data;
const result: PriceChartPoint[] = [...data];
for (let t = last.timestamp + ONE_HOUR; t <= now; t += ONE_HOUR) {
result.push({ timestamp: t, price: last.price });
}
if (result[result.length - 1]!.timestamp < now) {
result.push({ timestamp: now, price: last.price });
}
return result;
}

function formatChartTime(ts: number): string {
const d = new Date(ts * 1000);
const now = new Date();
const isToday =
d.getDate() === now.getDate() &&
d.getMonth() === now.getMonth() &&
d.getFullYear() === now.getFullYear();
if (isToday) {
return d.toLocaleTimeString(undefined, {
hour: '2-digit',
minute: '2-digit',
});
}
return d.toLocaleDateString(undefined, {
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
});
}

/**
* Price chart built with Recharts (SVG-based). Web3-style dark card.
* @see https://recharts.org/
*/
export function PriceChart({
data,
height = DEFAULT_HEIGHT,
priceInAtto = true,
isLoading = false,
error = null,
}: PriceChartProps) {
const uid = useId();
const expandedData = useMemo(() => expandDataWithHourlyPoints(data), [data]);

const chartData = useMemo(
() =>
expandedData.map((d) => ({
...d,
timeLabel: formatChartTime(d.timestamp),
})),
[expandedData],
);

const formatPrice = useMemo(
() => (p: number) =>
priceInAtto && p > 0 ? formatEthDecimal(BigInt(p), 2, 0) : p.toFixed(2),
[priceInAtto],
);

const currentPriceFormatted = useMemo(() => {
if (!chartData.length) return '0';
return formatPrice(chartData[chartData.length - 1]!.price);
}, [chartData, formatPrice]);

const isSinglePoint = chartData.length === 1;

const gradientId = `price-chart-gradient-${uid.replace(/:/g, '')}`;

if (error) {
return (
<div
className="border-haqq-border bg-haqq-black/80 rounded-xl border p-6 text-center"
style={{ minHeight: height }}
>
<div className="text-haqq-modal-border text-sm">
Failed to load price chart
</div>
</div>
);
}

if (isLoading) {
return (
<div
className="border-haqq-border bg-haqq-black/50 animate-pulse rounded-xl border"
style={{ height, width: '100%' }}
/>
);
}

if (!data.length) {
return (
<div
className="border-haqq-border bg-haqq-black/80 rounded-xl border p-6 text-center"
style={{ minHeight: height }}
>
<div className="text-haqq-modal-border text-sm">No chart data yet</div>
</div>
);
}

return (
<div className="border-haqq-border bg-haqq-black/95 w-full overflow-hidden rounded-xl border shadow-lg">
<div className="border-haqq-border flex flex-wrap items-center justify-between gap-2 border-b px-4 py-3">
<span className="text-haqq-modal-border text-xs font-medium tracking-wider uppercase">
Price history
</span>
<div className="flex items-baseline gap-2">
{isSinglePoint && (
<span className="bg-haqq-seaweed/20 text-haqq-seaweed rounded px-2 py-0.5 text-[10px] font-medium">
Holding
</span>
)}
<span className="text-haqq-azure font-mono text-sm font-semibold tabular-nums">
{currentPriceFormatted}
</span>
<span className="text-haqq-modal-border text-[10px]">ISLM/HAQQ</span>
</div>
</div>

<div
className="px-2 py-3"
style={{ width: '100%', height, minHeight: 200 }}
>
<ResponsiveContainer width="100%" height="100%">
<AreaChart
data={chartData}
margin={{ top: 16, right: 24, bottom: 28, left: 16 }}
>
<defs>
<linearGradient id={gradientId} x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stopColor="#157C83" stopOpacity={0.3} />
<stop offset="60%" stopColor="#157C83" stopOpacity={0.08} />
<stop offset="100%" stopColor="#157C83" stopOpacity={0} />
</linearGradient>
</defs>
<CartesianGrid
strokeDasharray="0"
stroke="#FFFFFF1A"
vertical={false}
/>
<XAxis
dataKey="timestamp"
type="number"
domain={['dataMin', 'dataMax']}
tickFormatter={(ts: number) => formatChartTime(ts)}
stroke="#C5C5C5"
tick={{ fill: '#C5C5C5', fontSize: 9 }}
axisLine={false}
tickLine={false}
/>
<YAxis
dataKey="price"
type="number"
domain={['auto', 'auto']}
tickFormatter={(p: number) => formatPrice(p)}
stroke="#C5C5C5"
tick={{ fill: '#C5C5C5', fontSize: 10 }}
axisLine={false}
tickLine={false}
width={56}
tickMargin={12}
/>
<Tooltip
content={({
active,
payload,
}: {
active?: boolean;
payload?: ReadonlyArray<{
payload: {
timestamp: number;
price: number;
timeLabel: string;
};
}>;
}) => {
if (!active || !payload?.length) return null;
const point = payload[0]?.payload as {
timestamp: number;
price: number;
timeLabel: string;
};
if (!point) return null;
return (
<div className="border-haqq-border bg-haqq-black rounded-lg border px-3 py-2 shadow-xl">
<div className="text-haqq-modal-border text-[10px]">
{point.timeLabel}
</div>
<div className="text-haqq-azure font-mono text-sm font-semibold tabular-nums">
{formatPrice(point.price)} ISLM
</div>
</div>
);
}}
cursor={{ stroke: '#C5C5C5', strokeWidth: 1 }}
/>
<Area
type="monotone"
dataKey="price"
stroke="#157C83"
strokeWidth={2.5}
fill={`url(#${gradientId})`}
isAnimationActive={false}
/>
</AreaChart>
</ResponsiveContainer>
</div>
</div>
);
}
19 changes: 19 additions & 0 deletions libs/burn-waitlist/src/lib/components/requests-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Button } from '@haqq/shell-ui-kit';
import { FundsSource } from '../constants/waitlist-config';
import type { Application } from '../hooks/use-waitlist-applications';
import type { WaitlistBalancesResponse } from '../hooks/use-waitlist-balances';
import { formatEthDecimal } from '@haqq/shell-shared';

export interface RequestsListProps {
applications: Array<
Expand Down Expand Up @@ -100,6 +101,24 @@ export function RequestsList({
{amount} ISLM
</span>
</div>
{app.price !== undefined && app.price !== '' && (
<div>
Minting price:{' '}
<span className="font-[500] text-[#0D0D0E]">
{formatEthDecimal(BigInt(app.price), 0, 0)} ISLM/HAQQ
</span>
</div>
)}
{app.receiveAmount !== undefined &&
app.receiveAmount !== '' && (
<div>
Expected receive:{' '}
<span className="font-[500] text-[#0D0D0E]">
{formatEthDecimal(BigInt(app.receiveAmount), 4, 18)}{' '}
HAQQ
</span>
</div>
)}
<div>
Source:{' '}
<span className="font-medium text-[#0D0D0E]">
Expand Down
Loading
Loading