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

Create abaquery forum #4446

Merged
merged 4 commits into from
Feb 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
16 changes: 16 additions & 0 deletions app/actions/ActionTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -408,3 +408,19 @@ export const Reaction = {
ADD: generateStatuses('Reaction.ADD') as AAT,
DELETE: generateStatuses('Reaction.DELETE') as AAT,
};

export const Forum = {
FETCH_ALL: generateStatuses('Forum.FETCH_ALL') as AAT,
CREATE: generateStatuses('Forum.CREATE') as AAT,
FETCH: generateStatuses('Forum. FETCH') as AAT,
DELETE: generateStatuses('Forum.DELETE') as AAT,
UPDATE: generateStatuses('Forum.UPDATE') as AAT,
};

export const Thread = {
FETCH_ALL: generateStatuses('Thread.FETCH_ALL') as AAT,
CREATE: generateStatuses('Thread.CREATE') as AAT,
FETCH: generateStatuses('Thread. FETCH') as AAT,
DELETE: generateStatuses('Thread.DELETE') as AAT,
UPDATE: generateStatuses('Thread.UPDATE') as AAT,
};
170 changes: 170 additions & 0 deletions app/actions/ForumActions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import { forumSchema, threadSchema } from 'app/reducers';
import { Forum, Thread } from './ActionTypes';
import callAPI from './callAPI';
import type { ID } from 'app/store/models';
import type {
CreateForum,
CreateThread,
DetailedForum,
DetailedThread,
PublicForum,
PublicThread,
UpdateForum,
UpdateThread,
} from 'app/store/models/Forum';

export function fetchForums() {
return callAPI<PublicForum[]>({
types: Forum.FETCH_ALL,
endpoint: '/forums/',
schema: [forumSchema],
method: 'GET',
meta: {
errorMessage: 'Henting av forum feilet',
},
propagateError: true,
requiresAuthentication: true,
});
}

export function fetchForum(forumId: ID) {
return callAPI<DetailedForum>({
types: Forum.FETCH,
endpoint: `/forums/${forumId}/`,
schema: forumSchema,
meta: {
errorMessage: 'Henting av forum feilet',
},
propagateError: true,
requiresAuthentication: true,
});
}

export function createForum(forum: CreateForum) {
return callAPI<DetailedForum>({
types: Forum.CREATE,
endpoint: '/forums/',
method: 'POST',
body: forum,
schema: forumSchema,
meta: {
errorMessage: 'Opprettelse av forum feilet',
},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any reason why you only added requiresAuthentication to the first two endpoints?

});
}

export function editForum(forum: UpdateForum) {
return callAPI({
types: Forum.UPDATE,
endpoint: `/forums/${forum.id}/`,
method: 'PUT',
body: { title: forum.title, description: forum.description },
meta: {
errorMessage: 'Endring av forum feilet',
},
});
}

export function deleteForum(forumId: ID) {
return callAPI({
types: Forum.DELETE,
endpoint: `/forums/${forumId}/`,
method: 'DELETE',
meta: {
id: forumId,
errorMessage: 'Sletting av forum feilet',
},
});
}

export function fetchThreads() {
return callAPI<PublicThread[]>({
types: Thread.FETCH_ALL,
endpoint: '/threads/',
schema: [threadSchema],
method: 'GET',
meta: {
errorMessage: 'Henting av tråder feilet',
},
propagateError: true,
});
}
Comment on lines +80 to +91
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Delete this?


export function fetchThreadsByForum(forumId: ID) {
return callAPI<PublicThread[]>({
types: Thread.FETCH_ALL,
endpoint: `/forums/${forumId}/threads/`,
schema: [threadSchema],
method: 'GET',
meta: {
errorMessage: 'Henting av tråder feilet',
},
propagateError: true,
});
}

export function fetchThread(threadId: ID) {
return callAPI<DetailedThread>({
types: Thread.FETCH,
endpoint: `/threads/${threadId}/`,
schema: threadSchema,
meta: {
errorMessage: 'Henting av tråd feilet',
},
propagateError: true,
});
}
Comment on lines +106 to +116
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Delete this?


