Skip to content
Open
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
15 changes: 13 additions & 2 deletions components/DrawerHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { DropdownActionItem } from './table/RowActionsMenu';
import { Button } from './ui/Button';
import { DropdownMenu, DropdownMenuContent, DropdownMenuSeparator, DropdownMenuTrigger } from './ui/DropdownMenu';
import { SheetClose } from './ui/Sheet';
import Link from './Link';

type DrawerHeaderProps = {
actions?: {
Expand Down Expand Up @@ -68,15 +69,25 @@ export default function DrawerHeader({
{primary?.map(action => (
<Button
key={action.key}
asChild={Boolean(action.href)}
variant="outline"
size="xs"
className="gap-1.5"
onClick={action.onClick}
disabled={action.disabled}
data-cy={action['data-cy']}
>
{action.Icon && <action.Icon size={16} />}
<span>{action.label}</span>
{action.href ? (
<Link href={action.href} target={action.target}>
{action.Icon && <action.Icon size={16} />}
<span>{action.label}</span>
</Link>
) : (
<React.Fragment>
{action.Icon && <action.Icon size={16} />}
<span>{action.label}</span>
</React.Fragment>
)}
</Button>
))}
</div>
Expand Down
10 changes: 4 additions & 6 deletions components/ExchangeRate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import type { CurrencyExchangeRate, CurrencyExchangeRateInput } from '../lib/gra
import { cn } from '../lib/utils';

import { Input } from './ui/Input';
import { Tooltip, TooltipContent, TooltipPortal, TooltipTrigger } from './ui/Tooltip';
import { Tooltip, TooltipContent, TooltipTrigger } from './ui/Tooltip';
import { formatFxRateInfo } from './AmountWithExchangeRateInfo';
import Spinner from './Spinner';

Expand Down Expand Up @@ -98,11 +98,9 @@ export const ExchangeRate = ({
)}
</div>
</TooltipTrigger>
<TooltipPortal>
<TooltipContent data-cy={`${dataCy}-tooltip`}>
{formatFxRateInfo(intl, exchangeRate, { approximateCustomMessage, warning, error })}
</TooltipContent>
</TooltipPortal>
<TooltipContent data-cy={`${dataCy}-tooltip`}>
{formatFxRateInfo(intl, exchangeRate, { approximateCustomMessage, warning, error })}
</TooltipContent>
</Tooltip>
);
};
49 changes: 20 additions & 29 deletions components/dashboard/sections/contributions/actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,12 @@ import type { ContributionDrawerQuery, ManagedOrderFieldsFragment } from '../../
import { ContributionFrequency, OrderStatus, PaymentMethodType } from '../../../../lib/graphql/types/v2/graphql';
import useLoggedInUser from '../../../../lib/hooks/useLoggedInUser';
import { getWebsiteUrl } from '../../../../lib/utils';
import useClipboard from '@/lib/hooks/useClipboard';

import ContributionConfirmationModal from '../../../ContributionConfirmationModal';
import { getTransactionsUrl } from '../../../contributions/ContributionTimeline';
import { CopyID } from '../../../CopyId';
import type { EditOrderActions } from '../../../EditOrderModal';
import EditOrderModal from '../../../EditOrderModal';
import Link from '../../../Link';
import { useModal } from '../../../ModalContext';
import { useToast } from '../../../ui/useToast';

Expand Down Expand Up @@ -54,6 +53,7 @@ export function useContributionActions<T extends ManagedOrderFieldsFragment | Co
const { toast } = useToast();
const { showModal, showConfirmationModal } = useModal();
const { LoggedInUser } = useLoggedInUser();
const { copy } = useClipboard();

const [expireOrder] = useMutation(expireOrderMutation);

Expand Down Expand Up @@ -119,12 +119,9 @@ export function useContributionActions<T extends ManagedOrderFieldsFragment | Co
if (![OrderStatus.PENDING, OrderStatus.EXPIRED].includes(order.status)) {
actions.secondary.push({
key: 'view-transactions',
label: (
<Link href={transactionsUrl.toString()} className="flex flex-row items-center gap-2.5">
<ArrowLeftRightIcon size={16} className="text-muted-foreground" />
<FormattedMessage defaultMessage="View transactions" id="DfQJQ6" />
</Link>
),
Icon: ArrowLeftRightIcon,
href: transactionsUrl.toString(),
label: intl.formatMessage({ defaultMessage: 'View transactions', id: 'DfQJQ6' }),
onClick: () => {},
});
}
Expand Down Expand Up @@ -157,8 +154,9 @@ export function useContributionActions<T extends ManagedOrderFieldsFragment | Co
}

if (isExpectedFunds && (canMarkAsExpired || canMarkAsCompleted)) {
actions.primary.push({
actions.secondary.push({
label: intl.formatMessage({ defaultMessage: 'Edit expected funds', id: 'hQAJH9' }),
Icon: Pencil,
onClick: () => {
showModal(
CreatePendingContributionModal,
Expand Down Expand Up @@ -252,12 +250,8 @@ export function useContributionActions<T extends ManagedOrderFieldsFragment | Co
if (order.paymentMethod?.type === PaymentMethodType.HOST) {
actions.primary.push({
key: 'edit-funds',
label: (
<React.Fragment>
<Pencil size={16} className="text-muted-foreground" />
{intl.formatMessage({ defaultMessage: 'Edit funds', id: 'Kbjd3f' })}
</React.Fragment>
),
Icon: Pencil,
label: intl.formatMessage({ defaultMessage: 'Edit funds', id: 'Kbjd3f' }),
onClick: () => showEditOrderModal('editAddedFunds'),
});
}
Expand All @@ -268,20 +262,17 @@ export function useContributionActions<T extends ManagedOrderFieldsFragment | Co

actions.secondary.push({
key: 'copy-link',
label: (
<CopyID
Icon={null}
value={orderUrl}
tooltipLabel={<FormattedMessage defaultMessage="Copy link" id="CopyLink" />}
className=""
>
<div className="flex flex-row items-center gap-2.5">
<LinkIcon size={16} className="text-muted-foreground" />
<FormattedMessage defaultMessage="Copy link" id="CopyLink" />
</div>
</CopyID>
),
onClick: () => {},
Icon: LinkIcon,
label: intl.formatMessage({ defaultMessage: 'Copy link', id: 'CopyLink' }),
onClick: e => {
e.preventDefault(); // Prevent dropdown from closing when copying

copy(orderUrl);
toast({
message: <FormattedMessage id="Clipboard.Copied" defaultMessage="Copied!" />,
variant: 'success',
});
},
});

return actions;
Expand Down
16 changes: 8 additions & 8 deletions components/dashboard/sections/exports/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useRouter } from 'next/router';
import { defineMessage, FormattedMessage, useIntl } from 'react-intl';
import { z } from 'zod';

import type { Action, GetActions } from '@/lib/actions/types';
import { i18nGraphqlException } from '@/lib/errors';
import { formatFileSize } from '@/lib/file-utils';
import type { FilterComponentConfigs, Views } from '@/lib/filters/filter-types';
Expand Down Expand Up @@ -341,27 +342,25 @@ const Exports = ({ accountSlug, subpath }: DashboardSectionProps) => {
[intl, removeExportRequest, showConfirmationModal, toast],
);

const getActions = React.useCallback(
(exportRequest: ExportRequestNode) => {
const primary = [];
const secondary = [];
const getActions = React.useCallback<GetActions<ExportRequestNode>>(
exportRequest => {
const primary: Action[] = [];
const secondary: Action[] = [];

if (exportRequest?.status === ExportRequestStatus.COMPLETED && exportRequest.file) {
primary.push({
key: 'download',
label: intl.formatMessage({ defaultMessage: 'Download', id: 'Download' }),
Icon: Download,
onClick: () => {
window.open(exportRequest.file.url, '_blank');
},
href: exportRequest.file.url,
target: '_blank',
});
}

secondary.push({
key: 'delete',
label: intl.formatMessage({ defaultMessage: 'Delete', id: 'actions.delete' }),
Icon: Trash2,
variant: 'danger',
Copy link
Member Author

@gustavlrsn gustavlrsn Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing as variant is not implemented in actions pattern (and I think it is better to signal the destructive action in the confirmation dialog)

onClick: () => handleRemove(exportRequest),
});

Expand Down Expand Up @@ -398,6 +397,7 @@ const Exports = ({ accountSlug, subpath }: DashboardSectionProps) => {
getActions={getActions}
onClickRow={row => pushSubpath(row.original.id)}
mobileTableView
showQuickActions
/>
<Pagination queryFilter={queryFilter} total={data?.exportRequests?.totalCount} />
</div>
Expand Down
38 changes: 12 additions & 26 deletions components/dashboard/sections/transactions-imports/lib/actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react';
import { useMutation } from '@apollo/client';
import { cloneDeep, pick, uniq, update } from 'lodash';
import { ArchiveRestore, Banknote, Merge, PauseCircle, Receipt, SquareSlashIcon, Unlink } from 'lucide-react';
import { FormattedMessage, useIntl } from 'react-intl';
import { useIntl } from 'react-intl';

import type { GetActions } from '../../../../../lib/actions/types';
import { i18nGraphqlException } from '../../../../../lib/errors';
Expand All @@ -13,7 +13,6 @@ import type {
import { TransactionsImportRowStatus } from '../../../../../lib/graphql/types/v2/graphql';

import { useModal } from '../../../../ModalContext';
import Spinner from '../../../../Spinner';
import { useToast } from '../../../../ui/useToast';
import { HostCreateExpenseModal } from '../../expenses/HostCreateExpenseModal';
import { AddFundsModalFromImportRow } from '../AddFundsModalFromTransactionsImportRow';
Expand Down Expand Up @@ -176,7 +175,7 @@ export const useTransactionsImportActions = ({
actions.primary.push({
key: 'revert',
Icon: Unlink,
label: <FormattedMessage defaultMessage="Unlink" id="Transaction.Unlink" />,
label: intl.formatMessage({ defaultMessage: 'Unlink', id: 'Transaction.Unlink' }),
disabled: isUpdatingRow,
onClick: () => {
showModal(
Expand All @@ -195,7 +194,7 @@ export const useTransactionsImportActions = ({
actions.primary.push({
key: 'match',
Icon: Merge,
label: <FormattedMessage defaultMessage="Match" id="Qr9R5O" />,
label: intl.formatMessage({ defaultMessage: 'Match', id: 'Qr9R5O' }),
disabled: isUpdatingRow,
onClick: () =>
showModal(
Expand All @@ -217,15 +216,15 @@ export const useTransactionsImportActions = ({
actions.primary.push({
key: 'add-funds',
Icon: Banknote,
label: <FormattedMessage defaultMessage="Add funds" id="sx0aSl" />,
label: intl.formatMessage({ defaultMessage: 'Add funds', id: 'sx0aSl' }),
disabled: isUpdatingRow,
onClick: showAddFundsModal,
});
} else if (row.amount.valueInCents < 0) {
actions.primary.push({
key: 'match',
Icon: Merge,
label: <FormattedMessage defaultMessage="Match" id="Qr9R5O" />,
label: intl.formatMessage({ defaultMessage: 'Match', id: 'Qr9R5O' }),
disabled: isUpdatingRow,
onClick: () => {
showModal(
Expand All @@ -238,7 +237,7 @@ export const useTransactionsImportActions = ({
actions.primary.push({
key: 'create-expense',
Icon: Receipt,
label: <FormattedMessage defaultMessage="Create expense" id="YUK+rq" />,
label: intl.formatMessage({ defaultMessage: 'Create expense', id: 'YUK+rq' }),
disabled: isUpdatingRow,
onClick: () => {
showModal(
Expand All @@ -257,25 +256,17 @@ export const useTransactionsImportActions = ({
Icon: PauseCircle,
onClick: () => setRowsStatus([row.id], TransactionsImportRowStatus.ON_HOLD),
disabled: isUpdatingRow,
label: (
<div>
<FormattedMessage defaultMessage="Put on Hold" id="+pCc8I" />
{isUpdatingRow && <Spinner size={14} className="ml-2" />}
</div>
),
isLoading: isUpdatingRow,
label: intl.formatMessage({ defaultMessage: 'Put on Hold', id: '+pCc8I' }),
});
} else {
actions.primary.push({
key: 'restore',
Icon: ArchiveRestore,
onClick: () => setRowsStatus([row.id], TransactionsImportRowStatus.PENDING),
disabled: isUpdatingRow,
label: (
<div>
<FormattedMessage defaultMessage="Restore" id="zz6ObK" />
{isUpdatingRow && <Spinner size={14} className="ml-2" />}
</div>
),
isLoading: isUpdatingRow,
label: intl.formatMessage({ defaultMessage: 'Restore', id: 'zz6ObK' }),
});
}

Expand All @@ -285,13 +276,8 @@ export const useTransactionsImportActions = ({
Icon: SquareSlashIcon,
onClick: () => setRowsStatus([row.id], TransactionsImportRowStatus.IGNORED),
disabled: isUpdatingRow,
label: (
<div>
<FormattedMessage defaultMessage="No action" id="zue9QR" />

{isUpdatingRow && <Spinner size={14} className="ml-2" />}
</div>
),
isLoading: isUpdatingRow,
label: intl.formatMessage({ defaultMessage: 'No action', id: 'zue9QR' }),
});
}

Expand Down
5 changes: 4 additions & 1 deletion components/table/DataTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ interface DataTableProps<TData, TValue> {
rowSelection?: Record<string, boolean>;
setRowSelection?: OnChangeFn<RowSelectionState>;
enableMultiRowSelection?: boolean;
showQuickActions?: boolean;
}

const defaultGetRowId = (data: any) => data.id;
Expand Down Expand Up @@ -90,6 +91,7 @@ export function DataTable<TData, TValue>({
rowSelection: rowSelectionFromProps,
setRowSelection: setRowSelectionFromProps,
enableMultiRowSelection,
showQuickActions,
meta, // TODO: Possibly remove this prop once the getActions pattern is implemented fully
...tableProps
}: DataTableProps<TData, TValue>) {
Expand Down Expand Up @@ -125,6 +127,7 @@ export function DataTable<TData, TValue>({
hasDefaultColumnVisibility,
setColumnVisibility,
defaultColumnVisibility,
showQuickActions,
...meta,
},
});
Expand Down Expand Up @@ -277,7 +280,7 @@ export const stickyColumnVariants = cva(
select:
'left-0 z-1 w-10 max-w-10 min-w-10 [filter:drop-shadow(2px_0px_6px_rgba(0,0,0,var(--scroll-shadow-left,0)))] [clip-path:inset(0px_-20px_0px_0px)]',
actions:
'right-0 w-12 max-w-12 min-w-12 !pr-2 [filter:drop-shadow(-2px_0px_6px_rgba(0,0,0,var(--scroll-shadow-right,0)))] [clip-path:inset(0px_0px_0px_-20px)]',
'right-0 w-12 max-w-12 min-w-12 !pr-2 [filter:drop-shadow(-2px_0px_6px_rgba(0,0,0,var(--scroll-shadow-right,0)))]', //[clip-path:inset(0px_0px_0px_-20px)]
},
},
},
Expand Down
Loading
Loading