mirror of
https://github.com/actualbudget/actual.git
synced 2026-07-22 15:19:25 -05:00
* Update bare button colors to use page color variables * Refactor button text color variables in dark theme Updated button text colors to use page text variables. * Refactor button text color variables in light.css Updated button text colors to use page text variables. * Fix indentation for buttonBareText variable * Update button text colors in midnight theme * Refactor Titlebar component styles and props Updated button text color and removed unnecessary color styles. * Fix button text color styling in LoggedInUser component * Correct color style in LoggedInUser Button Fix color style assignment in Button component. * Remove color prop from LoggedInUser component Removed the 'color' prop from LoggedInUser component. * [autofix.ci] apply automated fixes * Update VRT screenshots Auto-generated by VRT workflow PR: #8267 --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
272 lines
7.4 KiB
TypeScript
272 lines
7.4 KiB
TypeScript
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
import type { CSSProperties } from 'react';
|
|
import { Trans, useTranslation } from 'react-i18next';
|
|
import { useLocation } from 'react-router';
|
|
|
|
import { Button } from '@actual-app/components/button';
|
|
import { Menu } from '@actual-app/components/menu';
|
|
import { Popover } from '@actual-app/components/popover';
|
|
import { styles } from '@actual-app/components/styles';
|
|
import { Text } from '@actual-app/components/text';
|
|
import { theme } from '@actual-app/components/theme';
|
|
import { View } from '@actual-app/components/view';
|
|
import { listen } from '@actual-app/core/platform/client/connection';
|
|
import type { RemoteFile, SyncedLocalFile } from '@actual-app/core/types/file';
|
|
import type { TransObjectLiteral } from '@actual-app/core/types/util';
|
|
|
|
import { useAuth } from '#auth/AuthProvider';
|
|
import { Permissions } from '#auth/types';
|
|
import { closeBudget } from '#budgetfiles/budgetfilesSlice';
|
|
import { useMetadataPref } from '#hooks/useMetadataPref';
|
|
import { useNavigate } from '#hooks/useNavigate';
|
|
import { useDispatch, useSelector } from '#redux';
|
|
import { getUserData, signOut } from '#users/usersSlice';
|
|
|
|
import { PrivacyFilter } from './PrivacyFilter';
|
|
import { useMultiuserEnabled, useServerURL } from './ServerContext';
|
|
|
|
type LoggedInUserProps = {
|
|
hideIfNoServer?: boolean;
|
|
style?: CSSProperties;
|
|
};
|
|
|
|
export function LoggedInUser({ hideIfNoServer, style }: LoggedInUserProps) {
|
|
const { t } = useTranslation();
|
|
const dispatch = useDispatch();
|
|
const navigate = useNavigate();
|
|
const userData = useSelector(state => state.user.data);
|
|
const [loading, setLoading] = useState(true);
|
|
const [menuOpen, setMenuOpen] = useState(false);
|
|
const serverUrl = useServerURL();
|
|
const triggerRef = useRef<HTMLButtonElement>(null);
|
|
const [budgetId] = useMetadataPref('id');
|
|
const [cloudFileId] = useMetadataPref('cloudFileId');
|
|
const location = useLocation();
|
|
const { hasPermission } = useAuth();
|
|
const multiuserEnabled = useMultiuserEnabled();
|
|
const allFiles = useSelector(state => state.budgetfiles.allFiles || []);
|
|
const remoteFiles = allFiles.filter(
|
|
f => f.state === 'remote' || f.state === 'synced' || f.state === 'detached',
|
|
) as (SyncedLocalFile | RemoteFile)[];
|
|
const currentFile = remoteFiles.find(f => f.cloudFileId === cloudFileId);
|
|
const hasSyncedPrefs = useSelector(state => state.prefs.synced);
|
|
|
|
const initializeUserData = useCallback(async () => {
|
|
try {
|
|
await dispatch(getUserData());
|
|
} catch (error) {
|
|
console.error('Failed to initialize user data:', error);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}, [dispatch]);
|
|
|
|
useEffect(() => {
|
|
void initializeUserData();
|
|
}, [initializeUserData]);
|
|
|
|
useEffect(() => {
|
|
return listen('sync-event', ({ type }) => {
|
|
if (type === 'start') {
|
|
setLoading(true);
|
|
|
|
return;
|
|
}
|
|
|
|
const shouldReinitialize =
|
|
userData &&
|
|
((type === 'success' && userData.offline) ||
|
|
(type === 'error' && !userData.offline));
|
|
|
|
if (shouldReinitialize) {
|
|
void initializeUserData();
|
|
} else {
|
|
setLoading(false);
|
|
}
|
|
});
|
|
}, [initializeUserData, userData]);
|
|
|
|
async function onCloseBudget() {
|
|
await dispatch(closeBudget());
|
|
}
|
|
|
|
async function onChangePassword() {
|
|
await onCloseBudget();
|
|
void navigate('/change-password');
|
|
}
|
|
|
|
const handleMenuSelect = async (type: string) => {
|
|
setMenuOpen(false);
|
|
|
|
switch (type) {
|
|
case 'change-password':
|
|
void onChangePassword();
|
|
break;
|
|
case 'sign-in':
|
|
await onCloseBudget();
|
|
void navigate('/login');
|
|
break;
|
|
case 'user-access':
|
|
void navigate('/user-access');
|
|
break;
|
|
case 'user-directory':
|
|
void navigate('/user-directory');
|
|
break;
|
|
case 'index':
|
|
void navigate('/');
|
|
break;
|
|
case 'sign-out':
|
|
void dispatch(signOut());
|
|
break;
|
|
case 'config-server':
|
|
await onCloseBudget();
|
|
void navigate('/config-server');
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
};
|
|
|
|
function serverMessage() {
|
|
if (!serverUrl) {
|
|
return t('No server');
|
|
}
|
|
|
|
if (userData?.offline) {
|
|
return t('Server offline');
|
|
}
|
|
|
|
return t('Server online');
|
|
}
|
|
|
|
if (hideIfNoServer && !serverUrl) return null;
|
|
|
|
if (loading && serverUrl) {
|
|
return (
|
|
<Text
|
|
style={{
|
|
color: theme.pageTextLight,
|
|
fontStyle: 'italic',
|
|
...styles.delayedFadeIn,
|
|
...style,
|
|
}}
|
|
>
|
|
<Trans>Connecting...</Trans>
|
|
</Text>
|
|
);
|
|
}
|
|
|
|
type MenuItem = {
|
|
name: string;
|
|
text: string;
|
|
};
|
|
|
|
const getMenuItems = (): (MenuItem | typeof Menu.line)[] => {
|
|
const isAdmin = hasPermission(Permissions.ADMINISTRATOR);
|
|
|
|
const baseMenu: (MenuItem | typeof Menu.line)[] = [];
|
|
if (
|
|
serverUrl &&
|
|
!userData?.offline &&
|
|
userData?.loginMethod === 'password'
|
|
) {
|
|
baseMenu.push({ name: 'change-password', text: t('Change password') });
|
|
}
|
|
if (serverUrl) {
|
|
baseMenu.push({ name: 'sign-out', text: t('Sign out') });
|
|
}
|
|
baseMenu.push({
|
|
name: 'config-server',
|
|
text: serverUrl ? t('Change server URL') : t('Start using a server'),
|
|
});
|
|
|
|
const adminMenu: (MenuItem | typeof Menu.line)[] = [];
|
|
if (multiuserEnabled && isAdmin) {
|
|
if (!budgetId && location.pathname !== '/') {
|
|
adminMenu.push({ name: 'index', text: t('View file list') });
|
|
} else if (
|
|
serverUrl &&
|
|
!userData?.offline &&
|
|
location.pathname !== '/user-directory'
|
|
) {
|
|
adminMenu.push({ name: 'user-directory', text: t('User Directory') });
|
|
}
|
|
}
|
|
|
|
if (
|
|
multiuserEnabled &&
|
|
((currentFile && userData && currentFile.owner === userData.userId) ||
|
|
isAdmin) &&
|
|
serverUrl &&
|
|
!userData?.offline &&
|
|
cloudFileId &&
|
|
location.pathname !== '/user-access'
|
|
) {
|
|
adminMenu.push({
|
|
name: 'user-access',
|
|
text: t('User Access Management'),
|
|
});
|
|
}
|
|
|
|
if (adminMenu.length > 0) {
|
|
adminMenu.push(Menu.line);
|
|
}
|
|
|
|
return [...adminMenu, ...baseMenu];
|
|
};
|
|
|
|
return (
|
|
<View style={{ flexDirection: 'row', alignItems: 'center', ...style }}>
|
|
<Button ref={triggerRef} variant="bare" onPress={() => setMenuOpen(true)}>
|
|
{serverMessage()}
|
|
</Button>
|
|
{!loading &&
|
|
multiuserEnabled &&
|
|
userData &&
|
|
userData?.displayName &&
|
|
!hasSyncedPrefs && (
|
|
<small>
|
|
(
|
|
<Trans>
|
|
logged in as:{' '}
|
|
<span>
|
|
{{ userName: userData?.displayName } as TransObjectLiteral}
|
|
</span>
|
|
</Trans>
|
|
)
|
|
</small>
|
|
)}
|
|
{!loading &&
|
|
multiuserEnabled &&
|
|
userData &&
|
|
userData?.displayName &&
|
|
hasSyncedPrefs && (
|
|
<small>
|
|
(
|
|
<Trans>
|
|
logged in as:{' '}
|
|
<span>
|
|
<PrivacyFilter>
|
|
{{ userName: userData?.displayName } as TransObjectLiteral}
|
|
</PrivacyFilter>
|
|
</span>
|
|
</Trans>
|
|
)
|
|
</small>
|
|
)}
|
|
|
|
<Popover
|
|
offset={8}
|
|
triggerRef={triggerRef}
|
|
isOpen={menuOpen}
|
|
onOpenChange={() => setMenuOpen(false)}
|
|
>
|
|
<Menu
|
|
onMenuSelect={handleMenuSelect}
|
|
items={getMenuItems().filter(Boolean)}
|
|
/>
|
|
</Popover>
|
|
</View>
|
|
);
|
|
}
|