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

My home #2425

Merged
merged 93 commits into from
Dec 17, 2024
Merged

My home #2425

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
93 commits
Select commit Hold shift + click to select a range
8790007
Move canvasser UI away from /my route
richardolsson Dec 11, 2024
fce8b30
Scaffold tabbed layout for "my" pages
richardolsson Dec 11, 2024
2e8b47b
Coalesce two separate user contexts into a single solution
richardolsson Dec 11, 2024
d45df0f
Add header with headline and user avatar
richardolsson Dec 11, 2024
297cbde
Remove /my/canvassassignments page
richardolsson Dec 11, 2024
c600050
Create useRemoteList() hook as suspense-compatible replacement for lo…
richardolsson Dec 11, 2024
ec9ad1e
Use useRemoteList() in useMyCanvassAssignments()
richardolsson Dec 11, 2024
b4286dc
List canvass assignments on home page
richardolsson Dec 11, 2024
bb7044c
Create hook and store logic for retrieving the user's assignments
richardolsson Dec 11, 2024
45134a4
Add call assignments to home page
richardolsson Dec 11, 2024
96fe8ec
Create hook and store logic for loading user's events
richardolsson Dec 11, 2024
9b09e79
Include events on home page
richardolsson Dec 11, 2024
d121cf8
Create loading indicator component based on Zetkin logo
richardolsson Dec 12, 2024
91e4ed7
Move activity list to separate component enclosed in Suspense
richardolsson Dec 12, 2024
81d9b68
Make tab bar sticky
richardolsson Dec 13, 2024
288884d
List the ids of all future events of all orgs that user is member of.
ziggabyte Dec 13, 2024
70bbe58
Create crude version of activity list for My pages
richardolsson Dec 13, 2024
9a9d843
Use new components for activity and events lists
richardolsson Dec 13, 2024
36de657
Sort events by start time.
ziggabyte Dec 13, 2024
a66df62
Only return open and scheduled events from the rpc.
ziggabyte Dec 13, 2024
a0bcf9a
Merge branch 'issue-1616/my-home' of github.com:zetkin/app.zetkin.org…
ziggabyte Dec 13, 2024
abf6ea6
Open drawer to filter events by organisation.
ziggabyte Dec 13, 2024
9222676
Filter by dates in calendar.
ziggabyte Dec 13, 2024
1edb4e1
Clear all filters with little button.
ziggabyte Dec 13, 2024
9158ac2
Filter out old events and assignments
richardolsson Dec 14, 2024
bea6d8f
Add actions to activity items
richardolsson Dec 14, 2024
4b89472
Retrieve (and distinguish) both signed-up and booked events
richardolsson Dec 14, 2024
f1c7487
Add useUserMemberships() hook
richardolsson Dec 14, 2024
0c03222
Add hook and store logic for signing up to events (and undoing)
richardolsson Dec 14, 2024
50c8972
Add UI for signing up and undoing signup for events
richardolsson Dec 14, 2024
5f97f1d
Add "needed" label to events
richardolsson Dec 14, 2024
ec6bf78
Fix warning from NEXTjs Image component
richardolsson Dec 14, 2024
1cd9285
Use separate theme for /my to more closely resemble upcoming ZUI
richardolsson Dec 14, 2024
87c3e04
Show date headers in all events list
richardolsson Dec 14, 2024
28a0013
Add organization and project to activity items
richardolsson Dec 14, 2024
8e475f1
Correct state handling of date picker, and little label on date filte…
ziggabyte Dec 14, 2024
ccdb1ec
Adjust styling and message of filter buttons.
ziggabyte Dec 14, 2024
787dc31
Make filter button component.
ziggabyte Dec 14, 2024
cc0e3d7
Create hook for incremental delays
richardolsson Dec 14, 2024
52e41b6
Animate items in lists
richardolsson Dec 14, 2024
f7ebdbf
Merge branch 'issue-1616/my-home' of github.com:zetkin/app.zetkin.org…
ziggabyte Dec 14, 2024
a6907a9
Create ZUIModalBackground component
richardolsson Dec 14, 2024
4cf0b8c
Add background behind filter drawer
richardolsson Dec 14, 2024
7ef8dd8
Fix missing MUI X license on app router pages
richardolsson Dec 14, 2024
0fa3bd0
Check how many orgs have events before showing filter
richardolsson Dec 15, 2024
bd14ded
Move FilterButton component to separate file
richardolsson Dec 15, 2024
8b9625a
Add filtering to My activities tab
richardolsson Dec 15, 2024
04f8d6a
Extract DrawerModal as separate component
richardolsson Dec 15, 2024
0edbbd1
Animate DrawerModal open and close
richardolsson Dec 15, 2024
a30ffa5
Redirect /my to /my/home
richardolsson Dec 15, 2024
6b31876
Create utility for redirecting to login if not authenticated
richardolsson Dec 15, 2024
5346ed5
Add auth check on feed page
richardolsson Dec 15, 2024
b841c11
Fix bug causing modal to close prematurely
richardolsson Dec 15, 2024
0521721
Fix bug causing surveys not to work fully
richardolsson Dec 15, 2024
e319ca9
Implement crude desktop layout
richardolsson Dec 15, 2024
874d3ed
Tweak list style
richardolsson Dec 15, 2024
d101f93
Add tests for useRemoteList() and fix two problems
richardolsson Dec 16, 2024
67611ec
Create footer on "My page"
richardolsson Dec 16, 2024
7ff5e85
Custom day component with dot that indicates if day has events.
ziggabyte Dec 16, 2024
fdfc644
Style loading screens to put loading indicator more towards center
richardolsson Dec 16, 2024
6103e3b
Merge branch 'issue-1616/my-home' of github.com:zetkin/app.zetkin.org…
ziggabyte Dec 16, 2024
6174cd8
Place footer always at bottom of page, and add empty states for both …
ziggabyte Dec 16, 2024
fd1c545
Make tab color the main black-grey color.
ziggabyte Dec 16, 2024
f6c0fc3
Add button to clear filters if "all events list" is empty because of …
ziggabyte Dec 16, 2024
9e7a536
Only show row of filterbuttons if there are any events.
ziggabyte Dec 16, 2024
172878b
Localise some strings.
ziggabyte Dec 16, 2024
604569a
Fix unintentional reset of ZUIModalBackground on every render
richardolsson Dec 16, 2024
30e4c6c
Add whitelabel title for My pages
richardolsson Dec 16, 2024
a4f1959
Change import to avoid loading context components on server
richardolsson Dec 16, 2024
c37b950
Add title to home pages
richardolsson Dec 16, 2024
aa4f1cd
Fix sticky tabs that broke after 6174cd863b95e6953553095fb0de449bb944…
richardolsson Dec 16, 2024
6dda732
Add redirect to call app
richardolsson Dec 16, 2024
ca4b1b8
Add filter to find events happening today.
ziggabyte Dec 16, 2024
29ebd37
Add filter for tomorrow and this week.
ziggabyte Dec 16, 2024
b715c1b
Localise the filterbutton labels and rename things in messages object.
ziggabyte Dec 16, 2024
e388198
Add favicon to My pages
richardolsson Dec 16, 2024
0896730
Make filter buttons scroll horisontally.
ziggabyte Dec 16, 2024
2a5e242
Update code in event filtering to be able to find based on all filters.
ziggabyte Dec 16, 2024
1363fa7
Hide empty labels
richardolsson Dec 17, 2024
887818e
Tweak styling of icons and bullets in activity cards
richardolsson Dec 17, 2024
2c998e6
Move buttons to left side
richardolsson Dec 17, 2024
4732ba4
Show label when signed up for an event
richardolsson Dec 17, 2024
bd7f850
Add instructions for how to cancel from booked event
richardolsson Dec 17, 2024
b73b02b
Allow icons to be excluded from list items
richardolsson Dec 17, 2024
6dd8e85
Hide event icons in "All events" list
richardolsson Dec 17, 2024
83059c6
Put active filters first in list of filters.
ziggabyte Dec 17, 2024
6822bb9
Remove "clear dates" button in the date range picker.
ziggabyte Dec 17, 2024
5332549
Put filter buttons in correct order.
ziggabyte Dec 17, 2024
9353298
Sort orgs alphabetically.
ziggabyte Dec 17, 2024
076f5f0
Make drawers scroll properly.
ziggabyte Dec 17, 2024
948d48f
Normal case letters in tabs and make tabs full width.
ziggabyte Dec 17, 2024
c52edb2
Show event start and end time in event list cards.
ziggabyte Dec 17, 2024
69f07f5
Sort multi-day events into the first day when filtering by dates.
ziggabyte Dec 17, 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
1 change: 1 addition & 0 deletions .env.development
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
ZETKIN_APP_DOMAIN=http://www.dev.zetkin.org
ZETKIN_GEN2_ORGANIZE_URL=http://organize.dev.zetkin.org
ZETKIN_GEN2_CALL_URL=http://call.dev.zetkin.org

