diff --git a/cypress/fixtures/dummyEventResponses.json b/cypress/fixtures/dummyEventResponses.json
new file mode 100644
index 000000000..ff73c36c2
--- /dev/null
+++ b/cypress/fixtures/dummyEventResponses.json
@@ -0,0 +1,14 @@
+{
+ "data": [
+ {
+ "action_id": 25,
+ "response_date": "2021-01-21T18:30:00+00:00",
+ "organization_id": 1,
+ "person": {
+ "name": "Dummy User",
+ "id": 2
+ },
+ "id": 2
+ }
+ ]
+}
\ No newline at end of file
diff --git a/cypress/integration/org_event_page.spec.ts b/cypress/integration/org_event_page.spec.ts
index ad80a36ad..dec1034ef 100644
--- a/cypress/integration/org_event_page.spec.ts
+++ b/cypress/integration/org_event_page.spec.ts
@@ -1,28 +1,79 @@
describe('/o/[orgId]/events/[eventId]', () => {
+ beforeEach(() => {
+ cy.request('delete', 'http://localhost:8001/_mocks');
+ });
+
+ after(() => {
+ cy.request('delete', 'http://localhost:8001/_mocks');
+ });
+
it('contains non-interactive event content', () => {
- cy.visit('/o/1/events/22');
+ cy.visit('/o/1/events/25');
cy.get('[data-test="event-title"]').should('be.visible');
cy.get('[data-test="duration"]').should('be.visible');
cy.get('[data-test="location"]').should('be.visible');
});
it('contains clickable org name that leads to org page', () => {
- cy.visit('/o/1/events/22');
+ cy.visit('/o/1/events/25');
cy.waitUntilReactRendered();
cy.findByText('My Organization').click();
cy.url().should('match', /\/o\/1$/);
});
it('contains clickable campaign name that leads to campaign page', () => {
- cy.visit('/o/1/events/22');
+ cy.visit('/o/1/events/25');
cy.waitUntilReactRendered();
cy.findByText('Second campaign').click();
cy.url().should('match', /\/o\/1\/campaigns\/2$/);
});
- it('contains a sign-up button', () => {
- cy.visit('/o/1/events/22');
- cy.findByText('Sign-up').should('be.visible');
+ it('shows a sign-up button if user is not signed up to the event', () => {
+ cy.request('put', 'http://localhost:8001/v1/users/me/action_responses/_mocks/get', {
+ response: {
+ data: {
+ data: [],
+ },
+ },
+ });
+
+ cy.visit('/login');
+ cy.get('input[aria-label="E-mail address"]').type('testadmin@example.com');
+ cy.get('input[aria-label="Password"]').type('password');
+ cy.get('input[aria-label="Log in"]')
+ .click();
+
+ cy.visit('/o/1/events/25');
+ cy.waitUntilReactRendered();
+ cy.findByText('Sign-up').click();
+ //TODO: Verify that API request is done corrently.
+ });
+
+ it('shows an undo sign-up button if user is signed up to the event', () => {
+ cy.fixture('dummyEventResponses').then(json => {
+ cy.request('put', 'http://localhost:8001/v1/users/me/action_responses/_mocks/get', {
+ response: {
+ data: json,
+ },
+ });
+
+ cy.request('put', 'http://localhost:8001/v1/orgs/1/actions/25/responses/2/_mocks/delete', {
+ response: {
+ status: 204,
+ },
+ });
+
+ cy.visit('/login');
+ cy.get('input[aria-label="E-mail address"]').type('testadmin@example.com');
+ cy.get('input[aria-label="Password"]').type('password');
+ cy.get('input[aria-label="Log in"]')
+ .click();
+
+ cy.visit('/o/1/events/25');
+ cy.waitUntilReactRendered();
+ cy.findByText('Undo sign-up').click();
+ //TODO: Verify that API request is done corrently.
+ });
});
});
diff --git a/cypress/integration/org_events_page.spec.ts b/cypress/integration/org_events_page.spec.ts
index 4635b7327..6cf25f47a 100644
--- a/cypress/integration/org_events_page.spec.ts
+++ b/cypress/integration/org_events_page.spec.ts
@@ -3,12 +3,17 @@ describe('/o/[orgId]/events', () => {
cy.request('delete', 'http://localhost:8001/_mocks');
});
+ after(() => {
+ cy.request('delete', 'http://localhost:8001/_mocks');
+ });
+
it('contains name of organization', () => {
cy.visit('/o/1/events');
+ cy.waitUntilReactRendered();
cy.contains('My Organization');
});
- it.only('contains events which are linked to event pages', () => {
+ it('contains events which are linked to event pages', () => {
cy.request('put', 'http://localhost:8001/v1/orgs/1/campaigns/_mocks/get', {
response: {
data: {
@@ -38,7 +43,7 @@ describe('/o/[orgId]/events', () => {
});
it('contains a placeholder if there are no events', () => {
- cy.request('put', 'http://localhost:8001/v1/orgs/1/campaigns/1/actions/_mocks/get', {
+ cy.request('put', 'http://localhost:8001/v1/orgs/1/campaigns/_mocks/get', {
response: {
data: {
data: [],
@@ -49,6 +54,56 @@ describe('/o/[orgId]/events', () => {
cy.visit('/o/1/events');
cy.get('[data-test="no-events-placeholder"]').should('be.visible');
});
+
+ it('shows a sign-up button if user is not signed up to an event', () => {
+ cy.request('put', 'http://localhost:8001/v1/users/me/action_responses/_mocks/get', {
+ response: {
+ data: {
+ data: [],
+ },
+ },
+ });
+
+ cy.visit('/login');
+ cy.get('input[aria-label="E-mail address"]').type('testadmin@example.com');
+ cy.get('input[aria-label="Password"]').type('password');
+ cy.get('input[aria-label="Log in"]')
+ .click();
+
+ cy.visit('/o/1/events');
+ cy.waitUntilReactRendered();
+ cy.get('[data-test="event-response-button"]')
+ .eq(4)
+ .click();
+ //TODO: Verify that API request is done corrently.
+ });
+
+ it('shows an undo sign-up button if user is signed up to an event', () => {
+ cy.fixture('dummyEventResponses').then(json => {
+ cy.request('put', 'http://localhost:8001/v1/users/me/action_responses/_mocks/get', {
+ response: {
+ data: json,
+ },
+ });
+
+ cy.request('put', 'http://localhost:8001/v1/orgs/1/actions/25/responses/2/_mocks/delete', {
+ response: {
+ status: 204,
+ },
+ });
+
+ cy.visit('/login');
+ cy.get('input[aria-label="E-mail address"]').type('testadmin@example.com');
+ cy.get('input[aria-label="Password"]').type('password');
+ cy.get('input[aria-label="Log in"]')
+ .click();
+
+ cy.visit('/o/1/events');
+ cy.waitUntilReactRendered();
+ cy.findByText('Undo sign-up').click();
+ //TODO: Verify that API request is done corrently.
+ });
+ });
});
// Hack to flag for typescript as module
diff --git a/cypress/integration/reocurring_functionality.spec.ts b/cypress/integration/reocurring_functionality.spec.ts
index 22ed57747..2375c31eb 100644
--- a/cypress/integration/reocurring_functionality.spec.ts
+++ b/cypress/integration/reocurring_functionality.spec.ts
@@ -1,4 +1,5 @@
describe('Reocurring functionality', () => {
+
it('contains a clickable org logo which leads to org page', () => {
cy.visit('/o/1/events');
cy.get('[data-test="org-avatar"]')
diff --git a/src/components/EventList.spec.tsx b/src/components/EventList.spec.tsx
index 1cc664fe2..8c6994685 100644
--- a/src/components/EventList.spec.tsx
+++ b/src/components/EventList.spec.tsx
@@ -1,11 +1,13 @@
import EventList from './EventList';
import { mountWithProviders } from '../utils/testing';
import { ZetkinEvent } from '../interfaces/ZetkinEvent';
+import { ZetkinEventResponse } from '../types/zetkin';
import { ZetkinOrganization } from '../interfaces/ZetkinOrganization';
describe('EventList', () => {
let dummyOrg : ZetkinOrganization;
let dummyEvents : ZetkinEvent[];
+ let dummyEventResponses : ZetkinEventResponse[];
beforeEach(()=> {
cy.fixture('dummyOrg.json')
@@ -16,11 +18,20 @@ describe('EventList', () => {
.then((data : {data: ZetkinEvent[]}) => {
dummyEvents = data.data;
});
+ cy.fixture('dummyEventResponses.json')
+ .then((data : {data: ZetkinEventResponse[]}) => {
+ dummyEventResponses = data.data;
+ });
});
it('contains data for each event', () => {
mountWithProviders(
- ,
+ null }
+ org={ dummyOrg }
+ />,
);
cy.get('[data-test="event"]').each((item) => {
@@ -36,8 +47,14 @@ describe('EventList', () => {
it('contains an activity title instead of missing event title', () => {
dummyEvents[0].title = undefined;
+
mountWithProviders(
- ,
+ null }
+ org={ dummyOrg }
+ />,
);
cy.get('[data-test="event"]')
@@ -46,16 +63,33 @@ describe('EventList', () => {
});
it('contains a sign-up button for each event', () => {
+ const spyOnSubmit = cy.spy();
+
mountWithProviders(
- ,
+ ,
);
- cy.contains('misc.eventList.signup');
+ cy.findByText('misc.eventList.signup')
+ .eq(0)
+ .click()
+ .then(() => {
+ expect(spyOnSubmit).to.be.calledOnce;
+ });
});
it('contains a button for more info on each event', () => {
mountWithProviders(
- ,
+ null }
+ org={ dummyOrg }
+ />,
);
cy.contains('misc.eventList.moreInfo');
@@ -63,8 +97,14 @@ describe('EventList', () => {
it('shows a placeholder when the list is empty', () => {
dummyEvents = [];
+
mountWithProviders(
- ,
+ null }
+ org={ dummyOrg }
+ />,
);
cy.contains('misc.eventList.placeholder');
@@ -72,7 +112,12 @@ describe('EventList', () => {
it('shows a placeholder when the list is undefined', () => {
mountWithProviders(
- ,
+ null }
+ org={ dummyOrg }
+ />,
);
cy.contains('misc.eventList.placeholder');
diff --git a/src/components/EventList.tsx b/src/components/EventList.tsx
index cce5f0b33..3267b6d6e 100644
--- a/src/components/EventList.tsx
+++ b/src/components/EventList.tsx
@@ -12,14 +12,18 @@ import {
} from 'react-intl';
import { ZetkinEvent } from '../interfaces/ZetkinEvent';
+import { ZetkinEventResponse } from '../types/zetkin';
import { ZetkinOrganization } from '../interfaces/ZetkinOrganization';
interface EventListProps {
events: ZetkinEvent[] | undefined;
- org: ZetkinOrganization | undefined;
+ org: ZetkinOrganization;
+ eventResponses: ZetkinEventResponse[] | undefined;
+ onEventResponse: (eventId: number, orgId: number, response: boolean) => void;
}
-const EventList = ({ events, org } : EventListProps) : JSX.Element => {
+const EventList = ({ eventResponses, events, onEventResponse, org } : EventListProps) : JSX.Element => {
+
if (!events || events.length === 0) {
return (
@@ -31,46 +35,63 @@ const EventList = ({ events, org } : EventListProps) : JSX.Element => {
return (
<>
- { events?.map((e) => (
-
-
- { e.title ? e.title : e.activity.title }
-
- { org?.title }
- { e.campaign.title }
-
-
- ,
-
-
-
- ,
-
- { e.location.title }
-
-
-
-
-
-
- )) }
+ ) }
+
+
+
+
+
+
+
+
+ );
+ }) }
>
);
diff --git a/src/fetching/deleteEventResponse.ts b/src/fetching/deleteEventResponse.ts
new file mode 100644
index 000000000..52001ba09
--- /dev/null
+++ b/src/fetching/deleteEventResponse.ts
@@ -0,0 +1,20 @@
+import apiUrl from '../utils/apiUrl';
+import { ZetkinMembership } from '../types/zetkin';
+
+interface MutationVariables {
+ eventId: number;
+ orgId: number;
+}
+
+export default async function deleteEventResponse({ eventId, orgId } : MutationVariables) : Promise {
+ const mRes = await fetch(apiUrl('/users/me/memberships'));
+ const mData = await mRes.json();
+ //TODO: Memberships should be cached.
+ const orgMembership = mData.data.find((m : ZetkinMembership ) => m.organization.id === orgId);
+
+ if (orgMembership) {
+ await fetch(apiUrl(`/orgs/${orgId}/actions/${eventId}/responses/${orgMembership.profile.id}`), {
+ method: 'DELETE',
+ });
+ }
+}
\ No newline at end of file
diff --git a/src/fetching/getEvent.ts b/src/fetching/getEvent.ts
index 56968a629..0c864da0c 100644
--- a/src/fetching/getEvent.ts
+++ b/src/fetching/getEvent.ts
@@ -1,13 +1,13 @@
-import apiUrl from '../utils/apiUrl';
+import { defaultFetch } from '.';
import { ZetkinEvent } from '../interfaces/ZetkinEvent';
-export default function getEvent(orgId : string, eventId : string) {
+export default function getEvent(orgId : string, eventId : string, fetch = defaultFetch) {
return async () : Promise => {
- const cRes = await fetch(apiUrl(`/orgs/${orgId}/campaigns`));
+ const cRes = await fetch(`/orgs/${orgId}/campaigns`);
const cData = await cRes.json();
for (const obj of cData.data) {
- const eventsRes = await fetch(apiUrl(`/orgs/${orgId}/campaigns/${obj.id}/actions`));
+ const eventsRes = await fetch(`/orgs/${orgId}/campaigns/${obj.id}/actions`);
const campaignEvents = await eventsRes.json();
const eventData = campaignEvents.data.find((event : ZetkinEvent) => event.id.toString() === eventId);
if (eventData) {
diff --git a/src/fetching/getEventResponses.ts b/src/fetching/getEventResponses.ts
new file mode 100644
index 000000000..f3d55b312
--- /dev/null
+++ b/src/fetching/getEventResponses.ts
@@ -0,0 +1,10 @@
+import { defaultFetch } from '.';
+import { ZetkinEventResponse } from '../types/zetkin';
+
+export default function getEventResponses(fetch = defaultFetch) {
+ return async () : Promise => {
+ const rRes = await fetch('/users/me/action_responses');
+ const rData = await rRes.json();
+ return rData.data;
+ };
+}
\ No newline at end of file
diff --git a/src/fetching/getEvents.ts b/src/fetching/getEvents.ts
index aae159590..c49d99e00 100644
--- a/src/fetching/getEvents.ts
+++ b/src/fetching/getEvents.ts
@@ -1,15 +1,15 @@
-import apiUrl from '../utils/apiUrl';
+import { defaultFetch } from '.';
import { ZetkinEvent } from '../interfaces/ZetkinEvent';
-export default function getEvents(orgId : string) {
+export default function getEvents(orgId : string, fetch = defaultFetch) {
return async () : Promise => {
- const cRes = await fetch(apiUrl(`/orgs/${orgId}/campaigns`));
+ const cRes = await fetch(`/orgs/${orgId}/campaigns`);
const cData = await cRes.json();
let allEventsData : ZetkinEvent[] = [];
for (const obj of cData.data) {
- const eventsRes = await fetch(apiUrl(`/orgs/${orgId}/campaigns/${obj.id}/actions`));
+ const eventsRes = await fetch(`/orgs/${orgId}/campaigns/${obj.id}/actions`);
const campaignEvents = await eventsRes.json();
allEventsData = allEventsData.concat(campaignEvents.data);
}
diff --git a/src/fetching/getOrg.ts b/src/fetching/getOrg.ts
index cc593bcb5..4d8028b9d 100644
--- a/src/fetching/getOrg.ts
+++ b/src/fetching/getOrg.ts
@@ -1,11 +1,9 @@
-import apiUrl from '../utils/apiUrl';
-
+import { defaultFetch } from '.';
import { ZetkinOrganization } from '../interfaces/ZetkinOrganization';
-export default function getOrg(orgId : string) {
+export default function getOrg(orgId : string, fetch = defaultFetch) {
return async () : Promise => {
- const url = apiUrl(`/orgs/${orgId}`);
- const oRes = await fetch(url);
+ const oRes = await fetch(`/orgs/${orgId}`);
const oData = await oRes.json();
return oData.data;
};
diff --git a/src/fetching/index.ts b/src/fetching/index.ts
new file mode 100644
index 000000000..0a1f0c4dc
--- /dev/null
+++ b/src/fetching/index.ts
@@ -0,0 +1,6 @@
+import apiUrl from '../utils/apiUrl';
+
+export function defaultFetch(path : string, init? : RequestInit) : Promise {
+ const url = apiUrl(path);
+ return fetch(url, init);
+}
\ No newline at end of file
diff --git a/src/fetching/putEventResponse.ts b/src/fetching/putEventResponse.ts
new file mode 100644
index 000000000..21c15716a
--- /dev/null
+++ b/src/fetching/putEventResponse.ts
@@ -0,0 +1,25 @@
+import apiUrl from '../utils/apiUrl';
+import { ZetkinEventResponse, ZetkinMembership } from '../types/zetkin';
+
+interface MutationVariables {
+ eventId: number;
+ orgId: number;
+}
+
+export default async function putEventResponse({ eventId, orgId } : MutationVariables) : Promise {
+ const mRes = await fetch(apiUrl('/users/me/memberships'));
+ const mData = await mRes.json();
+ //TODO: Memberships should be cached.
+ const orgMembership = mData.data.find((m : ZetkinMembership ) => m.organization.id === orgId);
+
+ if (orgMembership) {
+ const eventRes = await fetch(apiUrl(`/orgs/${orgId}/actions/${eventId}/responses/${orgMembership.profile.id}`), {
+ method: 'PUT',
+ });
+ const eventResData = await eventRes.json();
+
+ return eventResData.data;
+ }
+
+ throw 'no membership';
+}
\ No newline at end of file
diff --git a/src/hooks/index.ts b/src/hooks/index.ts
index 4bff73f92..92d51980c 100644
--- a/src/hooks/index.ts
+++ b/src/hooks/index.ts
@@ -1,8 +1,51 @@
import React from 'react';
+import { useMutation, useQuery, useQueryClient } from 'react-query';
+
+import deleteEventResponse from '../fetching/deleteEventResponse';
+import getEventResponses from '../fetching/getEventResponses';
+import putEventResponse from '../fetching/putEventResponse';
+import { ZetkinEventResponse } from '../types/zetkin';
import { ZetkinUser } from '../interfaces/ZetkinUser';
export const UserContext = React.createContext(null);
export const useUser = () : ZetkinUser | null => {
return React.useContext(UserContext);
+};
+
+type OnEventResponse = (eventId : number, orgId : number, response : boolean) => void;
+
+type EventResponses = {
+ eventResponses: ZetkinEventResponse[] | undefined;
+ onEventResponse: OnEventResponse;
+}
+
+export const useEventResponses = () : EventResponses => {
+ const responseQuery = useQuery('eventResponses', getEventResponses());
+ const eventResponses = responseQuery.data;
+
+ const queryClient = useQueryClient();
+
+ const removeFunc = useMutation(deleteEventResponse, {
+ onSettled: () => {
+ queryClient.invalidateQueries('eventResponses');
+ },
+ });
+
+ const addFunc = useMutation(putEventResponse, {
+ onSettled: () => {
+ queryClient.invalidateQueries('eventResponses');
+ },
+ });
+
+ function onEventResponse (eventId : number, orgId : number, response : boolean) {
+ if (response) {
+ removeFunc.mutate({ eventId, orgId });
+ }
+ else {
+ addFunc.mutate({ eventId, orgId });
+ }
+ }
+
+ return { eventResponses, onEventResponse };
};
\ No newline at end of file
diff --git a/src/locale/misc/eventList/en.yml b/src/locale/misc/eventList/en.yml
index 01db07ec1..e60a72153 100644
--- a/src/locale/misc/eventList/en.yml
+++ b/src/locale/misc/eventList/en.yml
@@ -1,3 +1,4 @@
placeholder: Sorry, there are no planned events at the moment.
moreInfo: More info
-signup: Sign-up
\ No newline at end of file
+signup: Sign-up
+undoSignup: Undo sign-up
\ No newline at end of file
diff --git a/src/locale/misc/eventList/sv.yml b/src/locale/misc/eventList/sv.yml
index f30b97b2c..be3ee8f90 100644
--- a/src/locale/misc/eventList/sv.yml
+++ b/src/locale/misc/eventList/sv.yml
@@ -1,3 +1,4 @@
placeholder: Tyvärr finns det inga planerade händelser för tillfället.
moreInfo: Mer info
-signup: Anmälan
\ No newline at end of file
+signup: Anmälan
+undoSignup: Ångra anmälan
\ No newline at end of file
diff --git a/src/locale/pages/orgEvent/en.yml b/src/locale/pages/orgEvent/en.yml
index fe4b19989..d6014be83 100644
--- a/src/locale/pages/orgEvent/en.yml
+++ b/src/locale/pages/orgEvent/en.yml
@@ -1,2 +1,3 @@
actions:
- signUp: Sign-up
\ No newline at end of file
+ signup: Sign-up
+ undoSignup: Undo sign-up
diff --git a/src/locale/pages/orgEvent/sv.yml b/src/locale/pages/orgEvent/sv.yml
index 94a204a7d..b3e65f08a 100644
--- a/src/locale/pages/orgEvent/sv.yml
+++ b/src/locale/pages/orgEvent/sv.yml
@@ -1,2 +1,3 @@
actions:
- signUp: Anmälan
+ signup: Anmälan
+ undoSignup: Ångra anmälan
\ No newline at end of file
diff --git a/src/pages/o/[orgId]/campaigns/[campId].tsx b/src/pages/o/[orgId]/campaigns/[campId].tsx
index 033137772..c667b5cfd 100644
--- a/src/pages/o/[orgId]/campaigns/[campId].tsx
+++ b/src/pages/o/[orgId]/campaigns/[campId].tsx
@@ -10,6 +10,7 @@ import getCampaignEvents from '../../../../fetching/getCampaignEvents';
import getOrg from '../../../../fetching/getOrg';
import { PageWithLayout } from '../../../../types';
import { scaffold } from '../../../../utils/next';
+import { useEventResponses } from '../../../../hooks';
export const getServerSideProps : GetServerSideProps = scaffold(async (context) => {
const queryClient = new QueryClient();
@@ -51,6 +52,8 @@ const OrgCampaignPage : PageWithLayout = (props) => {
const orgQuery = useQuery(['org', orgId], getOrg(orgId));
const campaignEventsQuery = useQuery(['campaignEvents', campId], getCampaignEvents(orgId, campId));
+ const { eventResponses, onEventResponse } = useEventResponses();
+
return (
@@ -60,8 +63,11 @@ const OrgCampaignPage : PageWithLayout = (props) => {
{ campaignQuery.data?.info_text }
);
diff --git a/src/pages/o/[orgId]/events.tsx b/src/pages/o/[orgId]/events.tsx
index c8a3420d1..ad757faa3 100644
--- a/src/pages/o/[orgId]/events.tsx
+++ b/src/pages/o/[orgId]/events.tsx
@@ -4,11 +4,13 @@ import { GetServerSideProps } from 'next';
import { QueryClient, useQuery } from 'react-query';
import EventList from '../../../components/EventList';
+import getEventResponses from '../../../fetching/getEventResponses';
import getEvents from '../../../fetching/getEvents';
import getOrg from '../../../fetching/getOrg';
import MainOrgLayout from '../../../components/layout/MainOrgLayout';
import { PageWithLayout } from '../../../types';
import { scaffold } from '../../../utils/next';
+import { useEventResponses } from '../../../hooks';
const scaffoldOptions = {
localeScope: [
@@ -22,9 +24,14 @@ export const getServerSideProps : GetServerSideProps = scaffold(async (context)
const queryClient = new QueryClient();
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const { orgId } = context.params!;
+ const { user } = context;
- await queryClient.prefetchQuery('events', getEvents(orgId as string));
- await queryClient.prefetchQuery(['org', orgId], getOrg(orgId as string));
+ await queryClient.prefetchQuery('events', getEvents(orgId as string, context.apiFetch));
+ await queryClient.prefetchQuery(['org', orgId], getOrg(orgId as string, context.apiFetch));
+
+ if (user) {
+ await queryClient.prefetchQuery('eventResponses', getEventResponses(context.apiFetch));
+ }
const eventsState = queryClient.getQueryState('events');
const orgState = queryClient.getQueryState(['org', orgId]);
@@ -53,11 +60,16 @@ const OrgEventsPage : PageWithLayout = (props) => {
const eventsQuery = useQuery('events', getEvents(orgId));
const orgQuery = useQuery(['org', orgId], getOrg(orgId));
+ const { eventResponses, onEventResponse } = useEventResponses();
+
return (
);
diff --git a/src/pages/o/[orgId]/events/[eventId].tsx b/src/pages/o/[orgId]/events/[eventId].tsx
index 16f694aaa..d3a88cbf6 100644
--- a/src/pages/o/[orgId]/events/[eventId].tsx
+++ b/src/pages/o/[orgId]/events/[eventId].tsx
@@ -24,9 +24,11 @@ import { QueryClient, useQuery } from 'react-query';
import DefaultOrgLayout from '../../../../components/layout/DefaultOrgLayout';
import getEvent from '../../../../fetching/getEvent';
+import getEventResponses from '../../../../fetching/getEventResponses';
import getOrg from '../../../../fetching/getOrg';
import { PageWithLayout } from '../../../../types';
import { scaffold } from '../../../../utils/next';
+import { useEventResponses } from '../../../../hooks';
import { ZetkinEvent } from '../../../../interfaces/ZetkinEvent';
import { ZetkinOrganization } from '../../../../interfaces/ZetkinOrganization';
@@ -41,9 +43,17 @@ export const getServerSideProps : GetServerSideProps = scaffold(async (context)
const queryClient = new QueryClient();
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const { orgId, eventId } = context.params!;
+ const { user } = context;
- await queryClient.prefetchQuery(['event', eventId], getEvent(orgId as string, eventId as string));
- await queryClient.prefetchQuery(['org', orgId], getOrg(orgId as string));
+ await queryClient.prefetchQuery(['event', eventId], getEvent(orgId as string, eventId as string, context.apiFetch));
+ await queryClient.prefetchQuery(['org', orgId], getOrg(orgId as string, context.apiFetch));
+
+ if (user) {
+ await queryClient.prefetchQuery('eventResponses', getEventResponses(context.apiFetch));
+ }
+ else {
+ null;
+ }
const eventState = queryClient.getQueryState(['event', eventId]);
const orgState = queryClient.getQueryState(['org', orgId]);
@@ -73,6 +83,7 @@ const OrgEventPage : PageWithLayout = (props) => {
const { orgId, eventId } = props;
const eventQuery = useQuery(['event', eventId], getEvent(orgId, eventId));
const orgQuery = useQuery(['org', orgId], getOrg(orgId));
+ const { eventResponses, onEventResponse } = useEventResponses();
if (!eventQuery.data) {
return null;
@@ -81,6 +92,8 @@ const OrgEventPage : PageWithLayout = (props) => {
const event = eventQuery.data as ZetkinEvent;
const org = orgQuery.data as ZetkinOrganization;
+ const response = eventResponses?.find(response => response.action_id === event.id);
+
return (
<>
@@ -151,9 +164,21 @@ const OrgEventPage : PageWithLayout = (props) => {
marginTop="size-200"
position="absolute"
right="size-200">
-
-
-
+ { response ? (
+ onEventResponse(event.id, org.id, true) }
+ variant="cta" width="100%">
+
+
+ ) : (
+ onEventResponse(event.id, org.id, false) }
+ variant="cta" width="100%">
+
+
+ ) }
>
);
diff --git a/src/types/zetkin.ts b/src/types/zetkin.ts
new file mode 100644
index 000000000..1398fa1aa
--- /dev/null
+++ b/src/types/zetkin.ts
@@ -0,0 +1,19 @@
+export interface ZetkinMembership {
+ organization: {
+ id: number;
+ title: string;
+ };
+ profile: {
+ id: number;
+ };
+}
+
+export interface ZetkinEventResponse {
+ action_id: number;
+ response_date: string;
+ person: {
+ name: string;
+ id: number;
+ };
+ id: number;
+}
\ No newline at end of file
diff --git a/src/utils/next.ts b/src/utils/next.ts
index a1db9017d..3fc43cf97 100644
--- a/src/utils/next.ts
+++ b/src/utils/next.ts
@@ -26,6 +26,7 @@ export type ScaffoldedProps = RegularProps & {
export type ScaffoldedContext = GetServerSidePropsContext & {
apiFetch: (path : string, init? : RequestInit) => Promise;
+ user: ZetkinUser | null;
z: ZetkinZ;
};
@@ -74,6 +75,16 @@ export const scaffold = (wrapped : ScaffoldedGetServerSideProps, options? : Scaf
ctx.z.setTokenData(reqWithSession.session.tokenData);
}
+ try {
+ const userRes = await ctx.z.resource('users', 'me').get();
+ ctx.user = userRes.data.data as ZetkinUser;
+ }
+ catch (error) {
+ ctx.user = null;
+ }
+
+ const user = ctx.user;
+
const result = await wrapped(ctx);
// Figure out browser's preferred language
@@ -83,24 +94,14 @@ export const scaffold = (wrapped : ScaffoldedGetServerSideProps, options? : Scaf
const messages = await getMessages(lang, options?.localeScope ?? []);
- const augmentProps = (user : ZetkinUser | null) => {
- if (hasProps(result)) {
- const scaffoldedProps : ScaffoldedProps = {
- ...result.props,
- lang,
- messages,
- user,
- };
- result.props = scaffoldedProps;
- }
- };
-
- try {
- const user = await ctx.z.resource('users', 'me').get();
- augmentProps(user.data.data as ZetkinUser);
- }
- catch (error) {
- augmentProps(null);
+ if (hasProps(result)) {
+ const scaffoldedProps : ScaffoldedProps = {
+ ...result.props,
+ lang,
+ messages,
+ user,
+ };
+ result.props = scaffoldedProps;
}
return result as GetServerSidePropsResult;