Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 53 additions & 14 deletions src/ui/segments/project/balance.tsx
Original file line number Diff line number Diff line change
@@ -1,45 +1,84 @@
import { LoadingOutlined, WarningOutlined } from '@ant-design/icons';
import { useQuery } from '@tanstack/react-query';
import { match, P } from 'ts-pattern';
import { Tooltip } from 'antd';
import { useState } from 'react';

import { CreditsTransferModal } from '@/ui/segments/project/credits/credits-transfer-modal';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/ui/molecules/tooltip';
import { getProjectAccountBalance } from '@/services/virtual-lab/projects';
import { useDefaultBreakpoint } from '@/ui/hooks/create-break-point';
import { getUserGroups } from '@/api/virtual-lab-svc/queries/user';
import { keyBuilder } from '@/ui/use-query-keys/workspace';
import { useWorkspace } from '@/ui/hooks/use-workspace';
import { CoinsIcon } from '@/components/icons/buttons';
import { keyBuilder } from '@/ui/use-query-keys/workspace';
import { makeRoles } from '@/hooks/use-user-role';
import { Badge } from '@/ui/molecules/badge';
import { cn } from '@/utils/css-class';

export function Wallet() {
const breakpoint = useDefaultBreakpoint();
const { virtualLabId, projectId } = useWorkspace();
const [showCreditsManagement, setShowCreditsManagement] = useState(false);
const handleTransferCredits = () => setShowCreditsManagement((prev) => !prev);

const { data, isLoading, isError, isSuccess, error } = useQuery({
queryKey: keyBuilder.wallet({ virtualLabId, projectId }),
queryFn: () => getProjectAccountBalance({ virtualLabId, projectId }),
select: (res) => res.balance,
});

const { data: roles, isLoading: loadingRoles } = useQuery({
queryKey: keyBuilder.roles(),
queryFn: getUserGroups,
});

const { isAdmin } = makeRoles(roles, virtualLabId, projectId);

const content = match({ isError, isLoading, isSuccess, data, error })
.with({ isLoading: true }, () => <LoadingOutlined spin />)
.with({ isError: true, error: P.select() }, (err) => (
<Tooltip placement="topLeft" title={err?.message} arrow>
<WarningOutlined />
<Tooltip>
<TooltipTrigger>
<WarningOutlined />
</TooltipTrigger>
<TooltipContent side="bottom">{err?.message}</TooltipContent>
</Tooltip>
))
.with({ isSuccess: true, data: P.select() }, (balance) => <>{balance}</>)
.otherwise(() => null);

return (
<Badge
rounded
id="workspace-project-credits"
className="min-w-16 font-bold select-none"
variant="outline"
size={breakpoint === 'xl' ? 'lg' : 'md'}
>
<CoinsIcon />
{content}
</Badge>
<>
<Tooltip>
<TooltipTrigger>
<Badge
rounded
id="workspace-project-credits"
className={cn('min-w-16 cursor-pointer font-bold select-none', {
'pointer-events-none cursor-not-allowed!': !isAdmin,
})}
variant="outline"
size={breakpoint === 'xl' ? 'lg' : 'md'}
aria-disabled={!isAdmin || loadingRoles || isLoading || isError}
onClick={isAdmin ? handleTransferCredits : undefined}
>
<CoinsIcon />
{content}
</Badge>
</TooltipTrigger>
{!isAdmin && (
<TooltipContent
avoidCollisions
side="bottom"
sideOffset={5}
collisionPadding={{ bottom: 20 }}
className="text-primary-8 max-w-2xs bg-white text-base shadow-lg"
>
For more information, please contact the Virtual lab administrators.
</TooltipContent>
)}
</Tooltip>
<CreditsTransferModal open={showCreditsManagement} onClose={handleTransferCredits} />
</>
);
}
44 changes: 33 additions & 11 deletions src/ui/segments/project/credits/credits-transfer-modal.tsx
Original file line number Diff line number Diff line change
@@ -1,43 +1,65 @@
'use client';

import { CloseOutlined } from '@ant-design/icons';
import { useQuery } from '@tanstack/react-query';
import noop from 'lodash/noop';

import { ManageCreditsStep } from '@/ui/segments/virtual-lab-settings/elements/manage-credits';
import { getProject } from '@/api/virtual-lab-svc/queries/project';
import { keyBuilder } from '@/ui/use-query-keys/workspace';
import { useWorkspace } from '@/ui/hooks/use-workspace';
import { Modal } from '@/ui/molecules/modal';
import { Button } from '@/ui/molecules/button';
import { cn } from '@/utils/css-class';

type Props = {
open: boolean;
onClose: () => void;
};

