Mobile create account (#1853)
* Mobile create account * Release notes * Update add button style * Add back desktop page header padding * Empty accounts handling * decimal keyboard on CreateLocalAccount balance input * VRT updates
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
@@ -6,13 +6,21 @@ import { theme, styles, type CSSProperties } from '../style';
|
||||
import Text from './common/Text';
|
||||
import View from './common/View';
|
||||
|
||||
function PageTitle({
|
||||
name,
|
||||
style,
|
||||
}: {
|
||||
type PageHeaderProps = {
|
||||
name: ReactNode;
|
||||
style?: CSSProperties;
|
||||
}) {
|
||||
leftContent?: ReactNode;
|
||||
rightContent?: ReactNode;
|
||||
};
|
||||
|
||||
const HEADER_HEIGHT = 50;
|
||||
|
||||
function PageHeader({
|
||||
name,
|
||||
style,
|
||||
leftContent,
|
||||
rightContent,
|
||||
}: PageHeaderProps) {
|
||||
const { isNarrowWidth } = useResponsive();
|
||||
|
||||
if (isNarrowWidth) {
|
||||
@@ -23,16 +31,42 @@ function PageTitle({
|
||||
backgroundColor: theme.mobilePageBackground,
|
||||
color: theme.mobileModalText,
|
||||
flexDirection: 'row',
|
||||
flex: '1 0 auto',
|
||||
fontSize: 18,
|
||||
fontWeight: 500,
|
||||
height: 50,
|
||||
justifyContent: 'center',
|
||||
overflowY: 'auto',
|
||||
flexShrink: 0,
|
||||
height: HEADER_HEIGHT,
|
||||
...style,
|
||||
}}
|
||||
>
|
||||
{name}
|
||||
<View
|
||||
style={{
|
||||
flexBasis: '25%',
|
||||
justifyContent: 'flex-start',
|
||||
flexDirection: 'row',
|
||||
}}
|
||||
>
|
||||
{leftContent}
|
||||
</View>
|
||||
<View
|
||||
style={{
|
||||
alignItems: 'center',
|
||||
flexDirection: 'row',
|
||||
flexBasis: '50%',
|
||||
fontSize: 18,
|
||||
fontWeight: 500,
|
||||
justifyContent: 'center',
|
||||
overflowY: 'auto',
|
||||
}}
|
||||
>
|
||||
{name}
|
||||
</View>
|
||||
<View
|
||||
style={{
|
||||
flexBasis: '25%',
|
||||
justifyContent: 'flex-end',
|
||||
flexDirection: 'row',
|
||||
}}
|
||||
>
|
||||
{rightContent}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -51,25 +85,33 @@ function PageTitle({
|
||||
);
|
||||
}
|
||||
|
||||
type PageProps = {
|
||||
title: ReactNode;
|
||||
titleStyle?: CSSProperties;
|
||||
headerLeftContent?: ReactNode;
|
||||
headerRightContent?: ReactNode;
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
export function Page({
|
||||
title,
|
||||
children,
|
||||
titleStyle,
|
||||
}: {
|
||||
title: ReactNode;
|
||||
children: ReactNode;
|
||||
titleStyle?: CSSProperties;
|
||||
}) {
|
||||
headerLeftContent,
|
||||
headerRightContent,
|
||||
children,
|
||||
}: PageProps) {
|
||||
let { isNarrowWidth } = useResponsive();
|
||||
let HORIZONTAL_PADDING = isNarrowWidth ? 10 : 20;
|
||||
|
||||
return (
|
||||
<View style={isNarrowWidth ? undefined : styles.page}>
|
||||
<PageTitle
|
||||
<PageHeader
|
||||
name={title}
|
||||
leftContent={headerLeftContent}
|
||||
rightContent={headerRightContent}
|
||||
style={{
|
||||
...titleStyle,
|
||||
paddingInline: HORIZONTAL_PADDING,
|
||||
...(!isNarrowWidth && { paddingInline: HORIZONTAL_PADDING }),
|
||||
}}
|
||||
/>
|
||||
<View
|
||||
|
||||
@@ -106,109 +106,7 @@ export default function AccountDetails({
|
||||
top: 0,
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
flexShrink: 0,
|
||||
height: BUDGET_HEADER_HEIGHT,
|
||||
width: '100%',
|
||||
backgroundColor: theme.mobileHeaderBackground,
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
width: LEFT_RIGHT_FLEX_WIDTH,
|
||||
flexDirection: 'row',
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
type="bare"
|
||||
style={{
|
||||
color: theme.mobileHeaderText,
|
||||
justifyContent: 'center',
|
||||
margin: 10,
|
||||
paddingLeft: 5,
|
||||
paddingRight: 3,
|
||||
}}
|
||||
hoveredStyle={{
|
||||
color: theme.mobileHeaderText,
|
||||
background: theme.mobileHeaderTextHover,
|
||||
}}
|
||||
>
|
||||
<Link
|
||||
to={-1}
|
||||
style={{
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
textDecoration: 'none',
|
||||
}}
|
||||
>
|
||||
<CheveronLeft
|
||||
style={{ width: 30, height: 30, margin: -10, marginLeft: -5 }}
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
...styles.text,
|
||||
fontWeight: 500,
|
||||
marginLeft: 5,
|
||||
marginRight: 5,
|
||||
}}
|
||||
>
|
||||
Back
|
||||
</Text>
|
||||
</Link>
|
||||
</Button>
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
fontSize: 16,
|
||||
fontWeight: 500,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
color: theme.mobileHeaderText,
|
||||
}}
|
||||
role="heading"
|
||||
>
|
||||
{account.name}
|
||||
</View>
|
||||
|
||||
<View
|
||||
style={{
|
||||
width: LEFT_RIGHT_FLEX_WIDTH,
|
||||
flexDirection: 'row',
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
}}
|
||||
/>
|
||||
<ButtonLink
|
||||
to="transactions/new"
|
||||
type="bare"
|
||||
aria-label="Add Transaction"
|
||||
style={{
|
||||
justifyContent: 'center',
|
||||
padding: 10,
|
||||
margin: 10,
|
||||
color: theme.mobileHeaderText,
|
||||
}}
|
||||
hoveredStyle={{
|
||||
color: theme.mobileHeaderText,
|
||||
background: theme.mobileHeaderTextHover,
|
||||
}}
|
||||
activeStyle={{ background: 'transparent' }}
|
||||
>
|
||||
<Add width={20} height={20} style={{ margin: -5 }} />
|
||||
</ButtonLink>
|
||||
</View>
|
||||
</View>
|
||||
<AccountDetailsHeader account={account} />
|
||||
<Label title="BALANCE" style={{ marginTop: 10 }} />
|
||||
<CellValue
|
||||
binding={balance}
|
||||
@@ -245,3 +143,112 @@ export default function AccountDetails({
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
function AccountDetailsHeader({ account }) {
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
flexShrink: 0,
|
||||
height: BUDGET_HEADER_HEIGHT,
|
||||
width: '100%',
|
||||
backgroundColor: theme.mobileHeaderBackground,
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
width: LEFT_RIGHT_FLEX_WIDTH,
|
||||
flexDirection: 'row',
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
type="bare"
|
||||
style={{
|
||||
color: theme.mobileHeaderText,
|
||||
justifyContent: 'center',
|
||||
margin: 10,
|
||||
paddingLeft: 5,
|
||||
paddingRight: 3,
|
||||
}}
|
||||
hoveredStyle={{
|
||||
color: theme.mobileHeaderText,
|
||||
background: theme.mobileHeaderTextHover,
|
||||
}}
|
||||
>
|
||||
<Link
|
||||
to={-1}
|
||||
style={{
|
||||
...styles.noTapHighlight,
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
textDecoration: 'none',
|
||||
}}
|
||||
>
|
||||
<CheveronLeft
|
||||
style={{ width: 30, height: 30, margin: -10, marginLeft: -5 }}
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
...styles.text,
|
||||
fontWeight: 500,
|
||||
marginLeft: 5,
|
||||
marginRight: 5,
|
||||
}}
|
||||
>
|
||||
Back
|
||||
</Text>
|
||||
</Link>
|
||||
</Button>
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
fontSize: 16,
|
||||
fontWeight: 500,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
color: theme.mobileHeaderText,
|
||||
}}
|
||||
role="heading"
|
||||
>
|
||||
{account.name}
|
||||
</View>
|
||||
|
||||
<View
|
||||
style={{
|
||||
width: LEFT_RIGHT_FLEX_WIDTH,
|
||||
flexDirection: 'row',
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
}}
|
||||
/>
|
||||
<ButtonLink
|
||||
to="transactions/new"
|
||||
type="bare"
|
||||
aria-label="Add Transaction"
|
||||
style={{
|
||||
justifyContent: 'center',
|
||||
padding: 10,
|
||||
margin: 10,
|
||||
color: theme.mobileHeaderText,
|
||||
}}
|
||||
hoveredStyle={{
|
||||
color: theme.mobileHeaderText,
|
||||
background: theme.mobileHeaderTextHover,
|
||||
}}
|
||||
activeStyle={{ background: 'transparent' }}
|
||||
>
|
||||
<Add width={20} height={20} style={{ margin: -5 }} />
|
||||
</ButtonLink>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import { useActions } from '../../hooks/useActions';
|
||||
import useCategories from '../../hooks/useCategories';
|
||||
import useNavigate from '../../hooks/useNavigate';
|
||||
import { useSetThemeColor } from '../../hooks/useSetThemeColor';
|
||||
import Add from '../../icons/v1/Add';
|
||||
import { theme, styles } from '../../style';
|
||||
import Button from '../common/Button';
|
||||
import Text from '../common/Text';
|
||||
@@ -124,7 +125,7 @@ function AccountCard({ account, updated, getBalanceQuery, onSelect }) {
|
||||
);
|
||||
}
|
||||
|
||||
function EmptyMessage({ onAdd }) {
|
||||
function EmptyMessage() {
|
||||
return (
|
||||
<View style={{ flex: 1, padding: 30 }}>
|
||||
<Text style={styles.text}>
|
||||
@@ -132,22 +133,6 @@ function EmptyMessage({ onAdd }) {
|
||||
account to automatically download transactions, or manage it locally
|
||||
yourself.
|
||||
</Text>
|
||||
|
||||
<Button
|
||||
type="primary"
|
||||
style={{ marginTop: 20, alignSelf: 'center' }}
|
||||
onClick={() =>
|
||||
alert(
|
||||
'Account creation is not supported on mobile on the self-hosted service yet',
|
||||
)
|
||||
}
|
||||
>
|
||||
Add Account
|
||||
</Button>
|
||||
|
||||
<Text style={{ marginTop: 20, color: theme.pageTextLight }}>
|
||||
In the future, you can add accounts using the add button in the header.
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -160,16 +145,14 @@ function AccountList({
|
||||
getOffBudgetBalance,
|
||||
onAddAccount,
|
||||
onSelectAccount,
|
||||
onSync,
|
||||
}) {
|
||||
const { syncAndDownload } = useActions();
|
||||
|
||||
const budgetedAccounts = accounts.filter(account => account.offbudget === 0);
|
||||
const offbudgetAccounts = accounts.filter(account => account.offbudget === 1);
|
||||
|
||||
// If there are no accounts, show a helpful message
|
||||
if (accounts.length === 0) {
|
||||
return <EmptyMessage onAdd={onAddAccount} />;
|
||||
}
|
||||
const noBackgroundColorStyle = {
|
||||
backgroundColor: 'transparent',
|
||||
color: 'white',
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1, backgroundColor: theme.mobilePageBackground }}>
|
||||
@@ -180,9 +163,27 @@ function AccountList({
|
||||
color: theme.mobileHeaderText,
|
||||
fontSize: 16,
|
||||
}}
|
||||
headerRightContent={
|
||||
<Button
|
||||
type="bare"
|
||||
style={{
|
||||
paddingLeft: 12,
|
||||
paddingRight: 12,
|
||||
...noBackgroundColorStyle,
|
||||
}}
|
||||
activeStyle={noBackgroundColorStyle}
|
||||
hoveredStyle={noBackgroundColorStyle}
|
||||
onClick={onAddAccount}
|
||||
>
|
||||
<Add width={20} height={20} />
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<PullToRefresh onRefresh={syncAndDownload}>
|
||||
<AccountHeader name="For Budget" amount={getOnBudgetBalance()} />
|
||||
{accounts.length === 0 && <EmptyMessage />}
|
||||
<PullToRefresh onRefresh={onSync}>
|
||||
{budgetedAccounts.length > 0 && (
|
||||
<AccountHeader name="For Budget" amount={getOnBudgetBalance()} />
|
||||
)}
|
||||
{budgetedAccounts.map(acct => (
|
||||
<AccountCard
|
||||
account={acct}
|
||||
@@ -193,11 +194,13 @@ function AccountList({
|
||||
/>
|
||||
))}
|
||||
|
||||
<AccountHeader
|
||||
name="Off budget"
|
||||
amount={getOffBudgetBalance()}
|
||||
style={{ marginTop: 30 }}
|
||||
/>
|
||||
{offbudgetAccounts.length > 0 && (
|
||||
<AccountHeader
|
||||
name="Off budget"
|
||||
amount={getOffBudgetBalance()}
|
||||
style={{ marginTop: 30 }}
|
||||
/>
|
||||
)}
|
||||
{offbudgetAccounts.map(acct => (
|
||||
<AccountCard
|
||||
account={acct}
|
||||
@@ -225,7 +228,7 @@ export default function Accounts() {
|
||||
);
|
||||
|
||||
const { list: categories } = useCategories();
|
||||
let { getAccounts } = useActions();
|
||||
let { getAccounts, replaceModal, syncAndDownload } = useActions();
|
||||
|
||||
const transactions = useState({});
|
||||
const navigate = useNavigate();
|
||||
@@ -258,9 +261,10 @@ export default function Accounts() {
|
||||
getBalanceQuery={queries.accountBalance}
|
||||
getOnBudgetBalance={queries.budgetedAccountBalance}
|
||||
getOffBudgetBalance={queries.offbudgetAccountBalance}
|
||||
onAddAccount={() => {}} // () => navigate('AddAccountModal')
|
||||
onAddAccount={() => replaceModal('add-account')}
|
||||
onSelectAccount={onSelectAccount}
|
||||
onSelectTransaction={onSelectTransaction}
|
||||
onSync={syncAndDownload}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -132,6 +132,7 @@ function CreateLocalAccount({ modalProps, actions }: CreateLocalAccountProps) {
|
||||
<InlineField label="Balance" width="75%">
|
||||
<Input
|
||||
name="balance"
|
||||
inputMode="decimal"
|
||||
value={balance}
|
||||
onChange={event => setBalance(event.target.value)}
|
||||
onBlur={event => {
|
||||
|
||||
@@ -362,6 +362,7 @@ class TransactionEditInner extends PureComponent {
|
||||
<Button
|
||||
type="bare"
|
||||
style={{
|
||||
...styles.noTapHighlight,
|
||||
color: theme.mobileHeaderText,
|
||||
justifyContent: 'center',
|
||||
margin: 10,
|
||||
|
||||
6
upcoming-release-notes/1853.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
category: Enhancements
|
||||
authors: [joel-jeremy]
|
||||
---
|
||||
|
||||
Mobile create account.
|
||||