Updated profile menu. Fixed dashboard.

This commit is contained in:
Mike Cao 2024-02-02 21:06:55 -08:00
parent a91b9c9716
commit 400657d59e
12 changed files with 73 additions and 70 deletions

View File

@ -75,7 +75,7 @@ const redirects = [
}, },
{ {
source: '/teams/:id', source: '/teams/:id',
destination: '/teams/:id/websites', destination: '/teams/:id/dashboard',
permanent: true, permanent: true,
}, },
{ {

View File

@ -7,39 +7,30 @@ import WebsiteChartList from '../websites/[websiteId]/WebsiteChartList';
import DashboardSettingsButton from 'app/(main)/dashboard/DashboardSettingsButton'; import DashboardSettingsButton from 'app/(main)/dashboard/DashboardSettingsButton';
import DashboardEdit from 'app/(main)/dashboard/DashboardEdit'; import DashboardEdit from 'app/(main)/dashboard/DashboardEdit';
import EmptyPlaceholder from 'components/common/EmptyPlaceholder'; import EmptyPlaceholder from 'components/common/EmptyPlaceholder';
import { useApi } from 'components/hooks'; import { useMessages, useLocale, useTeamContext, useWebsites } from 'components/hooks';
import useDashboard from 'store/dashboard'; import useDashboard from 'store/dashboard';
import { useMessages, useLocale, useLogin, useFilterQuery } from 'components/hooks';
export function Dashboard() { export function Dashboard() {
const { formatMessage, labels, messages } = useMessages(); const { formatMessage, labels, messages } = useMessages();
const { user } = useLogin(); const { teamId } = useTeamContext();
const { showCharts, editing } = useDashboard(); const { showCharts, editing } = useDashboard();
const { dir } = useLocale(); const { dir } = useLocale();
const { get } = useApi();
const pageSize = 10; const pageSize = 10;
const { query, params, setParams, result } = useFilterQuery({ const { result, query, params, setParams } = useWebsites({}, { pageSize });
queryKey: ['dashboard:websites'], const { page } = params;
queryFn: (params: any) => { const hasData = !!result?.data;
return get(`/users/${user.id}/websites`, { ...params, includeTeams: true, pageSize });
},
});
const handlePageChange = (page: number) => { const handlePageChange = (page: number) => {
setParams({ ...params, page }); setParams({ ...params, page });
}; };
const { data, count } = result || {};
const hasData = !!(data as any)?.length;
const { page } = params;
if (query.isLoading) { if (query.isLoading) {
return <Loading />; return <Loading />;
} }
return ( return (
<> <section style={{ marginBottom: 60 }}>
<PageHeader title={formatMessage(labels.dashboard)}> <PageHeader title={formatMessage(labels.dashboard)}>
{!editing && hasData && <DashboardSettingsButton />} {!editing && hasData && <DashboardSettingsButton />}
</PageHeader> </PageHeader>
@ -57,21 +48,25 @@ export function Dashboard() {
)} )}
{hasData && ( {hasData && (
<> <>
{editing && <DashboardEdit />} {editing && <DashboardEdit teamId={teamId} />}
{!editing && ( {!editing && (
<> <>
<WebsiteChartList websites={data as any} showCharts={showCharts} limit={pageSize} /> <WebsiteChartList
websites={result?.data as any}
showCharts={showCharts}
limit={pageSize}
/>
<Pager <Pager
page={page} page={page}
pageSize={pageSize} pageSize={pageSize}
count={count} count={result?.count}
onPageChange={handlePageChange} onPageChange={handlePageChange}
/> />
</> </>
)} )}
</> </>
)} )}
</> </section>
); );
} }

View File

