mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-30 02:23:47 +00:00
feat(cabinet): migrate all icons to the panel's Phosphor set
Replace the cabinet's hand-written heroicons-style SVG icon components with the Remnawave panel's own icon family — Phosphor via react-icons/pi (Duotone). All icons now live in a single central barrel (src/components/icons: index + extended-icons + editor-icons, 144 icons); every feature file imports from it instead of redefining inline SVGs (~440 icon defs removed). - Add react-icons dependency - Desktop nav (AppShell) now uses the central Phosphor icons, removing a stroke-vs-duotone inconsistency the partial migration introduced - Icons with custom props (SortIcon's direction, expandable chevrons) kept as thin Phosphor wrappers; the RemnawaveIcon brand logo and animated SVGs (checkmarks, spinners) are intentionally left as-is - Restore the original per-call-site icon sizes wherever the centralized default differed (211 call sites)
This commit is contained in:
@@ -3,7 +3,7 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { promoApi, PromoOffer } from '../api/promo';
|
||||
import { ClockIcon, CheckIcon } from './icons';
|
||||
import { ClockIcon, CheckIcon, XCircleIcon } from './icons';
|
||||
import { useDestructiveConfirm } from '@/platform/hooks/useNativeDialog';
|
||||
|
||||
// Helper functions
|
||||
@@ -69,17 +69,6 @@ const getOfferDescription = (
|
||||
return t('promo.offers.activateDiscountHint');
|
||||
};
|
||||
|
||||
// Icons for deactivation
|
||||
const XCircleIcon = () => (
|
||||
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M9.75 9.75l4.5 4.5m0-4.5l-4.5 4.5M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
interface PromoOffersSectionProps {
|
||||
className?: string;
|
||||
}
|
||||
@@ -237,7 +226,7 @@ export default function PromoOffersSection({ className = '' }: PromoOffersSectio
|
||||
onClick={handleDeactivateClick}
|
||||
className="flex items-center justify-center gap-1.5 rounded-xl border border-dark-600/50 bg-dark-900/50 px-4 py-2.5 text-sm text-dark-400 transition-colors hover:border-error-500/30 hover:bg-error-500/10 hover:text-error-400"
|
||||
>
|
||||
<XCircleIcon />
|
||||
<XCircleIcon className="h-4 w-4" />
|
||||
<span>{t('promo.deactivate.button')}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -12,69 +12,14 @@ import { useCurrency } from '../hooks/useCurrency';
|
||||
import { useTelegramSDK } from '../hooks/useTelegramSDK';
|
||||
import { useFocusTrap } from '../hooks/useFocusTrap';
|
||||
import { useHaptic } from '@/platform';
|
||||
|
||||
// Icons
|
||||
const CheckCircleIcon = () => (
|
||||
<svg
|
||||
className="h-16 w-16"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
const WalletIcon = () => (
|
||||
<svg className="h-8 w-8" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M21 12a2.25 2.25 0 00-2.25-2.25H15a3 3 0 11-6 0H5.25A2.25 2.25 0 003 12m18 0v6a2.25 2.25 0 01-2.25 2.25H5.25A2.25 2.25 0 013 18v-6m18 0V9M3 12V9m18 0a2.25 2.25 0 00-2.25-2.25H5.25A2.25 2.25 0 003 9m18 0V6a2.25 2.25 0 00-2.25-2.25H5.25A2.25 2.25 0 003 6v3"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
const RocketIcon = () => (
|
||||
<svg className="h-8 w-8" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M15.59 14.37a6 6 0 01-5.84 7.38v-4.8m5.84-2.58a14.98 14.98 0 006.16-12.12A14.98 14.98 0 009.631 8.41m5.96 5.96a14.926 14.926 0 01-5.841 2.58m-.119-8.54a6 6 0 00-7.381 5.84h4.8m2.581-5.84a14.927 14.927 0 00-2.58 5.84m2.699 2.7c-.103.021-.207.041-.311.06a15.09 15.09 0 01-2.448-2.448 14.9 14.9 0 01.06-.312m-2.24 2.39a4.493 4.493 0 00-1.757 4.306 4.493 4.493 0 004.306-1.758M16.5 9a1.5 1.5 0 11-3 0 1.5 1.5 0 013 0z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
const DevicesIcon = () => (
|
||||
<svg className="h-8 w-8" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M10.5 1.5H8.25A2.25 2.25 0 006 3.75v16.5a2.25 2.25 0 002.25 2.25h7.5A2.25 2.25 0 0018 20.25V3.75a2.25 2.25 0 00-2.25-2.25H13.5m-3 0V3h3V1.5m-3 0h3m-3 18.75h3"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
const TrafficIcon = () => (
|
||||
<svg className="h-8 w-8" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5m-13.5-9L12 3m0 0l4.5 4.5M12 3v13.5"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
const CloseIcon = () => (
|
||||
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
);
|
||||
import {
|
||||
CheckCircleIcon,
|
||||
CloseIcon,
|
||||
DevicesIcon,
|
||||
RocketIcon,
|
||||
TrafficIcon,
|
||||
WalletIcon,
|
||||
} from '@/components/icons';
|
||||
|
||||
export default function SuccessNotificationModal() {
|
||||
const { t } = useTranslation();
|
||||
@@ -161,33 +106,33 @@ export default function SuccessNotificationModal() {
|
||||
// Determine title and message
|
||||
let title = data.title;
|
||||
const message = data.message;
|
||||
let icon = <CheckCircleIcon />;
|
||||
let icon = <CheckCircleIcon className="h-16 w-16" />;
|
||||
let gradientClass = 'from-success-500 to-success-600';
|
||||
|
||||
if (!title) {
|
||||
if (isBalanceTopup) {
|
||||
title = t('successNotification.balanceTopup.title', 'Balance topped up!');
|
||||
icon = <WalletIcon />;
|
||||
icon = <WalletIcon className="h-8 w-8" />;
|
||||
gradientClass = 'from-success-500 to-success-600';
|
||||
} else if (data.type === 'subscription_activated') {
|
||||
title = t('successNotification.subscriptionActivated.title', 'Subscription activated!');
|
||||
icon = <RocketIcon />;
|
||||
icon = <RocketIcon className="h-8 w-8" />;
|
||||
gradientClass = 'from-accent-500 to-purple-600';
|
||||
} else if (data.type === 'subscription_renewed') {
|
||||
title = t('successNotification.subscriptionRenewed.title', 'Subscription renewed!');
|
||||
icon = <RocketIcon />;
|
||||
icon = <RocketIcon className="h-8 w-8" />;
|
||||
gradientClass = 'from-accent-500 to-purple-600';
|
||||
} else if (data.type === 'subscription_purchased') {
|
||||
title = t('successNotification.subscriptionPurchased.title', 'Subscription purchased!');
|
||||
icon = <RocketIcon />;
|
||||
icon = <RocketIcon className="h-8 w-8" />;
|
||||
gradientClass = 'from-accent-500 to-purple-600';
|
||||
} else if (data.type === 'devices_purchased') {
|
||||
title = t('successNotification.devicesPurchased.title', 'Devices added!');
|
||||
icon = <DevicesIcon />;
|
||||
icon = <DevicesIcon className="h-8 w-8" />;
|
||||
gradientClass = 'from-blue-500 to-cyan-600';
|
||||
} else if (data.type === 'traffic_purchased') {
|
||||
title = t('successNotification.trafficPurchased.title', 'Traffic added!');
|
||||
icon = <TrafficIcon />;
|
||||
icon = <TrafficIcon className="h-8 w-8" />;
|
||||
gradientClass = 'from-success-500 to-success-600';
|
||||
}
|
||||
}
|
||||
@@ -334,7 +279,7 @@ export default function SuccessNotificationModal() {
|
||||
onClick={handleGoToSubscription}
|
||||
className="flex w-full items-center justify-center gap-2 rounded-xl bg-accent-500 py-3.5 font-bold text-white shadow-lg shadow-accent-500/25 transition-colors hover:bg-accent-400 active:bg-accent-600"
|
||||
>
|
||||
<RocketIcon />
|
||||
<RocketIcon className="h-8 w-8" />
|
||||
<span>{t('successNotification.goToSubscription', 'Go to Subscription')}</span>
|
||||
</button>
|
||||
)}
|
||||
@@ -344,7 +289,7 @@ export default function SuccessNotificationModal() {
|
||||
onClick={handleGoToBalance}
|
||||
className="flex w-full items-center justify-center gap-2 rounded-xl bg-success-500 py-3.5 font-bold text-white shadow-lg shadow-success-500/25 transition-colors hover:bg-success-400 active:bg-success-600"
|
||||
>
|
||||
<WalletIcon />
|
||||
<WalletIcon className="h-8 w-8" />
|
||||
<span>{t('successNotification.goToBalance', 'Go to Balance')}</span>
|
||||
</button>
|
||||
)}
|
||||
@@ -354,7 +299,7 @@ export default function SuccessNotificationModal() {
|
||||
onClick={handleGoToSubscription}
|
||||
className="flex w-full items-center justify-center gap-2 rounded-xl bg-accent-500 py-3.5 font-bold text-white shadow-lg shadow-accent-500/25 transition-colors hover:bg-accent-400 active:bg-accent-600"
|
||||
>
|
||||
<DevicesIcon />
|
||||
<DevicesIcon className="h-8 w-8" />
|
||||
<span>{t('successNotification.goToSubscription', 'Go to Subscription')}</span>
|
||||
</button>
|
||||
)}
|
||||
@@ -364,7 +309,7 @@ export default function SuccessNotificationModal() {
|
||||
onClick={handleGoToSubscription}
|
||||
className="flex w-full items-center justify-center gap-2 rounded-xl bg-success-500 py-3.5 font-bold text-white shadow-lg shadow-success-500/25 transition-colors hover:bg-success-400 active:bg-success-600"
|
||||
>
|
||||
<TrafficIcon />
|
||||
<TrafficIcon className="h-8 w-8" />
|
||||
<span>{t('successNotification.goToSubscription', 'Go to Subscription')}</span>
|
||||
</button>
|
||||
)}
|
||||
|
||||
@@ -8,22 +8,7 @@ import { useToast } from './Toast';
|
||||
import { useWebSocket, WSMessage } from '../hooks/useWebSocket';
|
||||
import { useHeaderHeight } from '../hooks/useHeaderHeight';
|
||||
import type { TicketNotification } from '../types';
|
||||
|
||||
const BellIcon = () => (
|
||||
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M14.857 17.082a23.848 23.848 0 005.454-1.31A8.967 8.967 0 0118 9.75v-.7V9A6 6 0 006 9v.75a8.967 8.967 0 01-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 01-5.714 0m5.714 0a3 3 0 11-5.714 0"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
const CheckIcon = () => (
|
||||
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
);
|
||||
import { BellIcon, CheckIcon } from '@/components/icons';
|
||||
|
||||
interface TicketNotificationBellProps {
|
||||
isAdmin?: boolean;
|
||||
|
||||
@@ -18,6 +18,15 @@ import {
|
||||
verticalListSortingStrategy,
|
||||
} from '@dnd-kit/sortable';
|
||||
import { CSS } from '@dnd-kit/utilities';
|
||||
import { PiCaretDownDuotone } from 'react-icons/pi';
|
||||
import {
|
||||
GripIcon,
|
||||
TrashIcon,
|
||||
PlusIcon,
|
||||
LinkIcon,
|
||||
ArrowUpIcon,
|
||||
ArrowDownIcon,
|
||||
} from '@/components/icons';
|
||||
import {
|
||||
menuLayoutApi,
|
||||
type MenuConfig,
|
||||
@@ -31,82 +40,10 @@ import { Toggle } from './Toggle';
|
||||
import { useNotify } from '../../platform/hooks/useNotify';
|
||||
import { useNativeDialog } from '../../platform/hooks/useNativeDialog';
|
||||
|
||||
const GripIcon = () => (
|
||||
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
const TrashIcon = () => (
|
||||
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
const PlusIcon = () => (
|
||||
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
const ChevronIcon = ({ expanded }: { expanded: boolean }) => (
|
||||
<svg
|
||||
<PiCaretDownDuotone
|
||||
className={`h-3.5 w-3.5 transition-transform ${expanded ? 'rotate-180' : ''}`}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
const LinkIcon = () => (
|
||||
<svg
|
||||
className="h-3.5 w-3.5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M13.19 8.688a4.5 4.5 0 011.242 7.244l-4.5 4.5a4.5 4.5 0 01-6.364-6.364l1.757-1.757m9.86-2.54a4.5 4.5 0 00-1.242-7.244l-4.5-4.5a4.5 4.5 0 00-6.364 6.364L4.03 8.591"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
const ArrowUpIcon = () => (
|
||||
<svg
|
||||
className="h-3.5 w-3.5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M4.5 15.75l7.5-7.5 7.5 7.5" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
const ArrowDownIcon = () => (
|
||||
<svg
|
||||
className="h-3.5 w-3.5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5" />
|
||||
</svg>
|
||||
/>
|
||||
);
|
||||
|
||||
function generateId(): string {
|
||||
@@ -200,7 +137,7 @@ function ButtonChip({
|
||||
: 'cursor-default text-dark-700'
|
||||
}`}
|
||||
>
|
||||
<ArrowUpIcon />
|
||||
<ArrowUpIcon className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
<button
|
||||
onClick={onMoveDown ?? undefined}
|
||||
@@ -212,7 +149,7 @@ function ButtonChip({
|
||||
: 'cursor-default text-dark-700'
|
||||
}`}
|
||||
>
|
||||
<ArrowDownIcon />
|
||||
<ArrowDownIcon className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
<span className={`h-2.5 w-2.5 shrink-0 rounded-full ${colorDotClass}`} />
|
||||
@@ -221,7 +158,7 @@ function ButtonChip({
|
||||
</span>
|
||||
{!isBuiltin && (
|
||||
<span className="text-dark-500" title="URL">
|
||||
<LinkIcon />
|
||||
<LinkIcon className="h-3.5 w-3.5" />
|
||||
</span>
|
||||
)}
|
||||
<Toggle checked={button.enabled} onChange={() => onUpdate({ enabled: !button.enabled })} />
|
||||
@@ -236,7 +173,7 @@ function ButtonChip({
|
||||
onClick={onRemove}
|
||||
className="rounded-lg p-1 text-dark-500 transition-colors hover:bg-error-500/10 hover:text-error-400"
|
||||
>
|
||||
<TrashIcon />
|
||||
<TrashIcon className="h-4 w-4" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
@@ -425,7 +362,7 @@ function SortableRow({
|
||||
onClick={() => onRemoveRow(row.id)}
|
||||
className="rounded-lg p-1.5 text-dark-500 transition-colors hover:bg-error-500/10 hover:text-error-400"
|
||||
>
|
||||
<TrashIcon />
|
||||
<TrashIcon className="h-4 w-4" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
@@ -481,7 +418,7 @@ function InlineAddPanel({ rowId, usedBuiltinIds, onAddBuiltin, onAddCustom }: In
|
||||
onClick={() => setIsOpen(true)}
|
||||
className="flex w-full items-center justify-center gap-2 rounded-xl border-2 border-dashed border-dark-700/50 py-2.5 text-sm text-dark-500 transition-colors hover:border-dark-600 hover:text-dark-400"
|
||||
>
|
||||
<PlusIcon />
|
||||
<PlusIcon className="h-4 w-4" />
|
||||
{t('admin.menuEditor.addButton')}
|
||||
</button>
|
||||
);
|
||||
@@ -516,7 +453,7 @@ function InlineAddPanel({ rowId, usedBuiltinIds, onAddBuiltin, onAddCustom }: In
|
||||
}}
|
||||
className="flex w-full items-center gap-2 rounded-lg px-3 py-2 text-left text-sm text-dark-200 transition-colors hover:bg-dark-700/50"
|
||||
>
|
||||
<LinkIcon />
|
||||
<LinkIcon className="h-3.5 w-3.5" />
|
||||
{t('admin.menuEditor.addUrlButton')}
|
||||
</button>
|
||||
<button
|
||||
@@ -843,7 +780,7 @@ export function MenuEditorTab() {
|
||||
onClick={addRow}
|
||||
className="flex w-full items-center justify-center gap-2 rounded-2xl border-2 border-dashed border-dark-700/50 py-4 text-sm font-medium text-dark-500 transition-colors hover:border-dark-600 hover:text-dark-400"
|
||||
>
|
||||
<PlusIcon />
|
||||
<PlusIcon className="h-4 w-4" />
|
||||
{t('admin.menuEditor.addRow')}
|
||||
</button>
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSortable } from '@dnd-kit/sortable';
|
||||
import { PiCaretDownDuotone } from 'react-icons/pi';
|
||||
import { CSS } from '@dnd-kit/utilities';
|
||||
import { cn } from '../../lib/utils';
|
||||
import { GripIcon, TrashIcon } from '../icons/LandingIcons';
|
||||
@@ -10,15 +11,7 @@ import type { PaymentMethodSubOptionInfo } from '../../types';
|
||||
export type MethodWithId = AdminLandingPaymentMethod & { _id: string };
|
||||
|
||||
const ChevronDownIcon = ({ open }: { open: boolean }) => (
|
||||
<svg
|
||||
className={cn('h-5 w-5 transition-transform', open && 'rotate-180')}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5" />
|
||||
</svg>
|
||||
<PiCaretDownDuotone className={cn('h-5 w-5 transition-transform', open && 'rotate-180')} />
|
||||
);
|
||||
|
||||
interface SortableSelectedMethodProps {
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { CheckIcon, XCloseIcon } from '@/components/icons';
|
||||
import { useFocusTrap } from '@/hooks/useFocusTrap';
|
||||
import { DropdownSelect } from './DropdownSelect';
|
||||
import type { UserListItem } from '../../../api/adminUsers';
|
||||
@@ -48,24 +49,6 @@ export interface ModalState {
|
||||
progress: ProgressState | null;
|
||||
}
|
||||
|
||||
const CheckIcon = () => (
|
||||
<svg
|
||||
className="h-3 w-3 text-white"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={3}
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M4.5 12.75l6 6 9-13.5" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
const XCloseIcon = () => (
|
||||
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
function ProgressView({ progress }: { progress: ProgressState }) {
|
||||
const { t } = useTranslation();
|
||||
const logEndRef = useRef<HTMLDivElement>(null);
|
||||
@@ -520,7 +503,7 @@ export function ActionModal({
|
||||
)}
|
||||
aria-pressed={forceDeleteActivePaid}
|
||||
>
|
||||
{forceDeleteActivePaid && <CheckIcon />}
|
||||
{forceDeleteActivePaid && <CheckIcon className="h-3 w-3" />}
|
||||
</button>
|
||||
<span
|
||||
className={cn(
|
||||
@@ -558,7 +541,7 @@ export function ActionModal({
|
||||
)}
|
||||
aria-pressed={deleteFromPanel}
|
||||
>
|
||||
{deleteFromPanel && <CheckIcon />}
|
||||
{deleteFromPanel && <CheckIcon className="h-3 w-3" />}
|
||||
</button>
|
||||
<span
|
||||
className={cn(
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ChevronDownIcon } from '@/components/icons';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────
|
||||
@@ -7,11 +8,10 @@ import { cn } from '@/lib/utils';
|
||||
// chrome (border, focus ring, chevron).
|
||||
// ──────────────────────────────────────────────────────────────────
|
||||
|
||||
export const ChevronDownIcon = () => (
|
||||
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5" />
|
||||
</svg>
|
||||
);
|
||||
// Re-exported so the sibling FloatingActionBar / MultiSelectDropdown keep
|
||||
// importing the chevron from this module while the glyph itself now comes
|
||||
// from the central Phosphor barrel instead of a hand-written SVG.
|
||||
export { ChevronDownIcon };
|
||||
|
||||
export interface DropdownOption {
|
||||
value: string;
|
||||
@@ -40,7 +40,7 @@ export function DropdownSelect({ value, options, onChange, className }: Dropdown
|
||||
))}
|
||||
</select>
|
||||
<div className="pointer-events-none absolute right-2.5 top-1/2 -translate-y-1/2 text-dark-500">
|
||||
<ChevronDownIcon />
|
||||
<ChevronDownIcon className="h-4 w-4" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,164 +1,35 @@
|
||||
// ──────────────────────────────────────────────────────────────────
|
||||
// TrafficIcons — the inline SVG set used across AdminTrafficUsage
|
||||
// (header controls, filter buttons, sort indicator, etc.). Page-local
|
||||
// in spirit; co-located here so the parent page imports a single
|
||||
// flat barrel rather than redefining 15 inline icons.
|
||||
// TrafficIcons — the icon set used across AdminTrafficUsage (header
|
||||
// controls, filter buttons, sort indicator, etc.). The glyphs now come
|
||||
// from the central Phosphor barrel (`@/components/icons`); only the
|
||||
// sort indicator keeps its custom `direction` prop and is therefore a
|
||||
// thin local wrapper over the panel's Phosphor caret icons.
|
||||
// ──────────────────────────────────────────────────────────────────
|
||||
|
||||
export const SearchIcon = () => (
|
||||
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
import { PiCaretDownDuotone, PiCaretUpDownDuotone, PiCaretUpDuotone } from 'react-icons/pi';
|
||||
|
||||
export const ChevronLeftIcon = () => (
|
||||
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M15.75 19.5L8.25 12l7.5-7.5" />
|
||||
</svg>
|
||||
);
|
||||
export {
|
||||
CalendarIcon,
|
||||
ChevronDownIcon,
|
||||
ChevronLeftIcon,
|
||||
ChevronRightIcon,
|
||||
DownloadIcon,
|
||||
FilterIcon,
|
||||
GlobeIcon,
|
||||
RefreshIcon,
|
||||
SearchIcon,
|
||||
ServerIcon,
|
||||
ServerSmallIcon,
|
||||
ShieldIcon,
|
||||
StatusIcon,
|
||||
XIcon,
|
||||
} from '@/components/icons';
|
||||
|
||||
export const ChevronRightIcon = () => (
|
||||
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const RefreshIcon = ({ className = 'w-5 h-5' }: { className?: string }) => (
|
||||
<svg className={className} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const DownloadIcon = () => (
|
||||
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5M16.5 12L12 16.5m0 0L7.5 12m4.5 4.5V3"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const SortIcon = ({ direction }: { direction: false | 'asc' | 'desc' }) => (
|
||||
<svg
|
||||
className="ml-1 inline h-3 w-3"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
>
|
||||
{direction === 'asc' ? (
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M4.5 15.75l7.5-7.5 7.5 7.5" />
|
||||
) : direction === 'desc' ? (
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5" />
|
||||
) : (
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M8.25 15L12 18.75 15.75 15m-7.5-6L12 5.25 15.75 9"
|
||||
/>
|
||||
)}
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const FilterIcon = () => (
|
||||
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M12 3c2.755 0 5.455.232 8.083.678.533.09.917.556.917 1.096v1.044a2.25 2.25 0 01-.659 1.591l-5.432 5.432a2.25 2.25 0 00-.659 1.591v2.927a2.25 2.25 0 01-1.244 2.013L9.75 21v-6.568a2.25 2.25 0 00-.659-1.591L3.659 7.409A2.25 2.25 0 013 5.818V4.774c0-.54.384-1.006.917-1.096A48.32 48.32 0 0112 3z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const ChevronDownIcon = () => (
|
||||
<svg className="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const ServerIcon = () => (
|
||||
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M21.75 17.25v-.228a4.5 4.5 0 00-.12-1.03l-2.268-9.64a3.375 3.375 0 00-3.285-2.602H7.923a3.375 3.375 0 00-3.285 2.602l-2.268 9.64a4.5 4.5 0 00-.12 1.03v.228m19.5 0a3 3 0 01-3 3H5.25a3 3 0 01-3-3m19.5 0a3 3 0 00-3-3H5.25a3 3 0 00-3 3m16.5 0h.008v.008h-.008v-.008zm-3 0h.008v.008h-.008v-.008z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const CalendarIcon = () => (
|
||||
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 012.25-2.25h13.5A2.25 2.25 0 0121 7.5v11.25m-18 0A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75m-18 0v-7.5A2.25 2.25 0 015.25 9h13.5A2.25 2.25 0 0121 11.25v7.5"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const XIcon = () => (
|
||||
<svg className="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const StatusIcon = () => (
|
||||
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const GlobeIcon = () => (
|
||||
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M12 21a9.004 9.004 0 008.716-6.747M12 21a9.004 9.004 0 01-8.716-6.747M12 21c2.485 0 4.5-4.03 4.5-9S14.485 3 12 3m0 18c-2.485 0-4.5-4.03-4.5-9S9.515 3 12 3m0 0a8.997 8.997 0 017.843 4.582M12 3a8.997 8.997 0 00-7.843 4.582m15.686 0A11.953 11.953 0 0112 10.5c-2.998 0-5.74-1.1-7.843-2.918m15.686 0A8.959 8.959 0 0121 12c0 .778-.099 1.533-.284 2.253m0 0A17.919 17.919 0 0112 16.5c-3.162 0-6.133-.815-8.716-2.247m0 0A9.015 9.015 0 013 12c0-1.605.42-3.113 1.157-4.418"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const ShieldIcon = () => (
|
||||
<svg
|
||||
className="h-3.5 w-3.5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M9 12.75L11.25 15 15 9.75m-3-7.036A11.959 11.959 0 013.598 6 11.99 11.99 0 003 9.749c0 5.592 3.824 10.29 9 11.623 5.176-1.332 9-6.03 9-11.622 0-1.31-.21-2.571-.598-3.751h-.152c-3.196 0-6.1-1.248-8.25-3.285z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const ServerSmallIcon = () => (
|
||||
<svg
|
||||
className="h-3.5 w-3.5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M5.25 14.25h13.5m-13.5 0a3 3 0 01-3-3m3 3a3 3 0 100 6h13.5a3 3 0 100-6m-16.5-3a3 3 0 013-3h13.5a3 3 0 013 3m-19.5 0a4.5 4.5 0 01.9-2.7L5.737 5.1a3.375 3.375 0 012.7-1.35h7.126c1.062 0 2.062.5 2.7 1.35l2.587 3.45a4.5 4.5 0 01.9 2.7m0 0a3 3 0 01-3 3m0 3h.008v.008h-.008v-.008zm0-6h.008v.008h-.008v-.008zm-3 6h.008v.008h-.008v-.008zm0-6h.008v.008h-.008v-.008z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
export const SortIcon = ({ direction }: { direction: false | 'asc' | 'desc' }) =>
|
||||
direction === 'asc' ? (
|
||||
<PiCaretUpDuotone className="ml-1 inline h-3 w-3" />
|
||||
) : direction === 'desc' ? (
|
||||
<PiCaretDownDuotone className="ml-1 inline h-3 w-3" />
|
||||
) : (
|
||||
<PiCaretUpDownDuotone className="ml-1 inline h-3 w-3" />
|
||||
);
|
||||
|
||||
@@ -49,13 +49,13 @@ export function CountryFilter({
|
||||
: 'border-dark-700 bg-dark-800 text-dark-200 hover:border-dark-600 hover:bg-dark-700'
|
||||
}`}
|
||||
>
|
||||
<GlobeIcon />
|
||||
<GlobeIcon className="h-4 w-4" />
|
||||
{activeCount > 0 && (
|
||||
<span className="rounded-full bg-accent-500 px-1.5 text-[10px] text-white">
|
||||
{activeCount}
|
||||
</span>
|
||||
)}
|
||||
<ChevronDownIcon />
|
||||
<ChevronDownIcon className="h-3 w-3" />
|
||||
</button>
|
||||
|
||||
{open && (
|
||||
|
||||
@@ -52,14 +52,14 @@ export function NodeFilter({
|
||||
: 'border-dark-700 bg-dark-800 text-dark-200 hover:border-dark-600 hover:bg-dark-700'
|
||||
}`}
|
||||
>
|
||||
<ServerIcon />
|
||||
<ServerIcon className="h-4 w-4" />
|
||||
{t('admin.trafficUsage.nodes')}
|
||||
{activeCount > 0 && (
|
||||
<span className="rounded-full bg-accent-500 px-1.5 text-[10px] text-white">
|
||||
{activeCount}
|
||||
</span>
|
||||
)}
|
||||
<ChevronDownIcon />
|
||||
<ChevronDownIcon className="h-3 w-3" />
|
||||
</button>
|
||||
|
||||
{open && (
|
||||
|
||||
@@ -42,7 +42,7 @@ export function PeriodSelector({
|
||||
if (dateMode) {
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<CalendarIcon />
|
||||
<CalendarIcon className="h-4 w-4" />
|
||||
<span className="text-xs text-dark-400">{t('admin.trafficUsage.dateFrom')}</span>
|
||||
<input
|
||||
type="date"
|
||||
@@ -66,7 +66,7 @@ export function PeriodSelector({
|
||||
className="rounded-lg p-1 text-dark-400 transition-colors hover:bg-dark-700 hover:text-dark-200"
|
||||
title={t('admin.trafficUsage.period')}
|
||||
>
|
||||
<XIcon />
|
||||
<XIcon className="h-3 w-3" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
@@ -96,7 +96,7 @@ export function PeriodSelector({
|
||||
className="rounded-lg border border-dark-700 bg-dark-800 p-1.5 text-dark-400 transition-colors hover:border-dark-600 hover:bg-dark-700 hover:text-dark-200"
|
||||
title={t('admin.trafficUsage.customDates')}
|
||||
>
|
||||
<CalendarIcon />
|
||||
<CalendarIcon className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -63,14 +63,14 @@ export function StatusFilter({
|
||||
: 'border-dark-700 bg-dark-800 text-dark-200 hover:border-dark-600 hover:bg-dark-700'
|
||||
}`}
|
||||
>
|
||||
<StatusIcon />
|
||||
<StatusIcon className="h-4 w-4" />
|
||||
{t('admin.trafficUsage.status')}
|
||||
{activeCount > 0 && (
|
||||
<span className="rounded-full bg-accent-500 px-1.5 text-[10px] text-white">
|
||||
{activeCount}
|
||||
</span>
|
||||
)}
|
||||
<ChevronDownIcon />
|
||||
<ChevronDownIcon className="h-3 w-3" />
|
||||
</button>
|
||||
|
||||
{open && (
|
||||
|
||||
@@ -50,14 +50,14 @@ export function TariffFilter({
|
||||
: 'border-dark-700 bg-dark-800 text-dark-200 hover:border-dark-600 hover:bg-dark-700'
|
||||
}`}
|
||||
>
|
||||
<FilterIcon />
|
||||
<FilterIcon className="h-4 w-4" />
|
||||
{t('admin.trafficUsage.tariff')}
|
||||
{activeCount > 0 && (
|
||||
<span className="rounded-full bg-accent-500 px-1.5 text-[10px] text-white">
|
||||
{activeCount}
|
||||
</span>
|
||||
)}
|
||||
<ChevronDownIcon />
|
||||
<ChevronDownIcon className="h-3 w-3" />
|
||||
</button>
|
||||
|
||||
{open && (
|
||||
|
||||
@@ -6,22 +6,7 @@ import { adminUsersApi, type UserDetailResponse } from '../../../api/adminUsers'
|
||||
import { promocodesApi } from '../../../api/promocodes';
|
||||
import { promoOffersApi } from '../../../api/promoOffers';
|
||||
import { createNumberInputHandler, toNumber } from '../../../utils/inputHelpers';
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────
|
||||
// Icons — local; balance is the only consumer.
|
||||
// ──────────────────────────────────────────────────────────────────
|
||||
|
||||
const PlusIcon = () => (
|
||||
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
const MinusIcon = () => (
|
||||
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M19.5 12h-15" />
|
||||
</svg>
|
||||
);
|
||||
import { PlusIcon, MinusIcon } from '@/components/icons';
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────
|
||||
// Balance tab — current balance, add/subtract form, active promo
|
||||
@@ -168,14 +153,14 @@ export function BalanceTab({
|
||||
disabled={actionLoading || balanceAmount === ''}
|
||||
className="flex flex-1 items-center justify-center gap-2 rounded-lg bg-success-500 py-2 text-white transition-colors hover:bg-success-600 disabled:opacity-50"
|
||||
>
|
||||
<PlusIcon /> {t('admin.users.detail.balance.add')}
|
||||
<PlusIcon className="h-4 w-4" /> {t('admin.users.detail.balance.add')}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleUpdateBalance(false)}
|
||||
disabled={actionLoading || balanceAmount === ''}
|
||||
className="flex flex-1 items-center justify-center gap-2 rounded-lg bg-error-500 py-2 text-white transition-colors hover:bg-error-600 disabled:opacity-50"
|
||||
>
|
||||
<MinusIcon /> {t('admin.users.detail.balance.subtract')}
|
||||
<MinusIcon className="h-4 w-4" /> {t('admin.users.detail.balance.subtract')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { MinusIcon, PlusIcon, RefreshIcon } from '@/components/icons';
|
||||
import { DEVICE_ALIAS_MAX_LENGTH } from '../../../constants/devices';
|
||||
import { createNumberInputHandler } from '../../../utils/inputHelpers';
|
||||
import { getFlagEmoji } from '../../../utils/subscriptionHelpers';
|
||||
@@ -37,28 +38,6 @@ function StatusBadge({ status }: { status: string }) {
|
||||
);
|
||||
}
|
||||
|
||||
const PlusIcon = () => (
|
||||
<svg className="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
const MinusIcon = () => (
|
||||
<svg className="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M19.5 12h-15" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
const RefreshIcon = ({ className = 'w-5 h-5' }: { className?: string }) => (
|
||||
<svg className={className} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
// Local device row type (matches the parent's inline type)
|
||||
type DeviceRow = {
|
||||
hwid: string;
|
||||
@@ -390,7 +369,7 @@ export function SubscriptionTab(props: SubscriptionTabProps) {
|
||||
disabled={actionLoading || selectedSub.device_limit <= 1}
|
||||
className="flex h-6 w-6 items-center justify-center rounded-md bg-dark-700 text-dark-300 transition-colors hover:bg-dark-600 disabled:opacity-30"
|
||||
>
|
||||
<MinusIcon />
|
||||
<MinusIcon className="h-3 w-3" />
|
||||
</button>
|
||||
<span className="min-w-[2ch] text-center text-dark-100">
|
||||
{selectedSub.device_limit}
|
||||
@@ -404,7 +383,7 @@ export function SubscriptionTab(props: SubscriptionTabProps) {
|
||||
}
|
||||
className="flex h-6 w-6 items-center justify-center rounded-md bg-dark-700 text-dark-300 transition-colors hover:bg-dark-600 disabled:opacity-30"
|
||||
>
|
||||
<PlusIcon />
|
||||
<PlusIcon className="h-3 w-3" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,26 +1,11 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ArrowDownIcon, ArrowUpIcon } from '@/components/icons';
|
||||
import type {
|
||||
UserDetailResponse,
|
||||
UserSubscriptionInfo,
|
||||
PanelSyncStatusResponse,
|
||||
} from '../../../api/adminUsers';
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────
|
||||
// Icons (sync-tab-local — not used outside this view)
|
||||
// ──────────────────────────────────────────────────────────────────
|
||||
|
||||
const ArrowDownIcon = ({ className = 'w-5 h-5' }: { className?: string }) => (
|
||||
<svg className={className} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M19.5 13.5L12 21m0 0l-7.5-7.5M12 21V3" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
const ArrowUpIcon = ({ className = 'w-5 h-5' }: { className?: string }) => (
|
||||
<svg className={className} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M4.5 10.5L12 3m0 0l7.5 7.5M12 3v18" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────
|
||||
// Sync tab — compares bot DB vs panel data, offers a 2-way push
|
||||
// ──────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -12,6 +12,7 @@ import { useTheme } from '@/hooks/useTheme';
|
||||
import { CardsBlock, TimelineBlock, AccordionBlock, MinimalBlock, BlockButtons } from './blocks';
|
||||
import type { BlockRendererProps } from './blocks';
|
||||
import TvQuickConnect from './TvQuickConnect';
|
||||
import { BackIcon } from '@/components/icons';
|
||||
|
||||
const platformOrder = ['ios', 'android', 'windows', 'macos', 'linux', 'androidTV', 'appleTV'];
|
||||
|
||||
@@ -33,12 +34,6 @@ const RENDERERS: Record<string, React.ComponentType<BlockRendererProps>> = {
|
||||
minimal: MinimalBlock,
|
||||
};
|
||||
|
||||
const BackIcon = () => (
|
||||
<svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M15 19l-7-7 7-7" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
interface Props {
|
||||
appConfig: AppConfig;
|
||||
onOpenDeepLink: (url: string) => void;
|
||||
@@ -200,7 +195,7 @@ export default function InstallationGuide({
|
||||
aria-label={t('common.back', 'Back')}
|
||||
className="flex h-10 w-10 items-center justify-center rounded-xl border border-dark-700 bg-dark-800 transition-colors hover:border-dark-600"
|
||||
>
|
||||
<BackIcon />
|
||||
<BackIcon className="h-6 w-6" />
|
||||
</button>
|
||||
)}
|
||||
<h2 className="flex-1 text-lg font-bold text-dark-100">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useState, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { CheckIcon, CopyIcon } from '@/components/icons';
|
||||
import type { RemnawaveButtonClient, LocalizedText } from '@/types';
|
||||
import { copyToClipboard } from '@/utils/clipboard';
|
||||
|
||||
@@ -20,22 +21,6 @@ function isValidExternalUrl(url: string | undefined): boolean {
|
||||
return lowerUrl.startsWith('http://') || lowerUrl.startsWith('https://');
|
||||
}
|
||||
|
||||
const CopyIcon = () => (
|
||||
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
const CheckIcon = () => (
|
||||
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
interface BlockButtonsProps {
|
||||
buttons: RemnawaveButtonClient[] | undefined;
|
||||
variant: 'light' | 'subtle';
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { PiCaretRightDuotone } from 'react-icons/pi';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Link } from 'react-router';
|
||||
import { useCurrency } from '../../hooks/useCurrency';
|
||||
@@ -12,22 +13,7 @@ interface StatsGridProps {
|
||||
}
|
||||
|
||||
const ChevronIcon = ({ color }: { color: string }) => (
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
style={{ flexShrink: 0 }}
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
d="M6 4l4 4-4 4"
|
||||
stroke={color}
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
<PiCaretRightDuotone width={16} height={16} style={{ flexShrink: 0, color }} aria-hidden="true" />
|
||||
);
|
||||
|
||||
export default function StatsGrid({
|
||||
|
||||
@@ -10,6 +10,7 @@ import { useTrafficZone } from '../../hooks/useTrafficZone';
|
||||
import { formatTraffic } from '../../utils/formatTraffic';
|
||||
import { getGlassColors } from '../../utils/glassTheme';
|
||||
import { HoverBorderGradient } from '../ui/hover-border-gradient';
|
||||
import { RefreshIcon } from '@/components/icons';
|
||||
import { useHaptic } from '../../platform';
|
||||
import type { Subscription } from '../../types';
|
||||
|
||||
@@ -25,23 +26,6 @@ interface SubscriptionCardActiveProps {
|
||||
connectedDevices: number;
|
||||
}
|
||||
|
||||
const RefreshIcon = ({ className = 'w-4 h-4' }: { className?: string }) => (
|
||||
<svg
|
||||
className={className}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default function SubscriptionCardActive({
|
||||
subscription,
|
||||
trafficData,
|
||||
|
||||
85
src/components/icons/editor-icons.tsx
Normal file
85
src/components/icons/editor-icons.tsx
Normal file
@@ -0,0 +1,85 @@
|
||||
import {
|
||||
PiTextBDuotone,
|
||||
PiTextItalicDuotone,
|
||||
PiTextUnderlineDuotone,
|
||||
PiTextStrikethroughDuotone,
|
||||
PiTextHOneDuotone,
|
||||
PiTextHTwoDuotone,
|
||||
PiTextHThreeDuotone,
|
||||
PiListBulletsDuotone,
|
||||
PiListNumbersDuotone,
|
||||
PiQuotesDuotone,
|
||||
PiCodeBlockDuotone,
|
||||
PiTextAlignLeftDuotone,
|
||||
PiTextAlignCenterDuotone,
|
||||
PiHighlighterDuotone,
|
||||
} from 'react-icons/pi';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
interface IconProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rich-text editor toolbar icons — Phosphor (react-icons/pi) Duotone, the
|
||||
* panel's icon family. Shared by the TipTap toolbars in AdminNewsCreate and
|
||||
* AdminInfoPageEditor. Names match the historical local definitions so the
|
||||
* toolbars import instead of hand-rolling SVGs.
|
||||
*/
|
||||
|
||||
export const BoldIcon = ({ className }: IconProps) => (
|
||||
<PiTextBDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const ItalicIcon = ({ className }: IconProps) => (
|
||||
<PiTextItalicDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const UnderlineIcon = ({ className }: IconProps) => (
|
||||
<PiTextUnderlineDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const StrikeIcon = ({ className }: IconProps) => (
|
||||
<PiTextStrikethroughDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const H1Icon = ({ className }: IconProps) => (
|
||||
<PiTextHOneDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const H2Icon = ({ className }: IconProps) => (
|
||||
<PiTextHTwoDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const H3Icon = ({ className }: IconProps) => (
|
||||
<PiTextHThreeDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const ListBulletIcon = ({ className }: IconProps) => (
|
||||
<PiListBulletsDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const ListOrderedIcon = ({ className }: IconProps) => (
|
||||
<PiListNumbersDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const QuoteIcon = ({ className }: IconProps) => (
|
||||
<PiQuotesDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const CodeBlockIcon = ({ className }: IconProps) => (
|
||||
<PiCodeBlockDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const AlignLeftIcon = ({ className }: IconProps) => (
|
||||
<PiTextAlignLeftDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const AlignCenterIcon = ({ className }: IconProps) => (
|
||||
<PiTextAlignCenterDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const HighlightIcon = ({ className }: IconProps) => (
|
||||
<PiHighlighterDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
415
src/components/icons/extended-icons.tsx
Normal file
415
src/components/icons/extended-icons.tsx
Normal file
@@ -0,0 +1,415 @@
|
||||
import {
|
||||
PiSlidersHorizontalDuotone,
|
||||
PiHeadsetDuotone,
|
||||
PiArrowDownDuotone,
|
||||
PiArrowRightDuotone,
|
||||
PiArrowUpDuotone,
|
||||
PiProhibitDuotone,
|
||||
PiMoneyDuotone,
|
||||
PiBellDuotone,
|
||||
PiLightningDuotone,
|
||||
PiRobotDuotone,
|
||||
PiBroadcastDuotone,
|
||||
PiAppWindowDuotone,
|
||||
PiCalendarDotsDuotone,
|
||||
PiCreditCardDuotone,
|
||||
PiChartBarDuotone,
|
||||
PiCheckCircleDuotone,
|
||||
PiCaretUpDownDuotone,
|
||||
PiCaretDownDuotone,
|
||||
PiCaretLeftDuotone,
|
||||
PiCaretUpDuotone,
|
||||
PiCurrencyBtcDuotone,
|
||||
PiDevicesDuotone,
|
||||
PiFileTextDuotone,
|
||||
PiCircleFill,
|
||||
PiEnvelopeDuotone,
|
||||
PiWarningDuotone,
|
||||
PiArrowSquareOutDuotone,
|
||||
PiEyeDuotone,
|
||||
PiFunnelDuotone,
|
||||
PiDotsSixDuotone,
|
||||
PiDotsSixVerticalDuotone,
|
||||
PiHeartbeatDuotone,
|
||||
PiClockCounterClockwiseDuotone,
|
||||
PiImageDuotone,
|
||||
PiInfinityDuotone,
|
||||
PiLinkDuotone,
|
||||
PiMegaphoneDuotone,
|
||||
PiMinusDuotone,
|
||||
PiNewspaperDuotone,
|
||||
PiHandshakeDuotone,
|
||||
PiPushPinDuotone,
|
||||
PiPlusDuotone,
|
||||
PiPowerDuotone,
|
||||
PiQuestionDuotone,
|
||||
PiRepeatDuotone,
|
||||
PiFlagDuotone,
|
||||
PiArrowCounterClockwiseDuotone,
|
||||
PiArrowClockwiseDuotone,
|
||||
PiRocketDuotone,
|
||||
PiFloppyDiskDuotone,
|
||||
PiPaperPlaneTiltDuotone,
|
||||
PiPercentDuotone,
|
||||
PiHardDrivesDuotone,
|
||||
PiGearSixDuotone,
|
||||
PiShareNetworkDuotone,
|
||||
PiSparkleDuotone,
|
||||
PiChartLineDuotone,
|
||||
PiGiftDuotone,
|
||||
PiTimerDuotone,
|
||||
PiCircleDuotone,
|
||||
PiTagDuotone,
|
||||
PiTelegramLogoDuotone,
|
||||
PiTicketDuotone,
|
||||
PiGaugeDuotone,
|
||||
PiTrashDuotone,
|
||||
PiTrophyDuotone,
|
||||
PiPushPinSlashDuotone,
|
||||
PiUserPlusDuotone,
|
||||
PiUsersThreeDuotone,
|
||||
PiVideoCameraDuotone,
|
||||
PiXCircleDuotone,
|
||||
PiXDuotone,
|
||||
} from 'react-icons/pi';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
interface IconProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extended cabinet icon set — Phosphor (react-icons/pi) Duotone, the panel's
|
||||
* icon family. These cover the icons that used to be hand-written inline across
|
||||
* feature pages and components. Names match the historical local definitions so
|
||||
* every page can import from the central barrel instead of redefining SVGs.
|
||||
*/
|
||||
|
||||
export const AdjustmentsIcon = ({ className }: IconProps) => (
|
||||
<PiSlidersHorizontalDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const AgentIcon = ({ className }: IconProps) => (
|
||||
<PiHeadsetDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const ArrowDownIcon = ({ className }: IconProps) => (
|
||||
<PiArrowDownDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const ArrowIcon = ({ className }: IconProps) => (
|
||||
<PiArrowRightDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const ArrowUpIcon = ({ className }: IconProps) => (
|
||||
<PiArrowUpDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const BanIcon = ({ className }: IconProps) => (
|
||||
<PiProhibitDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const BanknotesIcon = ({ className }: IconProps) => (
|
||||
<PiMoneyDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const BellIcon = ({ className }: IconProps) => (
|
||||
<PiBellDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const BoltIcon = ({ className }: IconProps) => (
|
||||
<PiLightningDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const BotIcon = ({ className }: IconProps) => (
|
||||
<PiRobotDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const BroadcastIcon = ({ className }: IconProps) => (
|
||||
<PiBroadcastDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const CabinetIcon = ({ className }: IconProps) => (
|
||||
<PiAppWindowDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const CalendarIcon = ({ className }: IconProps) => (
|
||||
<PiCalendarDotsDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const CardIcon = ({ className }: IconProps) => (
|
||||
<PiCreditCardDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const ChannelIcon = ({ className }: IconProps) => (
|
||||
<PiBroadcastDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const ChartBarIcon = ({ className }: IconProps) => (
|
||||
<PiChartBarDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const CheckCircleIcon = ({ className }: IconProps) => (
|
||||
<PiCheckCircleDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const ChevronExpandIcon = ({ className }: IconProps) => (
|
||||
<PiCaretUpDownDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const ChevronIcon = ({ className }: IconProps) => (
|
||||
<PiCaretDownDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const ChevronLeftIcon = ({ className }: IconProps) => (
|
||||
<PiCaretLeftDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const ChevronUpIcon = ({ className }: IconProps) => (
|
||||
<PiCaretUpDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const CryptoIcon = ({ className }: IconProps) => (
|
||||
<PiCurrencyBtcDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const DevicesIcon = ({ className }: IconProps) => (
|
||||
<PiDevicesDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const DocumentIcon = ({ className }: IconProps) => (
|
||||
<PiFileTextDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const DotIcon = ({ className }: IconProps) => (
|
||||
<PiCircleFill className={cn('h-2 w-2', className)} />
|
||||
);
|
||||
|
||||
export const EmailIcon = ({ className }: IconProps) => (
|
||||
<PiEnvelopeDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const ExclamationIcon = ({ className }: IconProps) => (
|
||||
<PiWarningDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const ExternalLinkIcon = ({ className }: IconProps) => (
|
||||
<PiArrowSquareOutDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const EyeIcon = ({ className }: IconProps) => (
|
||||
<PiEyeDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const FileTextIcon = ({ className }: IconProps) => (
|
||||
<PiFileTextDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const FilterIcon = ({ className }: IconProps) => (
|
||||
<PiFunnelDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const GripIcon = ({ className }: IconProps) => (
|
||||
<PiDotsSixDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const GripVerticalIcon = ({ className }: IconProps) => (
|
||||
<PiDotsSixVerticalDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const HealthIcon = ({ className }: IconProps) => (
|
||||
<PiHeartbeatDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const HistoryIcon = ({ className }: IconProps) => (
|
||||
<PiClockCounterClockwiseDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const ImageIcon = ({ className }: IconProps) => (
|
||||
<PiImageDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const InfinityIcon = ({ className }: IconProps) => (
|
||||
<PiInfinityDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const LinkIcon = ({ className }: IconProps) => (
|
||||
<PiLinkDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const MailIcon = ({ className }: IconProps) => (
|
||||
<PiEnvelopeDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const MegaphoneIcon = ({ className }: IconProps) => (
|
||||
<PiMegaphoneDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const MinusIcon = ({ className }: IconProps) => (
|
||||
<PiMinusDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const NewsIcon = ({ className }: IconProps) => (
|
||||
<PiNewspaperDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const PartnerIcon = ({ className }: IconProps) => (
|
||||
<PiHandshakeDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const PhotoIcon = ({ className }: IconProps) => (
|
||||
<PiImageDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const PercentIcon = ({ className }: IconProps) => (
|
||||
<PiPercentDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const PinIcon = ({ className }: IconProps) => (
|
||||
<PiPushPinDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const PlusSmallIcon = ({ className }: IconProps) => (
|
||||
<PiPlusDuotone className={cn('h-4 w-4', className)} />
|
||||
);
|
||||
|
||||
export const PowerIcon = ({ className }: IconProps) => (
|
||||
<PiPowerDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const QuestionIcon = ({ className }: IconProps) => (
|
||||
<PiQuestionDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const RepeatIcon = ({ className }: IconProps) => (
|
||||
<PiRepeatDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const ReportIcon = ({ className }: IconProps) => (
|
||||
<PiFlagDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const ResetIcon = ({ className }: IconProps) => (
|
||||
<PiArrowCounterClockwiseDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const RestartIcon = ({ className }: IconProps) => (
|
||||
<PiArrowClockwiseDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const RocketIcon = ({ className }: IconProps) => (
|
||||
<PiRocketDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const SaveIcon = ({ className }: IconProps) => (
|
||||
<PiFloppyDiskDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const SendIcon = ({ className }: IconProps) => (
|
||||
<PiPaperPlaneTiltDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const ServerSmallIcon = ({ className }: IconProps) => (
|
||||
<PiHardDrivesDuotone className={cn('h-4 w-4', className)} />
|
||||
);
|
||||
|
||||
export const SettingsIcon = ({ className }: IconProps) => (
|
||||
<PiGearSixDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const ShareIcon = ({ className }: IconProps) => (
|
||||
<PiShareNetworkDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const SparklesIcon = ({ className }: IconProps) => (
|
||||
<PiSparkleDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const StatBotIcon = ({ className }: IconProps) => (
|
||||
<PiRobotDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const StatCabinetIcon = ({ className }: IconProps) => (
|
||||
<PiAppWindowDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const StatPaidIcon = ({ className }: IconProps) => (
|
||||
<PiMoneyDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const StatsChartIcon = ({ className }: IconProps) => (
|
||||
<PiChartLineDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const StatTrialIcon = ({ className }: IconProps) => (
|
||||
<PiGiftDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const StatUptimeIcon = ({ className }: IconProps) => (
|
||||
<PiTimerDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const StatusIcon = ({ className }: IconProps) => (
|
||||
<PiCircleDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const TagIcon = ({ className }: IconProps) => (
|
||||
<PiTagDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const TelegramIcon = ({ className }: IconProps) => (
|
||||
<PiTelegramLogoDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const TelegramSmallIcon = ({ className }: IconProps) => (
|
||||
<PiTelegramLogoDuotone className={cn('h-4 w-4', className)} />
|
||||
);
|
||||
|
||||
export const TicketIcon = ({ className }: IconProps) => (
|
||||
<PiTicketDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const TrafficIcon = ({ className }: IconProps) => (
|
||||
<PiGaugeDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const TrashSmallIcon = ({ className }: IconProps) => (
|
||||
<PiTrashDuotone className={cn('h-4 w-4', className)} />
|
||||
);
|
||||
|
||||
export const TrophyIcon = ({ className }: IconProps) => (
|
||||
<PiTrophyDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const UnpinIcon = ({ className }: IconProps) => (
|
||||
<PiPushPinSlashDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const UserPlusIcon = ({ className }: IconProps) => (
|
||||
<PiUserPlusDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const UsersOnlineIcon = ({ className }: IconProps) => (
|
||||
<PiUsersThreeDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const VideoIcon = ({ className }: IconProps) => (
|
||||
<PiVideoCameraDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const WarningIcon = ({ className }: IconProps) => (
|
||||
<PiWarningDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const XCircleIcon = ({ className }: IconProps) => (
|
||||
<PiXCircleDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const XCloseIcon = ({ className }: IconProps) => (
|
||||
<PiXDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const XMarkIcon = ({ className }: IconProps) => (
|
||||
<PiXDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
@@ -1,611 +1,248 @@
|
||||
import type { CSSProperties } from 'react';
|
||||
import {
|
||||
PiArrowLeftDuotone,
|
||||
PiArrowRightDuotone,
|
||||
PiArrowsClockwiseDuotone,
|
||||
PiCaretDownDuotone,
|
||||
PiCaretRightDuotone,
|
||||
PiChartBarDuotone,
|
||||
PiChatCircleDuotone,
|
||||
PiCheckDuotone,
|
||||
PiClipboardTextDuotone,
|
||||
PiClockDuotone,
|
||||
PiCopyDuotone,
|
||||
PiCreditCardDuotone,
|
||||
PiDownloadSimpleDuotone,
|
||||
PiGameControllerDuotone,
|
||||
PiGearSixDuotone,
|
||||
PiGiftDuotone,
|
||||
PiGlobeDuotone,
|
||||
PiHardDrivesDuotone,
|
||||
PiHouseDuotone,
|
||||
PiInfoDuotone,
|
||||
PiListDuotone,
|
||||
PiLockDuotone,
|
||||
PiMagnifyingGlassDuotone,
|
||||
PiMegaphoneDuotone,
|
||||
PiMoonDuotone,
|
||||
PiPaletteDuotone,
|
||||
PiPauseCircleDuotone,
|
||||
PiPencilDuotone,
|
||||
PiPencilSimpleDuotone,
|
||||
PiPlayDuotone,
|
||||
PiPlusDuotone,
|
||||
PiShieldDuotone,
|
||||
PiSignOutDuotone,
|
||||
PiSparkleDuotone,
|
||||
PiStarDuotone,
|
||||
PiStarFill,
|
||||
PiSteeringWheelDuotone,
|
||||
PiStopDuotone,
|
||||
PiSunDuotone,
|
||||
PiTrashDuotone,
|
||||
PiUploadSimpleDuotone,
|
||||
PiUserDuotone,
|
||||
PiUsersDuotone,
|
||||
PiWalletDuotone,
|
||||
PiXDuotone,
|
||||
} from 'react-icons/pi';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
// Re-export the extended Phosphor icon sets so the whole cabinet imports the
|
||||
// panel's icon family from a single barrel.
|
||||
export * from './extended-icons';
|
||||
export * from './editor-icons';
|
||||
|
||||
interface IconProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cabinet icons are thin wrappers over the Remnawave panel's own icon library
|
||||
* (Phosphor, via `react-icons/pi`, Duotone variants). Each export keeps the
|
||||
* historical name + default Tailwind sizing so every consumer keeps working,
|
||||
* while the cabinet now renders the exact icon set the panel uses instead of
|
||||
* hand-written SVGs.
|
||||
*
|
||||
* react-icons components inherit `currentColor` and accept `className`, so
|
||||
* Tailwind size (`h-5 w-5`) and text-color utilities control them as before.
|
||||
*/
|
||||
|
||||
// Navigation & Layout
|
||||
export const HomeIcon = ({ className }: IconProps) => (
|
||||
<svg
|
||||
className={cn('h-5 w-5', className)}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M2.25 12l8.954-8.955c.44-.439 1.152-.439 1.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25"
|
||||
/>
|
||||
</svg>
|
||||
<PiHouseDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const BackIcon = ({ className }: IconProps) => (
|
||||
<svg
|
||||
className={cn('h-5 w-5', className)}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M15.75 19.5L8.25 12l7.5-7.5" />
|
||||
</svg>
|
||||
<PiArrowLeftDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const ChevronRightIcon = ({ className }: IconProps) => (
|
||||
<svg
|
||||
className={cn('h-5 w-5', className)}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" />
|
||||
</svg>
|
||||
<PiCaretRightDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const MenuIcon = ({ className }: IconProps) => (
|
||||
<svg
|
||||
className={cn('h-5 w-5', className)}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5"
|
||||
/>
|
||||
</svg>
|
||||
<PiListDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const CloseIcon = ({ className }: IconProps) => (
|
||||
<svg
|
||||
className={cn('h-5 w-5', className)}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
<PiXDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const ChevronDownIcon = ({ className }: IconProps) => (
|
||||
<svg
|
||||
className={cn('h-5 w-5', className)}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5" />
|
||||
</svg>
|
||||
<PiCaretDownDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const ArrowRightIcon = ({ className }: IconProps) => (
|
||||
<svg
|
||||
className={cn('h-5 w-5', className)}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M13.5 4.5L21 12m0 0l-7.5 7.5M21 12H3" />
|
||||
</svg>
|
||||
<PiArrowRightDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
// Actions
|
||||
export const SearchIcon = ({ className }: IconProps) => (
|
||||
<svg
|
||||
className={cn('h-5 w-5', className)}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z"
|
||||
/>
|
||||
</svg>
|
||||
<PiMagnifyingGlassDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const PlusIcon = ({ className }: IconProps) => (
|
||||
<svg
|
||||
className={cn('h-5 w-5', className)}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
|
||||
</svg>
|
||||
<PiPlusDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const EditIcon = ({ className }: IconProps) => (
|
||||
<svg
|
||||
className={cn('h-4 w-4', className)}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125"
|
||||
/>
|
||||
</svg>
|
||||
<PiPencilSimpleDuotone className={cn('h-4 w-4', className)} />
|
||||
);
|
||||
|
||||
export const PencilIcon = ({ className }: IconProps) => (
|
||||
<svg
|
||||
className={cn('h-4 w-4', className)}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0115.75 21H5.25A2.25 2.25 0 013 18.75V8.25A2.25 2.25 0 015.25 6H10"
|
||||
/>
|
||||
</svg>
|
||||
<PiPencilDuotone className={cn('h-4 w-4', className)} />
|
||||
);
|
||||
|
||||
export const TrashIcon = ({ className }: IconProps) => (
|
||||
<svg
|
||||
className={cn('h-5 w-5', className)}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"
|
||||
/>
|
||||
</svg>
|
||||
<PiTrashDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const UploadIcon = ({ className }: IconProps) => (
|
||||
<svg
|
||||
className={cn('h-5 w-5', className)}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5m-13.5-9L12 3m0 0l4.5 4.5M12 3v13.5"
|
||||
/>
|
||||
</svg>
|
||||
<PiUploadSimpleDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const DownloadIcon = ({ className }: IconProps) => (
|
||||
<svg
|
||||
className={cn('h-5 w-5', className)}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5M16.5 12L12 16.5m0 0L7.5 12m4.5 4.5V3"
|
||||
/>
|
||||
</svg>
|
||||
<PiDownloadSimpleDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const RefreshIcon = ({
|
||||
className,
|
||||
spinning = false,
|
||||
}: IconProps & { spinning?: boolean }) => (
|
||||
<svg
|
||||
className={cn('h-4 w-4', spinning && 'animate-spin', className)}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99"
|
||||
/>
|
||||
</svg>
|
||||
<PiArrowsClockwiseDuotone className={cn('h-4 w-4', spinning && 'animate-spin', className)} />
|
||||
);
|
||||
|
||||
export const SyncIcon = ({ className }: IconProps) => (
|
||||
<svg
|
||||
className={cn('h-5 w-5', className)}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99"
|
||||
/>
|
||||
</svg>
|
||||
<PiArrowsClockwiseDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
// Status
|
||||
export const CheckIcon = ({ className }: IconProps) => (
|
||||
<svg
|
||||
className={cn('h-4 w-4', className)}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M4.5 12.75l6 6 9-13.5" />
|
||||
</svg>
|
||||
<PiCheckDuotone className={cn('h-4 w-4', className)} />
|
||||
);
|
||||
|
||||
export const CopyIcon = ({ className }: IconProps) => (
|
||||
<svg
|
||||
className={cn('h-4 w-4', className)}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M15.666 3.888A2.25 2.25 0 0013.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 01-.75.75H9a.75.75 0 01-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 01-2.25 2.25H6.75A2.25 2.25 0 014.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 011.927-.184"
|
||||
/>
|
||||
</svg>
|
||||
<PiCopyDuotone className={cn('h-4 w-4', className)} />
|
||||
);
|
||||
|
||||
export const XIcon = ({ className }: IconProps) => (
|
||||
<svg
|
||||
className={cn('h-4 w-4', className)}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
<PiXDuotone className={cn('h-4 w-4', className)} />
|
||||
);
|
||||
|
||||
export const LockIcon = ({ className }: IconProps) => (
|
||||
<svg
|
||||
className={cn('h-4 w-4', className)}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M16.5 10.5V6.75a4.5 4.5 0 10-9 0v3.75m-.75 11.25h10.5a2.25 2.25 0 002.25-2.25v-6.75a2.25 2.25 0 00-2.25-2.25H6.75a2.25 2.25 0 00-2.25 2.25v6.75a2.25 2.25 0 002.25 2.25z"
|
||||
/>
|
||||
</svg>
|
||||
<PiLockDuotone className={cn('h-4 w-4', className)} />
|
||||
);
|
||||
|
||||
export const InfoIcon = ({ className }: IconProps) => (
|
||||
<svg
|
||||
className={cn('h-5 w-5', className)}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M11.25 11.25l.041-.02a.75.75 0 011.063.852l-.708 2.836a.75.75 0 001.063.853l.041-.021M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9-3.75h.008v.008H12V8.25z"
|
||||
/>
|
||||
</svg>
|
||||
<PiInfoDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
// User & People
|
||||
export const UserIcon = ({ className }: IconProps) => (
|
||||
<svg
|
||||
className={cn('h-5 w-5', className)}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M15.75 6a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0zM4.501 20.118a7.5 7.5 0 0114.998 0A17.933 17.933 0 0112 21.75c-2.676 0-5.216-.584-7.499-1.632z"
|
||||
/>
|
||||
</svg>
|
||||
<PiUserDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const UsersIcon = ({ className }: IconProps) => (
|
||||
<svg
|
||||
className={cn('h-5 w-5', className)}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M15 19.128a9.38 9.38 0 002.625.372 9.337 9.337 0 004.121-.952 4.125 4.125 0 00-7.533-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 018.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0111.964-3.07M12 6.375a3.375 3.375 0 11-6.75 0 3.375 3.375 0 016.75 0zm8.25 2.25a2.625 2.625 0 11-5.25 0 2.625 2.625 0 015.25 0z"
|
||||
/>
|
||||
</svg>
|
||||
<PiUsersDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const LogoutIcon = ({ className }: IconProps) => (
|
||||
<svg
|
||||
className={cn('h-5 w-5', className)}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M15.75 9V5.25A2.25 2.25 0 0013.5 3h-6a2.25 2.25 0 00-2.25 2.25v13.5A2.25 2.25 0 007.5 21h6a2.25 2.25 0 002.25-2.25V15m3 0l3-3m0 0l-3-3m3 3H9"
|
||||
/>
|
||||
</svg>
|
||||
<PiSignOutDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
// Theme
|
||||
export const SunIcon = ({ className }: IconProps) => (
|
||||
<svg
|
||||
className={cn('h-5 w-5', className)}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M12 3v2.25m6.364.386l-1.591 1.591M21 12h-2.25m-.386 6.364l-1.591-1.591M12 18.75V21m-4.773-4.227l-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0z"
|
||||
/>
|
||||
</svg>
|
||||
<PiSunDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const MoonIcon = ({ className }: IconProps) => (
|
||||
<svg
|
||||
className={cn('h-5 w-5', className)}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M21.752 15.002A9.718 9.718 0 0118 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 003 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 009.002-5.998z"
|
||||
/>
|
||||
</svg>
|
||||
<PiMoonDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const PaletteIcon = ({ className }: IconProps) => (
|
||||
<svg
|
||||
className={cn('h-5 w-5', className)}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M4.098 19.902a3.75 3.75 0 005.304 0l6.401-6.402M6.75 21A3.75 3.75 0 013 17.25V4.125C3 3.504 3.504 3 4.125 3h5.25c.621 0 1.125.504 1.125 1.125v4.072M6.75 21a3.75 3.75 0 003.75-3.75V8.197M6.75 21h13.125c.621 0 1.125-.504 1.125-1.125v-5.25c0-.621-.504-1.125-1.125-1.125h-4.072M10.5 8.197l2.88-2.88c.438-.439 1.15-.439 1.59 0l3.712 3.713c.44.44.44 1.152 0 1.59l-2.879 2.88M6.75 17.25h.008v.008H6.75v-.008z"
|
||||
/>
|
||||
</svg>
|
||||
<PiPaletteDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
// Features & Content
|
||||
export const SubscriptionIcon = ({ className }: IconProps) => (
|
||||
<svg
|
||||
className={cn('h-5 w-5', className)}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09zM18.259 8.715L18 9.75l-.259-1.035a3.375 3.375 0 00-2.455-2.456L14.25 6l1.036-.259a3.375 3.375 0 002.455-2.456L18 2.25l.259 1.035a3.375 3.375 0 002.456 2.456L21.75 6l-1.035.259a3.375 3.375 0 00-2.456 2.456zM16.894 20.567L16.5 21.75l-.394-1.183a2.25 2.25 0 00-1.423-1.423L13.5 18.75l1.183-.394a2.25 2.25 0 001.423-1.423l.394-1.183.394 1.183a2.25 2.25 0 001.423 1.423l1.183.394-1.183.394a2.25 2.25 0 00-1.423 1.423z"
|
||||
/>
|
||||
</svg>
|
||||
<PiSparkleDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const WalletIcon = ({ className }: IconProps) => (
|
||||
<svg
|
||||
className={cn('h-5 w-5', className)}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M21 12a2.25 2.25 0 00-2.25-2.25H15a3 3 0 11-6 0H5.25A2.25 2.25 0 003 12m18 0v6a2.25 2.25 0 01-2.25 2.25H5.25A2.25 2.25 0 013 18v-6m18 0V9M3 12V9m18 0a2.25 2.25 0 00-2.25-2.25H5.25A2.25 2.25 0 003 9m18 0V6a2.25 2.25 0 00-2.25-2.25H5.25A2.25 2.25 0 003 6v3"
|
||||
/>
|
||||
</svg>
|
||||
<PiWalletDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const ChatIcon = ({ className }: IconProps) => (
|
||||
<svg
|
||||
className={cn('h-5 w-5', className)}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M8.625 12a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0H8.25m4.125 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0H12m4.125 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0h-.375M21 12c0 4.556-4.03 8.25-9 8.25a9.764 9.764 0 01-2.555-.337A5.972 5.972 0 015.41 20.97a5.969 5.969 0 01-.474-.065 4.48 4.48 0 00.978-2.025c.09-.457-.133-.901-.467-1.226C3.93 16.178 3 14.189 3 12c0-4.556 4.03-8.25 9-8.25s9 3.694 9 8.25z"
|
||||
/>
|
||||
</svg>
|
||||
<PiChatCircleDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const GiftIcon = ({ className }: IconProps) => (
|
||||
<svg
|
||||
className={cn('h-4 w-4', className)}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M21 11.25v8.25a1.5 1.5 0 01-1.5 1.5H5.25a1.5 1.5 0 01-1.5-1.5v-8.25M12 4.875A2.625 2.625 0 109.375 7.5H12m0-2.625V7.5m0-2.625A2.625 2.625 0 1114.625 7.5H12m0 0V21m-8.625-9.75h18c.621 0 1.125-.504 1.125-1.125v-1.5c0-.621-.504-1.125-1.125-1.125h-18c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125z"
|
||||
/>
|
||||
</svg>
|
||||
<PiGiftDuotone className={cn('h-4 w-4', className)} />
|
||||
);
|
||||
|
||||
export const ClockIcon = ({ className }: IconProps) => (
|
||||
<svg
|
||||
className={cn('h-5 w-5', className)}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M12 6v6h4.5m4.5 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"
|
||||
/>
|
||||
</svg>
|
||||
<PiClockDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const StarIcon = ({ className, filled }: IconProps & { filled?: boolean }) => (
|
||||
<svg
|
||||
className={cn('h-5 w-5', className)}
|
||||
fill={filled ? 'currentColor' : 'none'}
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M11.48 3.499a.562.562 0 011.04 0l2.125 5.111a.563.563 0 00.475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 00-.182.557l1.285 5.385a.562.562 0 01-.84.61l-4.725-2.885a.563.563 0 00-.586 0L6.982 20.54a.562.562 0 01-.84-.61l1.285-5.386a.562.562 0 00-.182-.557l-4.204-3.602a.563.563 0 01.321-.988l5.518-.442a.563.563 0 00.475-.345L11.48 3.5z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
export const StarIcon = ({ className, filled }: IconProps & { filled?: boolean }) =>
|
||||
filled ? (
|
||||
<PiStarFill className={cn('h-5 w-5', className)} />
|
||||
) : (
|
||||
<PiStarDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const GamepadIcon = ({ className }: IconProps) => (
|
||||
<svg
|
||||
className={cn('h-5 w-5', className)}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M14.25 6.087c0-.355.186-.676.401-.959.221-.29.349-.634.349-1.003 0-1.036-1.007-1.875-2.25-1.875s-2.25.84-2.25 1.875c0 .369.128.713.349 1.003.215.283.401.604.401.959v0a.64.64 0 01-.657.643 48.39 48.39 0 01-4.163-.3c.186 1.613.293 3.25.315 4.907a.656.656 0 01-.658.663v0c-.355 0-.676-.186-.959-.401a1.647 1.647 0 00-1.003-.349c-1.036 0-1.875 1.007-1.875 2.25s.84 2.25 1.875 2.25c.369 0 .713-.128 1.003-.349.283-.215.604-.401.959-.401v0c.31 0 .555.26.532.57a48.039 48.039 0 01-.642 5.056c1.518.19 3.058.309 4.616.354a.64.64 0 00.657-.643v0c0-.355-.186-.676-.401-.959a1.647 1.647 0 01-.349-1.003c0-1.035 1.008-1.875 2.25-1.875 1.243 0 2.25.84 2.25 1.875 0 .369-.128.713-.349 1.003-.215.283-.4.604-.4.959v0c0 .333.277.599.61.58a48.1 48.1 0 005.427-.63 48.05 48.05 0 00.582-4.717.532.532 0 00-.533-.57v0c-.355 0-.676.186-.959.401-.29.221-.634.349-1.003.349-1.035 0-1.875-1.007-1.875-2.25s.84-2.25 1.875-2.25c.37 0 .713.128 1.003.349.283.215.604.401.959.401v0a.656.656 0 00.659-.663 47.703 47.703 0 00-.31-4.82.78.78 0 01.79-.869"
|
||||
/>
|
||||
</svg>
|
||||
<PiGameControllerDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const ClipboardIcon = ({ className }: IconProps) => (
|
||||
<svg
|
||||
className={cn('h-5 w-5', className)}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M9 12h3.75M9 15h3.75M9 18h3.75m3 .75H18a2.25 2.25 0 002.25-2.25V6.108c0-1.135-.845-2.098-1.976-2.192a48.424 48.424 0 00-1.123-.08m-5.801 0c-.065.21-.1.433-.1.664 0 .414.336.75.75.75h4.5a.75.75 0 00.75-.75 2.25 2.25 0 00-.1-.664m-5.8 0A2.251 2.251 0 0113.5 2.25H15c1.012 0 1.867.668 2.15 1.586m-5.8 0c-.376.023-.75.05-1.124.08C9.095 4.01 8.25 4.973 8.25 6.108V8.25m0 0H4.875c-.621 0-1.125.504-1.125 1.125v11.25c0 .621.504 1.125 1.125 1.125h9.75c.621 0 1.125-.504 1.125-1.125V9.375c0-.621-.504-1.125-1.125-1.125H8.25zM6.75 12h.008v.008H6.75V12zm0 3h.008v.008H6.75V15zm0 3h.008v.008H6.75V18z"
|
||||
/>
|
||||
</svg>
|
||||
<PiClipboardTextDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const CogIcon = ({ className }: IconProps) => (
|
||||
<svg
|
||||
className={cn('h-5 w-5', className)}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.324.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 011.37.49l1.296 2.247a1.125 1.125 0 01-.26 1.431l-1.003.827c-.293.24-.438.613-.431.992a6.759 6.759 0 010 .255c-.007.378.138.75.43.99l1.005.828c.424.35.534.954.26 1.43l-1.298 2.247a1.125 1.125 0 01-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.57 6.57 0 01-.22.128c-.331.183-.581.495-.644.869l-.213 1.28c-.09.543-.56.941-1.11.941h-2.594c-.55 0-1.02-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 01-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 01-1.369-.49l-1.297-2.247a1.125 1.125 0 01.26-1.431l1.004-.827c.292-.24.437-.613.43-.992a6.932 6.932 0 010-.255c.007-.378-.138-.75-.43-.99l-1.004-.828a1.125 1.125 0 01-.26-1.43l1.297-2.247a1.125 1.125 0 011.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.087.22-.128.332-.183.582-.495.644-.869l.214-1.281z"
|
||||
/>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
</svg>
|
||||
<PiGearSixDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const WheelIcon = ({ className }: IconProps) => (
|
||||
<svg
|
||||
className={cn('h-5 w-5', className)}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M12 3v2.25m6.364.386l-1.591 1.591M21 12h-2.25m-.386 6.364l-1.591-1.591M12 18.75V21m-4.773-4.227l-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0z"
|
||||
/>
|
||||
</svg>
|
||||
<PiSteeringWheelDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const ShieldIcon = ({ className }: IconProps) => (
|
||||
<PiShieldDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const ServerIcon = ({ className }: IconProps) => (
|
||||
<svg
|
||||
className={cn('h-5 w-5', className)}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M21.75 17.25v-.228a4.5 4.5 0 00-.12-1.03l-2.268-9.64a3.375 3.375 0 00-3.285-2.602H7.923a3.375 3.375 0 00-3.285 2.602l-2.268 9.64a4.5 4.5 0 00-.12 1.03v.228m19.5 0a3 3 0 01-3 3H5.25a3 3 0 01-3-3m19.5 0a3 3 0 00-3-3H5.25a3 3 0 00-3 3m16.5 0h.008v.008h-.008v-.008zm-3 0h.008v.008h-.008v-.008z"
|
||||
/>
|
||||
</svg>
|
||||
<PiHardDrivesDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const CampaignIcon = ({ className }: IconProps) => (
|
||||
<svg
|
||||
className={cn('h-5 w-5', className)}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M10.34 15.84c-.688-.06-1.386-.09-2.09-.09H7.5a4.5 4.5 0 110-9h.75c.704 0 1.402-.03 2.09-.09m0 9.18c.253.962.584 1.892.985 2.783.247.55.06 1.21-.463 1.511l-.657.38c-.551.318-1.26.117-1.527-.461a20.845 20.845 0 01-1.44-4.282m3.102.069a18.03 18.03 0 01-.59-4.59c0-1.586.205-3.124.59-4.59m0 9.18a23.848 23.848 0 018.835 2.535M10.34 6.66a23.847 23.847 0 008.835-2.535m0 0A23.74 23.74 0 0018.795 3m.38 1.125a23.91 23.91 0 011.014 5.395m-1.014 8.855c-.118.38-.245.754-.38 1.125m.38-1.125a23.91 23.91 0 001.014-5.395m0-3.46c.495.413.811 1.035.811 1.73 0 .695-.316 1.317-.811 1.73m0-3.46a24.347 24.347 0 010 3.46"
|
||||
/>
|
||||
</svg>
|
||||
<PiMegaphoneDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
// Brand mark — the genuine Remnawave panel logo (kept as-is, this is the
|
||||
// panel's own SVG, not a hand-drawn substitute).
|
||||
export const RemnawaveIcon = ({ className }: IconProps) => (
|
||||
<svg
|
||||
className={cn('h-5 w-5', className)}
|
||||
@@ -623,114 +260,29 @@ export const RemnawaveIcon = ({ className }: IconProps) => (
|
||||
);
|
||||
|
||||
export const ChartIcon = ({ className }: IconProps) => (
|
||||
<svg
|
||||
className={cn('h-4 w-4', className)}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M3 13.125C3 12.504 3.504 12 4.125 12h2.25c.621 0 1.125.504 1.125 1.125v6.75C7.5 20.496 6.996 21 6.375 21h-2.25A1.125 1.125 0 013 19.875v-6.75zM9.75 8.625c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v11.25c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V8.625zM16.5 4.125c0-.621.504-1.125 1.125-1.125h2.25C20.496 3 21 3.504 21 4.125v15.75c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 01-1.125-1.125V4.125z"
|
||||
/>
|
||||
</svg>
|
||||
<PiChartBarDuotone className={cn('h-4 w-4', className)} />
|
||||
);
|
||||
|
||||
export const GlobeIcon = ({ className }: IconProps) => (
|
||||
<svg
|
||||
className={cn('h-5 w-5', className)}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M12 21a9.004 9.004 0 008.716-6.747M12 21a9.004 9.004 0 01-8.716-6.747M12 21c2.485 0 4.5-4.03 4.5-9S14.485 3 12 3m0 18c-2.485 0-4.5-4.03-4.5-9S9.515 3 12 3m0 0a8.997 8.997 0 017.843 4.582M12 3a8.997 8.997 0 00-7.843 4.582m15.686 0A11.953 11.953 0 0112 10.5c-2.998 0-5.74-1.1-7.843-2.918m15.686 0A8.959 8.959 0 0121 12c0 .778-.099 1.533-.284 2.253m0 0A17.919 17.919 0 0112 16.5c-3.162 0-6.133-.815-8.716-2.247m0 0A9.015 9.015 0 013 12c0-1.605.42-3.113 1.157-4.418"
|
||||
/>
|
||||
</svg>
|
||||
<PiGlobeDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
export const PlayIcon = ({ className }: IconProps) => (
|
||||
<svg
|
||||
className={cn('h-4 w-4', className)}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M5.25 5.653c0-.856.917-1.398 1.667-.986l11.54 6.348a1.125 1.125 0 010 1.971l-11.54 6.347a1.125 1.125 0 01-1.667-.985V5.653z"
|
||||
/>
|
||||
</svg>
|
||||
<PiPlayDuotone className={cn('h-4 w-4', className)} />
|
||||
);
|
||||
|
||||
export const StopIcon = ({ className }: IconProps) => (
|
||||
<svg
|
||||
className={cn('h-4 w-4', className)}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M5.25 7.5A2.25 2.25 0 017.5 5.25h9a2.25 2.25 0 012.25 2.25v9a2.25 2.25 0 01-2.25 2.25h-9a2.25 2.25 0 01-2.25-2.25v-9z"
|
||||
/>
|
||||
</svg>
|
||||
<PiStopDuotone className={cn('h-4 w-4', className)} />
|
||||
);
|
||||
|
||||
export const ArrowPathIcon = ({ className }: IconProps) => (
|
||||
<svg
|
||||
className={cn('h-4 w-4', className)}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99"
|
||||
/>
|
||||
</svg>
|
||||
<PiArrowsClockwiseDuotone className={cn('h-4 w-4', className)} />
|
||||
);
|
||||
|
||||
export const PauseIcon = ({ className, style }: IconProps & { style?: React.CSSProperties }) => (
|
||||
<svg
|
||||
className={cn('h-5 w-5', className)}
|
||||
style={style}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M14.25 9v6m-4.5 0V9M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
export const PauseIcon = ({ className, style }: IconProps & { style?: CSSProperties }) => (
|
||||
<PiPauseCircleDuotone className={cn('h-5 w-5', className)} style={style} />
|
||||
);
|
||||
|
||||
export const CreditCardIcon = ({ className }: IconProps) => (
|
||||
<svg
|
||||
className={cn('h-5 w-5', className)}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M2.25 8.25h19.5M2.25 9h19.5m-16.5 5.25h6m-6 2.25h3m-3.75 3h15a2.25 2.25 0 002.25-2.25V6.75A2.25 2.25 0 0019.5 4.5h-15a2.25 2.25 0 00-2.25 2.25v10.5A2.25 2.25 0 004.5 19.5z"
|
||||
/>
|
||||
</svg>
|
||||
<PiCreditCardDuotone className={cn('h-5 w-5', className)} />
|
||||
);
|
||||
|
||||
@@ -21,173 +21,25 @@ import SuccessNotificationModal from '@/components/SuccessNotificationModal';
|
||||
import { PromptDialogHost } from '@/components/PromptDialogHost';
|
||||
import LanguageSwitcher from '@/components/LanguageSwitcher';
|
||||
import TicketNotificationBell from '@/components/TicketNotificationBell';
|
||||
import { SubscriptionIcon, GiftIcon } from '@/components/icons';
|
||||
import {
|
||||
SubscriptionIcon,
|
||||
GiftIcon,
|
||||
HomeIcon,
|
||||
CreditCardIcon,
|
||||
ChatIcon,
|
||||
UserIcon,
|
||||
UsersIcon,
|
||||
ShieldIcon,
|
||||
InfoIcon,
|
||||
LogoutIcon,
|
||||
SunIcon,
|
||||
MoonIcon,
|
||||
} from '@/components/icons';
|
||||
|
||||
import { MobileBottomNav } from './MobileBottomNav';
|
||||
import { AppHeader } from './AppHeader';
|
||||
import { BackgroundRenderer } from '@/components/backgrounds/BackgroundRenderer';
|
||||
|
||||
// Desktop nav icons
|
||||
const HomeIcon = ({ className }: { className?: string }) => (
|
||||
<svg
|
||||
className={className}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M2.25 12l8.954-8.955c.44-.439 1.152-.439 1.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
const CreditCardIcon = ({ className }: { className?: string }) => (
|
||||
<svg
|
||||
className={className}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M2.25 8.25h19.5M2.25 9h19.5m-16.5 5.25h6m-6 2.25h3m-3.75 3h15a2.25 2.25 0 002.25-2.25V6.75A2.25 2.25 0 0019.5 4.5h-15a2.25 2.25 0 00-2.25 2.25v10.5A2.25 2.25 0 004.5 19.5z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
const ChatIcon = ({ className }: { className?: string }) => (
|
||||
<svg
|
||||
className={className}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M8.625 12a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0H8.25m4.125 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0H12m4.125 0a.375.375 0 11-.75 0 .375.375 0 01.75 0zm0 0h-.375M21 12c0 4.556-4.03 8.25-9 8.25a9.764 9.764 0 01-2.555-.337A5.972 5.972 0 015.41 20.97a5.969 5.969 0 01-.474-.065 4.48 4.48 0 00.978-2.025c.09-.457-.133-.901-.467-1.226C3.93 16.178 3 14.189 3 12c0-4.556 4.03-8.25 9-8.25s9 3.694 9 8.25z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
const UserIcon = ({ className }: { className?: string }) => (
|
||||
<svg
|
||||
className={className}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M15.75 6a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0zM4.501 20.118a7.5 7.5 0 0114.998 0A17.933 17.933 0 0112 21.75c-2.676 0-5.216-.584-7.499-1.632z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
const UsersIcon = ({ className }: { className?: string }) => (
|
||||
<svg
|
||||
className={className}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M15 19.128a9.38 9.38 0 002.625.372 9.337 9.337 0 004.121-.952 4.125 4.125 0 00-7.533-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 018.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0111.964-3.07M12 6.375a3.375 3.375 0 11-6.75 0 3.375 3.375 0 016.75 0zm8.25 2.25a2.625 2.625 0 11-5.25 0 2.625 2.625 0 015.25 0z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
const ShieldIcon = ({ className }: { className?: string }) => (
|
||||
<svg
|
||||
className={className}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M9 12.75L11.25 15 15 9.75m-3-7.036A11.959 11.959 0 013.598 6 11.99 11.99 0 003 9.749c0 5.592 3.824 10.29 9 11.623 5.176-1.332 9-6.03 9-11.622 0-1.31-.21-2.571-.598-3.751h-.152c-3.196 0-6.1-1.248-8.25-3.285z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
const InfoIcon = ({ className }: { className?: string }) => (
|
||||
<svg
|
||||
className={className}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M11.25 11.25l.041-.02a.75.75 0 011.063.852l-.708 2.836a.75.75 0 001.063.853l.041-.021M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9-3.75h.008v.008H12V8.25z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
const LogoutIcon = ({ className }: { className?: string }) => (
|
||||
<svg
|
||||
className={className}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M15.75 9V5.25A2.25 2.25 0 0013.5 3h-6a2.25 2.25 0 00-2.25 2.25v13.5A2.25 2.25 0 007.5 21h6a2.25 2.25 0 002.25-2.25V15M12 9l-3 3m0 0l3 3m-3-3h12.75"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
const SunIcon = ({ className }: { className?: string }) => (
|
||||
<svg
|
||||
className={className}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M12 3v2.25m6.364.386l-1.591 1.591M21 12h-2.25m-.386 6.364l-1.591-1.591M12 18.75V21m-4.773-4.227l-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
const MoonIcon = ({ className }: { className?: string }) => (
|
||||
<svg
|
||||
className={className}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M21.752 15.002A9.718 9.718 0 0118 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 003 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 009.002-5.998z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
interface AppShellProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { motion } from 'framer-motion';
|
||||
import { newsApi } from '../../api/news';
|
||||
import { useHapticFeedback } from '../../platform/hooks/useHaptic';
|
||||
import { cn } from '../../lib/utils';
|
||||
import { ArrowIcon } from '@/components/icons';
|
||||
import type { NewsListItem } from '../../types/news';
|
||||
|
||||
// --- Security: hex color validation to prevent CSS injection ---
|
||||
@@ -31,19 +32,6 @@ const fadeSlideUp = {
|
||||
}),
|
||||
};
|
||||
|
||||
// --- Icons ---
|
||||
const ArrowIcon = () => (
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
|
||||
<path
|
||||
d="M3 8h10M9 4l4 4-4 4"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
// --- Sub-components ---
|
||||
|
||||
interface CategoryBadgeProps {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiCaretDownDuotone } from 'react-icons/pi';
|
||||
|
||||
import { CheckIcon, CopyIcon } from '@/components/icons';
|
||||
import type { PartnerCampaignInfo } from '../../api/partners';
|
||||
import { PARTNER_STATS } from '../../constants/partner';
|
||||
import { useCurrency } from '../../hooks/useCurrency';
|
||||
@@ -9,32 +11,8 @@ import { copyToClipboard } from '../../utils/clipboard';
|
||||
import { CampaignDetailStats } from './CampaignDetailStats';
|
||||
import { StatCard } from '../stats/StatCard';
|
||||
|
||||
const CopyIcon = () => (
|
||||
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M15.666 3.888A2.25 2.25 0 0013.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 01-.75.75H9a.75.75 0 01-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 01-2.25 2.25H6.75A2.25 2.25 0 014.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 011.927-.184"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
const CheckIcon = () => (
|
||||
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M4.5 12.75l6 6 9-13.5" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
const ChevronIcon = ({ expanded }: { expanded: boolean }) => (
|
||||
<svg
|
||||
className={`h-5 w-5 transition-transform ${expanded ? 'rotate-180' : ''}`}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.5}
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5" />
|
||||
</svg>
|
||||
<PiCaretDownDuotone className={`h-5 w-5 transition-transform ${expanded ? 'rotate-180' : ''}`} />
|
||||
);
|
||||
|
||||
interface CampaignCardProps {
|
||||
|
||||
@@ -1,20 +1,8 @@
|
||||
import { Command as CommandPrimitive } from 'cmdk';
|
||||
import { forwardRef, type ComponentPropsWithoutRef, type HTMLAttributes } from 'react';
|
||||
import { SearchIcon } from '@/components/icons';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
// Search icon
|
||||
const SearchIcon = () => (
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" className="text-dark-400">
|
||||
<path
|
||||
d="M7.333 12.667A5.333 5.333 0 1 0 7.333 2a5.333 5.333 0 0 0 0 10.667ZM14 14l-2.9-2.9"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
// Root Command
|
||||
export type CommandProps = ComponentPropsWithoutRef<typeof CommandPrimitive>;
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
useState,
|
||||
} from 'react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { CloseIcon } from '@/components/icons';
|
||||
import { backdrop, backdropTransition, scale, scaleTransition } from '../../motion/transitions';
|
||||
|
||||
export {
|
||||
@@ -16,19 +17,6 @@ export {
|
||||
Close as DialogClose,
|
||||
} from '@radix-ui/react-dialog';
|
||||
|
||||
// Close icon
|
||||
const CloseIcon = () => (
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
|
||||
<path
|
||||
d="M12 4L4 12M4 4l8 8"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
// Context for AnimatePresence
|
||||
const DialogContext = createContext<{ open: boolean }>({ open: false });
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
|
||||
import { motion } from 'framer-motion';
|
||||
import { forwardRef, type ComponentPropsWithoutRef } from 'react';
|
||||
import { CheckIcon, ChevronRightIcon, DotIcon } from '@/components/icons';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { usePlatform } from '@/platform';
|
||||
import { dropdown, dropdownTransition } from '../../motion/transitions';
|
||||
@@ -14,37 +15,6 @@ export {
|
||||
RadioGroup as DropdownMenuRadioGroup,
|
||||
} from '@radix-ui/react-dropdown-menu';
|
||||
|
||||
// Icons
|
||||
const CheckIcon = () => (
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
|
||||
<path
|
||||
d="M3.5 8.5L6.5 11.5L12.5 4.5"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
const ChevronRightIcon = () => (
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
|
||||
<path
|
||||
d="M6 4l4 4-4 4"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
const DotIcon = () => (
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
|
||||
<circle cx="8" cy="8" r="3" fill="currentColor" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
// SubTrigger
|
||||
export interface DropdownMenuSubTriggerProps extends ComponentPropsWithoutRef<
|
||||
typeof DropdownMenuPrimitive.SubTrigger
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as PopoverPrimitive from '@radix-ui/react-popover';
|
||||
import { motion } from 'framer-motion';
|
||||
import { forwardRef, type ComponentPropsWithoutRef } from 'react';
|
||||
import { CloseIcon } from '@/components/icons';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { dropdown, dropdownTransition } from '../../motion/transitions';
|
||||
|
||||
@@ -11,19 +12,6 @@ export {
|
||||
Close as PopoverClose,
|
||||
} from '@radix-ui/react-popover';
|
||||
|
||||
// Close icon
|
||||
const CloseIcon = () => (
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
|
||||
<path
|
||||
d="M12 4L4 12M4 4l8 8"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
// Content
|
||||
export interface PopoverContentProps extends ComponentPropsWithoutRef<
|
||||
typeof PopoverPrimitive.Content
|
||||
|
||||
@@ -2,36 +2,12 @@ import * as SelectPrimitive from '@radix-ui/react-select';
|
||||
import { motion } from 'framer-motion';
|
||||
import { forwardRef, type ComponentPropsWithoutRef, type ReactNode } from 'react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { CheckIcon, ChevronDownIcon } from '@/components/icons';
|
||||
import { usePlatform } from '@/platform';
|
||||
import { dropdown, dropdownTransition } from '../../motion/transitions';
|
||||
|
||||
export { Root as Select, Group as SelectGroup } from '@radix-ui/react-select';
|
||||
|
||||
// Icons
|
||||
const ChevronDownIcon = () => (
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" className="text-dark-400">
|
||||
<path
|
||||
d="M4 6L8 10L12 6"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
const CheckIcon = () => (
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" className="text-accent-400">
|
||||
<path
|
||||
d="M3.5 8.5L6.5 11.5L12.5 4.5"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
// Trigger
|
||||
export interface SelectTriggerProps extends ComponentPropsWithoutRef<
|
||||
typeof SelectPrimitive.Trigger
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
useState,
|
||||
useCallback,
|
||||
} from 'react';
|
||||
import { CloseIcon } from '@/components/icons';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { usePlatform } from '@/platform';
|
||||
import {
|
||||
@@ -23,19 +24,6 @@ export {
|
||||
Close as SheetClose,
|
||||
} from '@radix-ui/react-dialog';
|
||||
|
||||
// Close icon
|
||||
const CloseIcon = () => (
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
|
||||
<path
|
||||
d="M12 4L4 12M4 4l8 8"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
// Context for AnimatePresence and control
|
||||
interface SheetContextValue {
|
||||
open: boolean;
|
||||
|
||||
Reference in New Issue
Block a user