export function CreditsTransferModal({ open, onClose }: Props) {
const { virtualLabId } = useWorkspace();
const { virtualLabId, projectId } = useWorkspace();
const { data: project } = useQuery({
queryKey: keyBuilder.getOne({ virtualLabId, projectId }),
queryFn: () => getProject({ virtualLabId, projectId }),
});

return (
<Modal
closable={false}
open={open}
title={
<div className="flex flex-col items-start justify-between">
<h2 className="text-2xl font-bold text-white">Transfer Credits</h2>
<p className="text-neutral-1 text-sm font-light">
Transfer credits between your virtual lab and projects.
</p>
<div className="flex w-full items-center justify-between gap-4 select-none">
<div className="flex flex-col items-start justify-between">
<h2 className="text-2xl font-bold text-white">{project?.data.project.name}</h2>
<p className="text-neutral-1 text-sm font-light">
Transfer credits between your virtual lab and projects.
</p>
</div>
<Button
size="sm"
variant="outline"
onClick={onClose}
className="bg-primary-9 hover:bg-neutral-1/40 border-none !p-2"
>
<CloseOutlined className="text-lg text-white" />
</Button>
</div>
}
onClose={onClose}
size="auto"
position="center"
animation="scale"
maxWidth={700}
width={700}
className="!bg-primary-9 !fixed !top-1/2 !left-1/2 !z-[1000] !-translate-x-1/2 !-translate-y-1/2 !transform"
closable
closeIcon={<CloseOutlined className="text-lg text-white" />}
closeIconClassName="hover:bg-neutral-1/40"
headerClassName={cn('[&>div]:w-full')}
>
<ManageCreditsStep virtualLabId={virtualLabId} onBack={noop} shouldHaveBack={false} />
<ManageCreditsStep
virtualLabId={virtualLabId}
onBack={noop}
shouldHaveBack={false}
swapClassname=""
/>
</Modal>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ type ManageCreditsStepProps = {
virtualLabId: string;
onBack: () => void;
shouldHaveBack?: boolean;
swapClassname?: string;
};

type TransferDirection = 'vlab->proj' | 'proj->vlab';
Expand Down Expand Up @@ -70,6 +71,7 @@ export function ManageCreditsStep({
onBack,
virtualLabId,
shouldHaveBack = true,
swapClassname,
}: ManageCreditsStepProps) {
const queryClient = useQueryClient();
const [amount, setAmount] = useState<string | undefined>(undefined);
Expand Down Expand Up @@ -237,7 +239,10 @@ export function ManageCreditsStep({
<motion.button
type="button"
aria-label="Swap transfer direction"
className="bg-primary-8 hover:bg-primary-7 flex h-8 w-8 items-center justify-center rounded-md border border-white/20 text-white transition-all hover:scale-105 disabled:opacity-50"
className={cn(
'bg-primary-8 hover:bg-primary-7 flex h-8 w-8 items-center justify-center rounded-md border border-white/20 text-white transition-all hover:scale-105 disabled:opacity-50',
swapClassname
)}
onClick={onSwap}
disabled={isPending}
whileHover={{ scale: 1.05 }}
Expand Down
16 changes: 13 additions & 3 deletions src/ui/segments/workflows/build/memodel/index.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
'use client';

import { useSearchParams } from 'next/navigation';
import { match } from 'ts-pattern';
import { match, P } from 'ts-pattern';
import isNil from 'lodash/isNil';

import {
BuildStep,
useBuildMeModelSessionState,
} from '@/ui/segments/workflows/build/memodel/helpers';
import { EModel } from '@/ui/segments/workflows/build/memodel/e-model';
import { MModel } from '@/ui/segments/workflows/build/memodel/m-model';
import { EModel, EModelMiniDetail } from '@/ui/segments/workflows/build/memodel/e-model';
import { MModel, MModelMiniDetail } from '@/ui/segments/workflows/build/memodel/m-model';
import { Info } from '@/ui/segments/workflows/build/memodel/overview';
import { useWorkspace } from '@/ui/hooks/use-workspace';

Expand All @@ -34,11 +34,21 @@ export function Content({ sessionId }: Props) {
() => isNil(sessionValue) || isNil(sessionValue.mmodel),
() => <MModel sessionId={sessionId} />
)
.with(
{ step: BuildStep.MModel, sessionValue: P.when((v) => !isNil(v.mmodel)) },
() => !isNil(sessionValue.mmodel),
() => <MModelMiniDetail sessionId={sessionId} />
)
.with(
{ step: BuildStep.EModel },
() => isNil(sessionValue) || isNil(sessionValue.emodel),
() => <EModel sessionId={sessionId} />
)
.with(
{ step: BuildStep.EModel },
() => !isNil(sessionValue.emodel),
() => <EModelMiniDetail sessionId={sessionId} />
)
.otherwise(() => null);

return content;
Expand Down