export function fetchThreadByForum(forumId: ID, threadId: ID) {
return callAPI<PublicThread[]>({
types: Thread.FETCH,
endpoint: `/forums/${forumId}/threads/${threadId}`,
schema: threadSchema,
method: 'GET',
meta: {
errorMessage: 'Henting av tråder feilet',
},
propagateError: true,
});
}

export function createThread(thread: CreateThread) {
return callAPI<DetailedThread>({
types: Thread.CREATE,
endpoint: '/threads/',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
endpoint: '/threads/',
endpoint: `/forums/${forumId}/threads/`,

method: 'POST',
body: thread,
schema: threadSchema,
meta: {
errorMessage: 'Opprettelse av tråd feilet',
},
});
}

export function editThread(thread: UpdateThread) {
return callAPI({
types: Thread.UPDATE,
endpoint: `/threads/${thread.id}/`,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
endpoint: `/threads/${thread.id}/`,
endpoint: `/forums/${forumId}/threads/${threadId}`,

method: 'PUT',
body: {
title: thread.title,
content: thread.content,
forum: thread.forum,
},
meta: {
errorMessage: 'Endring av tråd feilet',
},
});
}

export function deleteThread(threadId: ID) {
return callAPI({
types: Thread.DELETE,
endpoint: `/threads/${threadId}/`,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
endpoint: `/threads/${threadId}/`,
endpoint: `/forums/${forumId}/threads/${threadId}`,

method: 'DELETE',
meta: {
id: threadId,
errorMessage: 'Sletting av tråd feilet',
},
});
}
12 changes: 11 additions & 1 deletion app/components/Comments/Comment.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Button, Flex, Icon } from '@webkom/lego-bricks';
import moment from 'moment';
import { useState } from 'react';
import { Link } from 'react-router-dom';
import { deleteComment } from 'app/actions/CommentActions';
Expand Down Expand Up @@ -60,7 +61,16 @@ const Comment = ({
/>
)}
</Flex>
<Time className={styles.timestamp} time={createdAt} wordsAgo />
<Tooltip
content={moment(createdAt).format('lll')}
placement="right"
>
<Time
className={styles.timestamp}
time={createdAt}
wordsAgo
/>
</Tooltip>
</Flex>
</Flex>