ZETKIN_PRIVACY_POLICY_LINK=https://zetkin.org/privacy

Expand Down
5 changes: 5 additions & 0 deletions next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ module.exports = {
},
async redirects() {
return [
{
source: '/my',
destination: '/my/home',
permanent: false,
},
{
source: '/:prevPath*/calendar/events',
destination: '/:prevPath*/calendar',
Expand Down
13 changes: 13 additions & 0 deletions src/app/call/[callAssId]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { redirect } from 'next/navigation';

interface PageProps {
params: {
callAssId: string;
};
}

export default async function Page({ params }: PageProps) {
const callUrl = process.env.ZETKIN_GEN2_CALL_URL;
const assignmentUrl = callUrl + '/assignments/' + params.callAssId;
redirect(assignmentUrl);
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,6 @@ export default async function Page({ params }: PageProps) {

return <MyCanvassAssignmentPage canvassAssId={canvassAssId} />;
} catch (err) {
return redirect(`/login?redirect=/my/canvassassignments/${canvassAssId}`);
return redirect(`/login?redirect=/canvass/${canvassAssId}`);
}
}
21 changes: 0 additions & 21 deletions src/app/my/canvassassignments/page.tsx

This file was deleted.

8 changes: 8 additions & 0 deletions src/app/my/feed/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import redirectIfLoginNeeded from 'core/utils/redirectIfLoginNeeded';
import AllEventsPage from 'features/home/pages/AllEventsPage';

export default async function Page() {
await redirectIfLoginNeeded();

return <AllEventsPage />;
}
8 changes: 8 additions & 0 deletions src/app/my/home/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import redirectIfLoginNeeded from 'core/utils/redirectIfLoginNeeded';
import HomePage from 'features/home/pages/HomePage';

export default async function Page() {
await redirectIfLoginNeeded();

return <HomePage />;
}
35 changes: 35 additions & 0 deletions src/app/my/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { FC, ReactNode } from 'react';
import { Metadata } from 'next';
import { headers } from 'next/headers';

import HomeLayout from 'features/home/layouts/HomeLayout';
import HomeThemeProvider from 'features/home/components/HomeThemeProvider';
import { getBrowserLanguage } from 'utils/locale';
import getServerMessages from 'core/i18n/server';
import messageIds from 'features/home/l10n/messageIds';

export async function generateMetadata(): Promise<Metadata> {
const lang = getBrowserLanguage(headers().get('accept-language') || '');
const messages = await getServerMessages(lang, messageIds);

return {
icons: [{ url: '/logo-zetkin.png' }],
title: process.env.HOME_TITLE || messages.title(),
};
}

type Props = {
children: ReactNode;
};

const MyHomeLayout: FC<Props> = ({ children }) => {
const homeTitle = process.env.HOME_TITLE;

return (
<HomeThemeProvider>
<HomeLayout title={homeTitle}>{children}</HomeLayout>
</HomeThemeProvider>
);
};

export default MyHomeLayout;
6 changes: 3 additions & 3 deletions src/core/Providers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ import { EventPopperProvider } from 'features/events/components/EventPopper/Even
import { MessageList } from 'utils/locale';
import { Store } from './store';
import { themeWithLocale } from '../theme';
import { UserContext } from 'utils/hooks/useFocusDate';
import { ZetkinUser } from 'utils/types/zetkin';
import { ZUIConfirmDialogProvider } from 'zui/ZUIConfirmDialogProvider';
import { ZUISnackbarProvider } from 'zui/ZUISnackbarContext';
import { UserProvider } from './env/UserContext';

type ProviderData = {
env: Environment;
Expand Down Expand Up @@ -63,7 +63,7 @@ const Providers: FC<ProvidersProps> = ({
return (
<ReduxProvider store={store}>
<EnvProvider env={env}>
<UserContext.Provider value={user}>
<UserProvider user={user}>
<StyledEngineProvider injectFirst>
<CacheProvider value={cache}>
<ThemeProvider theme={themeWithLocale(lang)}>
Expand All @@ -87,7 +87,7 @@ const Providers: FC<ProvidersProps> = ({
</ThemeProvider>
</CacheProvider>
</StyledEngineProvider>
</UserContext.Provider>
</UserProvider>
</EnvProvider>
</ReduxProvider>
);
Expand Down
25 changes: 17 additions & 8 deletions src/core/env/ClientContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import {
Theme,
ThemeProvider,
} from '@mui/material/styles';
import { LicenseInfo, LocalizationProvider } from '@mui/x-date-pickers-pro';
import { AdapterDayjs } from '@mui/x-date-pickers-pro/AdapterDayjs';

import BrowserApiClient from 'core/api/client/BrowserApiClient';
import Environment from 'core/env/Environment';
Expand Down Expand Up @@ -56,21 +58,28 @@ const ClientContext: FC<ClientContextProps> = ({
const env = new Environment(apiClient, envVars);
const cache = createCache({ key: 'css', prepend: true });

// MUI-X license
if (env.vars.MUIX_LICENSE_KEY) {
LicenseInfo.setLicenseKey(env.vars.MUIX_LICENSE_KEY);
}

return (
<ReduxProvider store={store}>
<StyledEngineProvider injectFirst>
<CacheProvider value={cache}>
<ThemeProvider theme={themeWithLocale(lang)}>
<EnvProvider env={env}>
<UserProvider user={user}>
<IntlProvider
defaultLocale="en"
locale={lang}
messages={messages}
>
<CssBaseline />
{children}
</IntlProvider>
<LocalizationProvider dateAdapter={AdapterDayjs}>
<IntlProvider
defaultLocale="en"
locale={lang}
messages={messages}
>
<CssBaseline />
{children}
</IntlProvider>
</LocalizationProvider>
</UserProvider>
</EnvProvider>
</ThemeProvider>
Expand Down
2 changes: 2 additions & 0 deletions src/core/env/Environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ type EnvVars = {
MUIX_LICENSE_KEY: string | null;
ZETKIN_APP_DOMAIN: string | null;
ZETKIN_GEN2_ORGANIZE_URL?: string | null;
ZETKIN_PRIVACY_POLICY_LINK?: string | null;
};

export default class Environment {
Expand All @@ -22,6 +23,7 @@ export default class Environment {
MUIX_LICENSE_KEY: null,
ZETKIN_APP_DOMAIN: null,
ZETKIN_GEN2_ORGANIZE_URL: null,
ZETKIN_PRIVACY_POLICY_LINK: null,
};
}

Expand Down
165 changes: 165 additions & 0 deletions src/core/hooks/useRemoteList.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import { describe, expect, it, jest } from '@jest/globals';
import { act, render } from '@testing-library/react';
import { FC, Suspense } from 'react';
import { Provider as ReduxProvider, useSelector } from 'react-redux';
import { configureStore } from '@reduxjs/toolkit';

import useRemoteList from './useRemoteList';
import { RemoteList, remoteList } from 'utils/storeUtils';

type ListObjectForTest = { id: number; name: string };
type StoreState = {
list: RemoteList<ListObjectForTest>;
};

describe('useRemoteList()', () => {
it('triggers a load when the data has not yet been loaded', async () => {
const { hooks, promise, render, store } = setupWrapperComponent();

const { queryByText } = render();

expect(queryByText('loading')).not.toBeNull();

await act(async () => {
await promise;
});

expect(hooks.loader).toHaveBeenCalled();
expect(store.dispatch).toHaveBeenCalledTimes(2);
expect(store.dispatch).toHaveBeenNthCalledWith(1, {
payload: undefined,
type: 'load',
});
expect(store.dispatch).toHaveBeenNthCalledWith(2, {
payload: [{ id: 1, name: 'Clara Zetkin' }],
type: 'loaded',
});

expect(queryByText('loading')).toBeNull();
expect(queryByText('loaded')).not.toBeNull();
});

it('returns data without load when the data has been loaded recently', async () => {
const { hooks, render, store } = setupWrapperComponent({
...remoteList([
{
id: 1,
name: 'Clara Zetkin',
},
]),
loaded: new Date().toISOString(),
});

const { queryByText } = render();

expect(store.dispatch).not.toHaveBeenCalled();
expect(hooks.loader).not.toHaveBeenCalled();
expect(queryByText('loading')).toBeNull();
expect(queryByText('loaded')).not.toBeNull();

const listItem = queryByText('Clara Zetkin');
expect(listItem?.tagName).toBe('LI');
});

it('returns stale data while re-loading', async () => {
const { hooks, promise, render, store } = setupWrapperComponent({
...remoteList([
{
id: 1,
name: 'Clara Zetkin',
},
]),
loaded: new Date(1857, 6, 5).toISOString(),
});

const { queryByText } = render();

expect(queryByText('Clara Zetkin')).not.toBeNull();

await act(async () => {
await promise;
});

expect(hooks.loader).toHaveBeenCalled();
expect(store.dispatch).toHaveBeenCalledTimes(2);
});
});

function setupWrapperComponent(initialList?: RemoteList<ListObjectForTest>) {
const store = configureStore<StoreState>({
preloadedState: {
list: initialList || remoteList(),
},
reducer: (state, action) => {
if (action.type == 'load') {
return {
list: {
...remoteList(),
isLoading: true,
},
};
} else if (action.type == 'loaded') {
return {
list: {
...remoteList(),
isLoading: false,
loaded: new Date().toISOString(),
},
};
}

return (
state || {
list: remoteList(),
}
);
},
});
jest.spyOn(store, 'dispatch');

const promise = Promise.resolve([{ id: 1, name: 'Clara Zetkin' }]);

const hooks = {
actionOnLoad: () => ({ payload: undefined, type: 'load' }),
actionOnSuccess: (data: ListObjectForTest[]) => ({
payload: data,
type: 'loaded',
}),
loader: () => promise,
};

jest.spyOn(hooks, 'loader');

const Component: FC = () => {
const list = useSelector<StoreState, RemoteList<ListObjectForTest>>(
(state) => state.list
);

const items = useRemoteList(list, hooks);

return (
<div>
<p>loaded</p>
<ul>
{items.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
);
};

return {
hooks,
promise,
render: () =>
render(
<ReduxProvider store={store}>
<Suspense fallback={<p>loading</p>}>
<Component />
</Suspense>
</ReduxProvider>
),
store,
};
}
Loading
Loading