Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add submit expense page #10776

Merged
merged 28 commits into from
Dec 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
8e7e381
Remove old components
hdiniz Nov 3, 2024
5af570a
Add options to existing components
hdiniz Nov 3, 2024
e2f5cb1
Add expense policies settings
hdiniz Nov 3, 2024
ed8d2c0
Add form sections
hdiniz Nov 3, 2024
7afac44
Add currency to payout method details
hdiniz Nov 3, 2024
7c80dc3
Refactor form parent components and form hook
hdiniz Nov 3, 2024
8ed166d
Add cva to tailwindFunctons plugin
hdiniz Nov 3, 2024
0fea669
Fix duplicated i18n messages
hdiniz Nov 3, 2024
4b9aaf7
Add intl messages
hdiniz Nov 3, 2024
82ba1e1
Go to new expense flow from collective page when feature flag is enabled
hdiniz Nov 4, 2024
a41a86a
fix prettier issues
hdiniz Nov 4, 2024
b0198f0
Fix deepscan issues
hdiniz Nov 4, 2024
17d97cf
fix e2e test
hdiniz Nov 4, 2024
1b9b031
Fix expense policy test
hdiniz Nov 4, 2024
574dbd3
wip
hdiniz Nov 6, 2024
2821196
Add RadioGroupCard ui component
gustavlrsn Nov 6, 2024
cf45ef9
wip
hdiniz Nov 6, 2024
e843847
Style adjustments
hdiniz Nov 6, 2024
4d11a29
remove unused component
hdiniz Nov 6, 2024
becc14b
Remove unused code
hdiniz Nov 6, 2024
c6548f0
Collapsible payout method details
hdiniz Nov 27, 2024
327e32b
remove unused export
hdiniz Nov 27, 2024
f82b981
Fix collapsible payout method to match design
hdiniz Nov 27, 2024
f495f99
update graphql
hdiniz Nov 29, 2024
2d19682
update langs
hdiniz Nov 29, 2024
daffd20
Update collapsible payout method details section
gustavlrsn Nov 29, 2024
1cb80b5
Fix expense type select and item attachment required hint
hdiniz Nov 29, 2024
4121f1f
update lang
hdiniz Nov 29, 2024
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
2 changes: 1 addition & 1 deletion .prettierrc
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@
"arrowParens": "avoid",
"printWidth": 120,
"plugins": ["prettier-plugin-tailwindcss"],
"tailwindFunctions": ["clsx", "cn"]
"tailwindFunctions": ["clsx", "cn", "cva"]
}
2 changes: 1 addition & 1 deletion components/AvatarWithLink.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import Avatar from './Avatar';
import LinkCollective from './LinkCollective';