@ -2,31 +2,30 @@
import { useState, useMemo } from 'react'; import { useState, useMemo } from 'react';
import { DragDropContext, Draggable, Droppable } from 'react-beautiful-dnd'; import { DragDropContext, Draggable, Droppable } from 'react-beautiful-dnd';
import classNames from 'classnames'; import classNames from 'classnames';
import { Button } from 'react-basics'; import { Button, Loading } from 'react-basics';
import { firstBy } from 'thenby'; import { firstBy } from 'thenby';
import useDashboard, { saveDashboard } from 'store/dashboard'; import useDashboard, { saveDashboard } from 'store/dashboard';
import { useMessages } from 'components/hooks'; import { useMessages, useWebsites } from 'components/hooks';
import { useApi } from 'components/hooks';
import styles from './DashboardEdit.module.css'; import styles from './DashboardEdit.module.css';
const dragId = 'dashboard-website-ordering'; const DRAG_ID = 'dashboard-website-ordering';
export function DashboardEdit() { export function DashboardEdit({ teamId }: { teamId: string }) {
const settings = useDashboard(); const settings = useDashboard();
const { websiteOrder } = settings; const { websiteOrder } = settings;
const { formatMessage, labels } = useMessages(); const { formatMessage, labels } = useMessages();
const [order, setOrder] = useState(websiteOrder || []); const [order, setOrder] = useState(websiteOrder || []);
const { get, useQuery } = useApi(); const {
const { data: result } = useQuery({ result,
queryKey: ['websites'], query: { isLoading },
queryFn: () => get('/websites'), } = useWebsites({ teamId });
});
const { data: websites } = result || {}; const websites = result?.data;
const ordered = useMemo(() => { const ordered = useMemo(() => {
if (websites) { if (websites) {
return websites return websites
.map(website => ({ ...website, order: order.indexOf(website.id) })) .map((website: { id: any }) => ({ ...website, order: order.indexOf(website.id) }))
.sort(firstBy('order')); .sort(firstBy('order'));
} }
return []; return [];
@ -57,6 +56,10 @@ export function DashboardEdit() {
setOrder([]); setOrder([]);
} }
if (isLoading) {
return <Loading />;
}
return ( return (
<> <>
<div className={styles.buttons}> <div className={styles.buttons}>
@ -72,7 +75,7 @@ export function DashboardEdit() {
</div> </div>
<div className={styles.dragActive}> <div className={styles.dragActive}>
<DragDropContext onDragEnd={handleWebsiteDrag}> <DragDropContext onDragEnd={handleWebsiteDrag}>
<Droppable droppableId={dragId}> <Droppable droppableId={DRAG_ID}>
{(provided, snapshot) => ( {(provided, snapshot) => (
<div <div
{...provided.droppableProps} {...provided.droppableProps}
@ -80,7 +83,7 @@ export function DashboardEdit() {
style={{ marginBottom: snapshot.isDraggingOver ? 260 : null }} style={{ marginBottom: snapshot.isDraggingOver ? 260 : null }}
> >
{ordered.map(({ id, name, domain }, index) => ( {ordered.map(({ id, name, domain }, index) => (
<Draggable key={id} draggableId={`${dragId}-${id}`} index={index}> <Draggable key={id} draggableId={`${DRAG_ID}-${id}`} index={index}>
{(provided, snapshot) => ( {(provided, snapshot) => (
<div <div
ref={provided.innerRef} ref={provided.innerRef}

View File

@ -18,5 +18,4 @@
min-height: 0; min-height: 0;
height: calc(100vh - 60px); height: calc(100vh - 60px);
overflow-y: auto; overflow-y: auto;
padding-bottom: 60px;
} }

View File

@ -3,7 +3,7 @@ import { FormRow } from 'react-basics';
import { parseDateRange } from 'lib/date'; import { parseDateRange } from 'lib/date';
import DateFilter from 'components/input/DateFilter'; import DateFilter from 'components/input/DateFilter';
import WebsiteSelect from 'components/input/WebsiteSelect'; import WebsiteSelect from 'components/input/WebsiteSelect';
import { useLogin, useMessages, useTeamContext } from 'components/hooks'; import { useMessages, useTeamContext } from 'components/hooks';
import { ReportContext } from './Report'; import { ReportContext } from './Report';
export interface BaseParametersProps { export interface BaseParametersProps {
@ -21,7 +21,6 @@ export function BaseParameters({
}: BaseParametersProps) { }: BaseParametersProps) {
const { report, updateReport } = useContext(ReportContext); const { report, updateReport } = useContext(ReportContext);
const { formatMessage, labels } = useMessages(); const { formatMessage, labels } = useMessages();
const { user } = useLogin();
const { teamId } = useTeamContext(); const { teamId } = useTeamContext();
const { parameters } = report || {}; const { parameters } = report || {};
@ -41,12 +40,7 @@ export function BaseParameters({
{showWebsiteSelect && ( {showWebsiteSelect && (
<FormRow label={formatMessage(labels.website)}> <FormRow label={formatMessage(labels.website)}>
{allowWebsiteSelect && ( {allowWebsiteSelect && (
<WebsiteSelect <WebsiteSelect teamId={teamId} websiteId={websiteId} onSelect={handleWebsiteSelect} />
userId={user.id}
teamId={teamId}
websiteId={websiteId}
onSelect={handleWebsiteSelect}
/>
)} )}
</FormRow> </FormRow>
)} )}

View File

@ -12,7 +12,7 @@ export default function SettingsLayout({ children }) {
const { teamId, renderTeamUrl } = useTeamContext(); const { teamId, renderTeamUrl } = useTeamContext();
const items = [ const items = [
{ teamId && {
key: 'team', key: 'team',
label: formatMessage(labels.team), label: formatMessage(labels.team),
url: renderTeamUrl('/settings/team'), url: renderTeamUrl('/settings/team'),

View File

@ -0,0 +1,3 @@
import Page from 'app/(main)/dashboard/page';
export default Page;

View File

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512.013 512.013" style="enable-background:new 0 0 512.013 512.013" xml:space="preserve"><path d="m372.653 244.726 22.56 22.56 112-112c6.204-6.241 6.204-16.319 0-22.56l-112-112-22.56 22.72 84.8 84.64H.013v32h457.44l-84.8 84.64zM512.013 352.086H54.573l84.8-84.64-22.72-22.72-112 112c-6.204 6.241-6.204 16.319 0 22.56l112 112 22.56-22.56-84.64-84.64h457.44v-32z"/></svg> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512.013 512.013" style="enable-background:new 0 0 512.013 512.013" xml:space="preserve"><path d="m372.653 244.726 22.56 22.56 112-112c6.204-6.241 6.204-16.319 0-22.56l-112-112-22.56 22.72 84.8 84.64H.013v32h457.44l-84.8 84.64zm139.36 107.36H54.573l84.8-84.64-22.72-22.72-112 112c-6.204 6.241-6.204 16.319 0 22.56l112 112 22.56-22.56-84.64-84.64h457.44v-32z"/></svg>

Before

Width:  |  Height:  |  Size: 420 B

After

Width:  |  Height:  |  Size: 418 B

View File

@ -1 +1 @@
<svg height="512" viewBox="0 0 508.467 508.467" width="512" xmlns="http://www.w3.org/2000/svg"><path d="M426.815 239.006c-11.722-11.724-30.702-11.729-42.427-.001L267.67 355.723c-53.811 53.809-142.478 19.197-140.68-54.511.547-22.415 9.826-43.738 26.129-60.041l116.717-116.717c11.724-11.722 11.728-30.702 0-42.427l-46.668-46.669c-11.725-11.725-30.702-11.726-42.427 0L60.629 155.47C21.579 194.52.047 246.44 0 301.665c-.093 110.827 88.182 206.288 206.244 206.394 56.778 0 109.204-21.924 148.29-61.01l118.948-118.948c11.724-11.722 11.728-30.702 0-42.427zM201.954 56.572l46.669 46.669-58.455 58.456-46.669-46.669zm131.367 369.264c-69.043 69.043-182.868 70.02-251.708.933-68.763-69.009-68.66-181.196.229-250.086l40.443-40.443 46.669 46.669-37.049 37.049c-45.115 45.112-46.916 116.85-3.395 160.371 43.279 43.279 115.221 41.756 160.372-3.394l37.049-37.049 46.669 46.669zm60.494-60.493-46.669-46.669 58.456-58.456 46.669 46.669zM379.357 95.099c15.199 3.839 30.418 19.07 34.336 34.192 2.089 8.058 10.303 12.828 18.283 10.758 8.02-2.078 12.836-10.264 10.758-18.283-6.651-25.662-30.176-49.223-56.03-55.753-8.032-2.027-16.188 2.838-18.217 10.869-2.029 8.032 2.837 16.189 10.87 18.217zM507.984 102.124C495.968 55.749 452.769 12.62 406.239.868c-8.032-2.027-16.188 2.838-18.217 10.869-2.029 8.032 2.838 16.188 10.87 18.217 35.882 9.063 70.769 43.871 80.051 79.695 2.088 8.058 10.304 12.828 18.283 10.758 8.02-2.078 12.836-10.263 10.758-18.283z"/></svg> <svg height="512" viewBox="0 0 508.467 508.467" width="512" xmlns="http://www.w3.org/2000/svg"><path d="M426.815 239.006c-11.722-11.724-30.702-11.729-42.427-.001L267.67 355.723c-53.811 53.809-142.478 19.197-140.68-54.511.547-22.415 9.826-43.738 26.129-60.041l116.717-116.717c11.724-11.722 11.728-30.702 0-42.427l-46.668-46.669c-11.725-11.725-30.702-11.726-42.427 0L60.629 155.47C21.579 194.52.047 246.44 0 301.665c-.093 110.827 88.182 206.288 206.244 206.394 56.778 0 109.204-21.924 148.29-61.01l118.948-118.948c11.724-11.722 11.728-30.702 0-42.427zM201.954 56.572l46.669 46.669-58.455 58.456-46.669-46.669zm131.367 369.264c-69.043 69.043-182.868 70.02-251.708.933-68.763-69.009-68.66-181.196.229-250.086l40.443-40.443 46.669 46.669-37.049 37.049c-45.115 45.112-46.916 116.85-3.395 160.371 43.279 43.279 115.221 41.756 160.372-3.394l37.049-37.049 46.669 46.669zm60.494-60.493-46.669-46.669 58.456-58.456 46.669 46.669zM379.357 95.099c15.199 3.839 30.418 19.07 34.336 34.192 2.089 8.058 10.303 12.828 18.283 10.758 8.02-2.078 12.836-10.264 10.758-18.283-6.651-25.662-30.176-49.223-56.03-55.753-8.032-2.027-16.188 2.838-18.217 10.869-2.029 8.032 2.837 16.189 10.87 18.217zm128.627 7.025C495.968 55.749 452.769 12.62 406.239.868c-8.032-2.027-16.188 2.838-18.217 10.869-2.029 8.032 2.838 16.188 10.87 18.217 35.882 9.063 70.769 43.871 80.051 79.695 2.088 8.058 10.304 12.828 18.283 10.758 8.02-2.078 12.836-10.263 10.758-18.283z"/></svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M224 256c70.7 0 128-57.3 128-128S294.7 0 224 0 96 57.3 96 128s57.3 128 128 128zm89.6 32h-16.7c-22.2 10.2-46.9 16-72.9 16s-50.6-5.8-72.9-16h-16.7C60.2 288 0 348.2 0 422.4V464c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48v-41.6c0-74.2-60.2-134.4-134.4-134.4z"/></svg> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" style="enable-background:new 0 0 512 512" xml:space="preserve"><path d="M256 0c-74.439 0-135 60.561-135 135s60.561 135 135 135 135-60.561 135-135S330.439 0 256 0zM423.966 358.195C387.006 320.667 338.009 300 286 300h-60c-52.008 0-101.006 20.667-137.966 58.195C51.255 395.539 31 444.833 31 497c0 8.284 6.716 15 15 15h420c8.284 0 15-6.716 15-15 0-52.167-20.255-101.461-57.034-138.805z"/></svg>

Before

Width:  |  Height:  |  Size: 336 B

After

Width:  |  Height:  |  Size: 452 B

View File

@ -1,5 +1,6 @@
import useApi from './useApi'; import { useApi } from './useApi';
import useFilterQuery from './useFilterQuery'; import { useFilterQuery } from './useFilterQuery';
import { useLogin } from './useLogin';
import useCache from 'store/cache'; import useCache from 'store/cache';
export function useWebsites( export function useWebsites(
@ -7,17 +8,17 @@ export function useWebsites(
params?: { [key: string]: string | number }, params?: { [key: string]: string | number },
) { ) {
const { get } = useApi(); const { get } = useApi();
const { user } = useLogin();
const modified = useCache((state: any) => state?.websites); const modified = useCache((state: any) => state?.websites);
return useFilterQuery({ return useFilterQuery({
queryKey: ['websites', { userId, teamId, modified, ...params }], queryKey: ['websites', { userId, teamId, modified, ...params }],
queryFn: (data: any) => { queryFn: (data: any) => {
return get(teamId ? `/teams/${teamId}/websites` : `/users/${userId}/websites`, { return get(teamId ? `/teams/${teamId}/websites` : `/users/${userId || user.id}/websites`, {
...data, ...data,
...params, ...params,
}); });
}, },
enabled: !!(userId || teamId),
}); });
} }

View File

@ -1,3 +1,4 @@
import { Key } from 'react';
import { Icon, Button, PopupTrigger, Popup, Menu, Item, Text } from 'react-basics'; import { Icon, Button, PopupTrigger, Popup, Menu, Item, Text } from 'react-basics';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import Icons from 'components/icons'; import Icons from 'components/icons';
@ -6,6 +7,7 @@ import { useLogin } from 'components/hooks';
import { useLocale } from 'components/hooks'; import { useLocale } from 'components/hooks';
import { CURRENT_VERSION } from 'lib/constants'; import { CURRENT_VERSION } from 'lib/constants';
import styles from './ProfileButton.module.css'; import styles from './ProfileButton.module.css';
import Avatar from 'components/common/Avatar';
export function ProfileButton() { export function ProfileButton() {
const { formatMessage, labels } = useMessages(); const { formatMessage, labels } = useMessages();
@ -14,13 +16,14 @@ export function ProfileButton() {
const { dir } = useLocale(); const { dir } = useLocale();
const cloudMode = Boolean(process.env.cloudMode); const cloudMode = Boolean(process.env.cloudMode);
const handleSelect = key => { const handleSelect = (key: Key, close: () => void) => {
if (key === 'profile') { if (key === 'profile') {
router.push('/settings/profile'); router.push('/settings/profile');
} }
if (key === 'logout') { if (key === 'logout') {
router.push('/logout'); router.push('/logout');
} }
close();
}; };
return ( return (
@ -31,26 +34,31 @@ export function ProfileButton() {
</Icon> </Icon>
</Button> </Button>
<Popup position="bottom" alignment={dir === 'rtl' ? 'start' : 'end'}> <Popup position="bottom" alignment={dir === 'rtl' ? 'start' : 'end'}>
<Menu onSelect={handleSelect} className={styles.menu}> {(close: () => void) => (
<Item key="user" className={styles.item}> <Menu onSelect={key => handleSelect(key, close)} className={styles.menu}>
<Text>{user.username}</Text> <Item key="user" className={styles.item}>
</Item> <Icon size="lg">
<Item key="profile" className={styles.item} divider={true}> <Avatar value={user.id} />
<Icon>
<Icons.User />
</Icon>
<Text>{formatMessage(labels.profile)}</Text>
</Item>
{!cloudMode && (
<Item key="logout" className={styles.item}>
<Icon>
<Icons.Logout />
</Icon> </Icon>
<Text>{formatMessage(labels.logout)}</Text> <Text>{user.username}</Text>
</Item> </Item>
)} <Item key="profile" className={styles.item} divider={true}>
<div className={styles.version}>{`v${CURRENT_VERSION}`}</div> <Icon>
</Menu> <Icons.User />
</Icon>
<Text>{formatMessage(labels.profile)}</Text>
</Item>
{!cloudMode && (
<Item key="logout" className={styles.item}>
<Icon>
<Icons.Logout />
</Icon>
<Text>{formatMessage(labels.logout)}</Text>
</Item>
)}
<div className={styles.version}>{`v${CURRENT_VERSION}`}</div>
</Menu>
)}
</Popup> </Popup>
</PopupTrigger> </PopupTrigger>
); );