Expand Down
6 changes: 6 additions & 0 deletions app/components/Search/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ const LINKS: Array<Link> = [
icon: 'book-outline',
url: '/articles',
},
{
key: 'forum',
title: 'Forum',
icon: 'chatbubbles-outline',
url: '/forum',
},
{
key: 'events',
title: 'Arrangementer',
Expand Down
24 changes: 24 additions & 0 deletions app/reducers/forums.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { createSelector } from '@reduxjs/toolkit';
import { Forum } from 'app/actions/ActionTypes';
import createEntityReducer from 'app/utils/createEntityReducer';

export default createEntityReducer({
key: 'forums',
types: {
fetch: Forum.FETCH_ALL,
mutate: Forum.CREATE,
delete: Forum.DELETE,
},
});

export const selectForums = createSelector(
(state) => state.forums.byId,
(state) => state.forums.items,
(forumsById, forumsIds) => forumsIds.map((id) => forumsById[id])
);

export const selectForumsById = createSelector(
(state) => state.forums.byId,
(state, props) => props.forumId,
(forumsByid, forumId) => forumsByid[forumId]
);
6 changes: 6 additions & 0 deletions app/reducers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,3 +132,9 @@ export const followersCompanySchema = new schema.Entity(
export const followersUserSchema = new schema.Entity(followersKeyGen('user'), {
follower: userSchema,
});
export const threadSchema = new schema.Entity('threads', {
comments: [commentSchema],
});
export const forumSchema = new schema.Entity('forums', {
threads: [threadSchema],
});
56 changes: 56 additions & 0 deletions app/reducers/threads.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { createSelector } from '@reduxjs/toolkit';
import { Thread } from 'app/actions/ActionTypes';
import createEntityReducer from 'app/utils/createEntityReducer';
import { mutateComments, selectCommentEntities } from './comments';
import type { ID } from 'app/models';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
import type { ID } from 'app/models';
import type { ID } from 'app/store/models';

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this resolved?


const mutate = mutateComments('threads');

export default createEntityReducer({
key: 'threads',
types: {
fetch: Thread.FETCH_ALL,
mutate: Thread.CREATE,
delete: Thread.DELETE,
},
mutate,
});

export const selectThreadsByForumId = (forumId: ID) =>
createSelector(
(state) => state.threads.byId,
(state) => state.threads.items,
(threadsById, threadIds) => {
const filteredThreadIds = threadIds.filter((id) => {
const thread = threadsById[id];
return thread && thread.forum === forumId;
});

return filteredThreadIds.map((id) => threadsById[id]);
Comment on lines +21 to +29
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm just writing a suggestion out of the top of my head, so idk if it runs - but this is where I thought it would be beneficial to have the /forums/<id>/threads API structure

Suggested change
(state) => state.threads.byId,
(state) => state.threads.items,
(threadsById, threadIds) => {
const filteredThreadIds = threadIds.filter((id) => {
const thread = threadsById[id];
return thread && thread.forum === forumId;
});
return filteredThreadIds.map((id) => threadsById[id]);
(state) => state.forums.byId,
(state, props) => props.forumId,
(forumsById, forumId) => forumsById[forumId].threads

}
);

export const selectThreads = createSelector(
(state) => state.threads.byId,
(state) => state.threads.items,
(threadsById, threadsIds) => threadsById.map((id) => threadsIds[id])
);
Comment on lines +33 to +37
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like a funky way of doing it. I'd imagine it simpler to just add selectors that fetch threads from a given forum, instead of having to find the list of threads from the forum and then call the selector?


export const selectThreadsById = createSelector(
(state) => state.threads.byId,
(state, props) => props.threadId,
(threadsById, threadId) => threadsById[threadId]
);

export const selectCommentsByIds = createSelector(
[selectCommentEntities, (state, commentIds) => commentIds],
(comments, commentIds) => {
return commentIds?.reduce((acc, id) => {
const comment = comments[id];
if (comment) {
acc.push(comment);
}
return acc;
}, []);
}
);
56 changes: 56 additions & 0 deletions app/routes/forum/components/ForumDetail.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { usePreparedEffect } from '@webkom/react-prepare';
import { useParams } from 'react-router-dom';
import { fetchForum } from 'app/actions/ForumActions';
import { Content, ContentMain } from 'app/components/Content';
import NavigationTab, { NavigationLink } from 'app/components/NavigationTab';
import { selectForumsById } from 'app/reducers/forums';
import { useAppDispatch, useAppSelector } from 'app/store/hooks';
import ThreadList from './ThreadList';
import type { DetailedForum } from 'app/store/models/Forum';

const ForumDetail = () => {
const { forumId } = useParams<{ forumId: string }>();
const dispatch = useAppDispatch();

usePreparedEffect(
'fetchDetailForum',
() => forumId && dispatch(fetchForum(forumId)),
[forumId]
);

const forum: DetailedForum = useAppSelector((state) =>
selectForumsById(state, { forumId })
);
const detailActionGrant = forum?.actionGrant;

return (
forum &&
detailActionGrant && (
<Content>
<ContentMain>
<NavigationTab
back={{
label: 'Tilbake til liste',
path: '/forum',
}}
>
{detailActionGrant.includes('edit') && (
<NavigationLink to={`/forum/${forumId}/edit`}>
Rediger
</NavigationLink>
)}
<NavigationLink to={`/forum/${forumId}/new`}>
Oprett tråd
</NavigationLink>
</NavigationTab>

<h1>{forum.title}</h1>
<p className="secondaryFontColor">{forum.description}</p>
<ThreadList forumId={forumId} />
</ContentMain>
</Content>
)
);
};

export default ForumDetail;