type AvatarWithLinkProps = {
account: Pick<Account, 'name' | 'type' | 'isIncognito' | 'slug' | 'imageUrl'>;
account: Pick<Account, 'name' | 'type' | 'slug' | 'imageUrl'> & { isIncognito?: boolean };
secondaryAccount?: Partial<Account> | null;
/** The size in pixels */
size: number;
Expand Down
6 changes: 3 additions & 3 deletions components/PayoutMethodLabel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export function PayoutMethodLabel(props: PayoutMethodLabelProps) {
} else if (pm.data?.details?.clabe) {
label = `Clabe ${pm.data.details.clabe}`;
} else if (pm.data?.details?.bankgiroNumber) {
label = `BankGiro ${pm.data.details.bankgiroNumber}`;
label = `Bankgiro ${pm.data.details.bankgiroNumber}`;
} else if (pm.data?.accountHolderName && pm.data?.currency) {
label = `${pm.data.accountHolderName} (${pm.data.currency})`;
}
Expand All @@ -83,11 +83,11 @@ export function PayoutMethodLabel(props: PayoutMethodLabelProps) {

if (props.showIcon) {
return (
<span className="whitespace-nowrap">
<div className="flex items-center gap-2 whitespace-nowrap">
<PayoutMethodIcon payoutMethod={pm} />
&nbsp;
{label}
</span>
</div>
);
}

Expand Down
32 changes: 18 additions & 14 deletions components/StyledDropzone.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ const StyledDropzone = ({
previewSize = size,
limit = undefined,
kind = null,
showReplaceAction = true,
...props
}: StyledDropzoneProps) => {
const { toast } = useToast();
Expand Down Expand Up @@ -189,7 +190,7 @@ const StyledDropzone = ({
size={size}
error={error}
>
<input name={name} {...getInputProps()} />
<input name={name} disabled={props.disabled} {...getInputProps()} />
{isLoading || isUploading || isUploadingWithGraphQL ? (
<Container
position="relative"
Expand Down Expand Up @@ -320,19 +321,21 @@ const StyledDropzone = ({
) : typeof value === 'string' ? (
<React.Fragment>
<UploadedFilePreview size={previewSize || size} url={value} border="none" />
<ReplaceContainer
onClick={dropProps.onClick}
role="button"
tabIndex={0}
onKeyDown={event => {
if (event.key === 'Enter') {
event.preventDefault();
dropProps.onClick(null);
}
}}
>
<FormattedMessage id="Image.Replace" defaultMessage="Replace" />
</ReplaceContainer>
{showReplaceAction && (
<ReplaceContainer
onClick={dropProps.onClick}
role="button"
tabIndex={0}
onKeyDown={event => {
if (event.key === 'Enter') {
event.preventDefault();
dropProps.onClick(null);
}
}}
>
<FormattedMessage id="Image.Replace" defaultMessage="Replace" />
</ReplaceContainer>
)}
</React.Fragment>
) : value instanceof File ? (
<LocalFilePreview size={previewSize || size} file={value} alignItems="center" />
Expand Down Expand Up @@ -418,6 +421,7 @@ type StyledDropzoneProps = Omit<ContainerProps, 'accept' | 'children' | 'ref' |
UploadingComponent?: React.ComponentType;
/** When isMulti is true, limit the number of files that can be uploaded */
limit?: number;
showReplaceAction?: boolean;
} & (
| {
/** Collect File only, do not upload files */
Expand Down
2 changes: 1 addition & 1 deletion components/StyledInputFormikField.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ const StyledInputFormikField = ({
StyledInputFormikField.propTypes = {
name: PropTypes.string.isRequired,
validate: PropTypes.func,
isFastField: PropTypes.func,
isFastField: PropTypes.bool,
children: PropTypes.func,
/** the label's 'for' attribute to be used as the 'name' and 'id' for the input */
htmlFor: PropTypes.string,
Expand Down
2 changes: 1 addition & 1 deletion components/UploadedFilePreview.js
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ const UploadedFilePreview = ({
}

const getContainerAttributes = () => {
if (isPrivate) {
if (isPrivate || !url) {
return { as: 'div' };
} else if (isText || !openFileViewer) {
return { href: url, target: '_blank', rel: 'noopener noreferrer', as: url.startsWith('/') ? Link : StyledLink };
Expand Down
45 changes: 34 additions & 11 deletions components/collective-navbar/ActionsMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ import { FormattedMessage } from 'react-intl';
import styled, { css } from 'styled-components';

import { getContributeRoute } from '../../lib/collective';
import { isSupportedExpenseType } from '../../lib/expenses';
import { ExpenseType } from '../../lib/graphql/types/v2/graphql';
import { PREVIEW_FEATURE_KEYS } from '../../lib/preview-features';
import { getCollectivePageRoute, getDashboardRoute } from '../../lib/url-helpers';

import ActionButton from '../ActionButton';
Expand Down Expand Up @@ -174,11 +177,21 @@ const StyledChevronDown = styled(ChevronDown)`

const ITEM_PADDING = '11px 14px';

const CollectiveNavbarActionsMenu = ({ collective, callsToAction = {}, hiddenActionForNonMobile, LoggedInUser }) => {
const CollectiveNavbarActionsMenu = ({
collective,
callsToAction = {},
hiddenActionForNonMobile,
LoggedInUser,
onOpenSubmitExpenseModalClick = () => {},
}) => {
const enabledCTAs = Object.keys(pickBy(callsToAction, Boolean));
const isEmpty = enabledCTAs.length < 1;
const hasOnlyOneHiddenCTA = enabledCTAs.length === 1 && hiddenActionForNonMobile === enabledCTAs[0];

const isNewExpenseFlowEnabled =
LoggedInUser?.hasPreviewFeatureEnabled(PREVIEW_FEATURE_KEYS.NEW_EXPENSE_FLOW) &&
!isSupportedExpenseType(collective, ExpenseType.GRANT);

// Do not render the menu if there are no available CTAs
if (isEmpty) {
return null;
Expand Down Expand Up @@ -226,16 +239,25 @@ const CollectiveNavbarActionsMenu = ({ collective, callsToAction = {}, hiddenAct
)}
{callsToAction.hasSubmitExpense && (
<MenuItem isHiddenOnMobile={hiddenActionForNonMobile === NAVBAR_ACTION_TYPE.SUBMIT_EXPENSE}>
<StyledLink
data-cy="submit-expense-dropdown"
as={Link}
href={`${getCollectivePageRoute(collective)}/expenses/new`}
>
<Container p={ITEM_PADDING}>
<Receipt size="20px" />
<FormattedMessage id="ExpenseForm.Submit" defaultMessage="Submit expense" />
</Container>
</StyledLink>
{isNewExpenseFlowEnabled ? (
<StyledLink onClick={onOpenSubmitExpenseModalClick}>
<Container p={ITEM_PADDING}>
<Receipt size="20px" />
<FormattedMessage id="ExpenseForm.Submit" defaultMessage="Submit expense" />
</Container>
</StyledLink>
) : (
<StyledLink
data-cy="submit-expense-dropdown"
as={Link}
href={`${getCollectivePageRoute(collective)}/expenses/new`}
>
<Container p={ITEM_PADDING}>
<Receipt size="20px" />
<FormattedMessage id="ExpenseForm.Submit" defaultMessage="Submit expense" />
</Container>
</StyledLink>
)}
</MenuItem>
)}
{callsToAction.hasRequestGrant && (
Expand Down Expand Up @@ -418,6 +440,7 @@ CollectiveNavbarActionsMenu.propTypes = {
}).isRequired,
hiddenActionForNonMobile: PropTypes.oneOf(Object.values(NAVBAR_ACTION_TYPE)),
LoggedInUser: PropTypes.object,
onOpenSubmitExpenseModalClick: PropTypes.func,
};

export default CollectiveNavbarActionsMenu;
44 changes: 40 additions & 4 deletions components/collective-navbar/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@ import EXPENSE_TYPE from '../../lib/constants/expenseTypes';
import roles from '../../lib/constants/roles';
import { isSupportedExpenseType } from '../../lib/expenses';
import { API_V2_CONTEXT, gql } from '../../lib/graphql/helpers';
import { ExpenseType } from '../../lib/graphql/types/v2/graphql';
import useGlobalBlur from '../../lib/hooks/useGlobalBlur';
import useLoggedInUser from '../../lib/hooks/useLoggedInUser';
import { PREVIEW_FEATURE_KEYS } from '../../lib/preview-features';
import { getCollectivePageRoute, getDashboardRoute } from '../../lib/url-helpers';

import ActionButton from '../ActionButton';
Expand All @@ -40,6 +42,7 @@ import LinkCollective from '../LinkCollective';
import LoadingPlaceholder from '../LoadingPlaceholder';
import StyledButton from '../StyledButton';
import { fadeIn } from '../StyledKeyframes';
import { SubmitExpenseFlow } from '../submit-expense/SubmitExpenseFlow';
import { Span } from '../Text';

import CollectiveNavbarActionsMenu from './ActionsMenu';
Expand Down Expand Up @@ -310,7 +313,13 @@ const getDefaultCallsToActions = (
/**
* Returns the main CTA that should be displayed as a button outside of the action menu in this component.
*/
const getMainAction = (collective, callsToAction, LoggedInUser) => {
const getMainAction = (
collective,
callsToAction,
LoggedInUser,
isNewExpenseFlowEnabled = false,
onOpenSubmitExpenseModalClick = () => {},
) => {
if (!collective || !callsToAction) {
return null;
}
Expand Down Expand Up @@ -366,7 +375,14 @@ const getMainAction = (collective, callsToAction, LoggedInUser) => {
} else if (callsToAction.includes('hasSubmitExpense')) {
return {
type: NAVBAR_ACTION_TYPE.SUBMIT_EXPENSE,
component: (
component: isNewExpenseFlowEnabled ? (
<ActionButton tabIndex="-1" onClick={onOpenSubmitExpenseModalClick}>
<Receipt size="1em" />
<Span ml={2}>
<FormattedMessage id="menu.submitExpense" defaultMessage="Submit Expense" />
</Span>
</ActionButton>
) : (
<Link href={`${getCollectivePageRoute(collective)}/expenses/new`}>
<ActionButton tabIndex="-1">
<Receipt size="1em" />
Expand Down Expand Up @@ -459,8 +475,14 @@ const CollectiveNavbar = ({
skip: !collective?.slug || !LoggedInUser,
});

const [isSubmitExpenseModalOpen, setIsSubmitExpenseModalOpen] = React.useState(false);

const loading = isLoading || dataLoading;

const isNewExpenseFlowEnabled =
LoggedInUser?.hasPreviewFeatureEnabled(PREVIEW_FEATURE_KEYS.NEW_EXPENSE_FLOW) &&
!isSupportedExpenseType(collective, ExpenseType.GRANT);

const isAllowedAddFunds = Boolean(data?.account?.permissions?.addFunds?.allowed);
const sections = React.useMemo(() => {
return sectionsFromParent || getFilteredSectionsForCollective(collective, isAdmin, isHostAdmin);
Expand All @@ -478,9 +500,14 @@ const CollectiveNavbar = ({
...callsToAction,
};
const actionsArray = Object.keys(pickBy(callsToAction, Boolean));
const mainAction = getMainAction(collective, actionsArray, LoggedInUser);
const mainAction = getMainAction(collective, actionsArray, LoggedInUser, isNewExpenseFlowEnabled, () =>
setIsSubmitExpenseModalOpen(true),
);
const secondAction =
actionsArray.length === 2 && getMainAction(collective, without(actionsArray, mainAction?.type), LoggedInUser);
actionsArray.length === 2 &&
getMainAction(collective, without(actionsArray, mainAction?.type), LoggedInUser, isNewExpenseFlowEnabled, () =>
setIsSubmitExpenseModalOpen(true),
);
const navbarRef = useRef();
const mainContainerRef = useRef();

Expand Down Expand Up @@ -620,6 +647,7 @@ const CollectiveNavbar = ({
)}
{!loading && (
<CollectiveNavbarActionsMenu
onOpenSubmitExpenseModalClick={() => setIsSubmitExpenseModalOpen(true)}
collective={collective}
callsToAction={callsToAction}
hiddenActionForNonMobile={mainAction?.type}
Expand All @@ -643,6 +671,14 @@ const CollectiveNavbar = ({
)}
</NavbarContentContainer>
</NavBarContainer>
{isSubmitExpenseModalOpen && (
<SubmitExpenseFlow
onClose={() => {
setIsSubmitExpenseModalOpen(false);
}}
submitExpenseTo={collective?.slug}
/>
)}
</Fragment>
);
};
Expand Down
2 changes: 1 addition & 1 deletion components/crowdfunding-redesign/Fundraiser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ const ContentOverview = ({ content }) => {
const doc = parser.parseFromString(content, 'text/html');
const headings = doc.querySelectorAll('h3');
const headingTexts = Array.from(headings).map(h3 => h3.textContent?.trim() || '');
const linkClasses = cva('px-2 font-semibold block hover:text-primary text-sm border-l-[3px]', {
const linkClasses = cva('block border-l-[3px] px-2 text-sm font-semibold hover:text-primary', {
variants: {
active: {
true: 'border-primary/70',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ import { accountExpensesQuery } from './queries';
const ROUTE_PARAMS = ['slug', 'section', 'subpath'];

const SubmittedExpenses = ({ accountSlug }: DashboardSectionProps) => {
const router = useRouter();
const [isExpenseFlowOpen, setIsExpenseFlowOpen] = React.useState(false);
const [duplicateExpenseId, setDuplicateExpenseId] = React.useState(null);
const router = useRouter();
const { LoggedInUser } = useLoggedInUser();

const queryFilter = useQueryFilter({
Expand Down
5 changes: 5 additions & 0 deletions components/edit-collective/sections/EditCollectivePage.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ export const getSettingsQuery = gql`
requiredForCollectiveAdmins
}
EXPENSE_PUBLIC_VENDORS
EXPENSE_POLICIES {
invoicePolicy
receiptPolicy
titlePolicy
}
}
... on AccountWithHost {
host {
Expand Down
Loading
Loading