From ece21d4c1d9d80cdbcad645dccc14002bc491d1a Mon Sep 17 00:00:00 2001 From: Justin Gasper Date: Wed, 7 Oct 2026 09:10:13 +1100 Subject: [PATCH 1/6] feat: share forum components and complete Markdown attachment editor --- src/apps/opportunities/README.md | 11 + .../opportunities/src/assets/forums/076a6.svg | 5 + .../opportunities/src/assets/forums/07c9e.svg | 5 + .../opportunities/src/assets/forums/0a628.svg | 5 + .../opportunities/src/assets/forums/0f9e6.svg | 5 + .../opportunities/src/assets/forums/171f8.svg | 5 + .../opportunities/src/assets/forums/1a172.svg | 5 + .../opportunities/src/assets/forums/1bd8b.svg | 5 + .../opportunities/src/assets/forums/1e461.svg | 5 + .../opportunities/src/assets/forums/1eecc.svg | 5 + .../opportunities/src/assets/forums/36954.svg | 5 + .../opportunities/src/assets/forums/381ef.svg | 5 + .../opportunities/src/assets/forums/3e0fd.svg | 5 + .../opportunities/src/assets/forums/3faa9.svg | 5 + .../opportunities/src/assets/forums/41360.svg | 5 + .../opportunities/src/assets/forums/42478.svg | 5 + .../opportunities/src/assets/forums/44ba2.svg | 5 + .../opportunities/src/assets/forums/476e5.svg | 5 + .../opportunities/src/assets/forums/4d85c.svg | 3 + .../opportunities/src/assets/forums/54374.svg | 5 + .../opportunities/src/assets/forums/5f27f.svg | 5 + .../opportunities/src/assets/forums/6292b.svg | 5 + .../opportunities/src/assets/forums/714f6.svg | 5 + .../opportunities/src/assets/forums/74494.svg | 5 + .../opportunities/src/assets/forums/78d47.svg | 5 + .../opportunities/src/assets/forums/7f1bc.svg | 5 + .../opportunities/src/assets/forums/84bb6.svg | 5 + .../opportunities/src/assets/forums/8ce62.svg | 5 + .../opportunities/src/assets/forums/8f583.svg | 5 + .../opportunities/src/assets/forums/9064c.svg | 5 + .../opportunities/src/assets/forums/90f38.svg | 5 + .../opportunities/src/assets/forums/94f98.svg | 5 + .../opportunities/src/assets/forums/a02e3.svg | 5 + .../opportunities/src/assets/forums/b697d.svg | 5 + .../opportunities/src/assets/forums/baad6.svg | 5 + .../opportunities/src/assets/forums/bad71.svg | 5 + .../opportunities/src/assets/forums/bfe3c.svg | 5 + .../opportunities/src/assets/forums/c7811.svg | 5 + .../opportunities/src/assets/forums/ce6d4.svg | 5 + .../opportunities/src/assets/forums/e203d.svg | 5 + .../opportunities/src/assets/forums/f14f0.svg | 5 + .../opportunities/src/assets/forums/f163b.svg | 5 + .../opportunities/src/assets/forums/f8147.svg | 5 + .../src/components/ChallengeForum.module.scss | 26 + .../src/components/ChallengeForum.spec.tsx | 24 + .../src/components/ChallengeForum.tsx | 674 +++++++++++------- .../src/components/ChallengeMarkdown.tsx | 5 + .../src/components/ForumIcon.tsx | 75 ++ .../src/components/ForumMarkdown.tsx | 10 + .../opportunities/src/models/forum.models.ts | 6 +- .../forum-attachments.service.spec.ts | 28 + .../src/services/forum-attachments.service.ts | 27 + .../src/services/forum.service.ts | 7 +- 53 files changed, 835 insertions(+), 266 deletions(-) create mode 100644 src/apps/opportunities/src/assets/forums/076a6.svg create mode 100644 src/apps/opportunities/src/assets/forums/07c9e.svg create mode 100644 src/apps/opportunities/src/assets/forums/0a628.svg create mode 100644 src/apps/opportunities/src/assets/forums/0f9e6.svg create mode 100644 src/apps/opportunities/src/assets/forums/171f8.svg create mode 100644 src/apps/opportunities/src/assets/forums/1a172.svg create mode 100644 src/apps/opportunities/src/assets/forums/1bd8b.svg create mode 100644 src/apps/opportunities/src/assets/forums/1e461.svg create mode 100644 src/apps/opportunities/src/assets/forums/1eecc.svg create mode 100644 src/apps/opportunities/src/assets/forums/36954.svg create mode 100644 src/apps/opportunities/src/assets/forums/381ef.svg create mode 100644 src/apps/opportunities/src/assets/forums/3e0fd.svg create mode 100644 src/apps/opportunities/src/assets/forums/3faa9.svg create mode 100644 src/apps/opportunities/src/assets/forums/41360.svg create mode 100644 src/apps/opportunities/src/assets/forums/42478.svg create mode 100644 src/apps/opportunities/src/assets/forums/44ba2.svg create mode 100644 src/apps/opportunities/src/assets/forums/476e5.svg create mode 100644 src/apps/opportunities/src/assets/forums/4d85c.svg create mode 100644 src/apps/opportunities/src/assets/forums/54374.svg create mode 100644 src/apps/opportunities/src/assets/forums/5f27f.svg create mode 100644 src/apps/opportunities/src/assets/forums/6292b.svg create mode 100644 src/apps/opportunities/src/assets/forums/714f6.svg create mode 100644 src/apps/opportunities/src/assets/forums/74494.svg create mode 100644 src/apps/opportunities/src/assets/forums/78d47.svg create mode 100644 src/apps/opportunities/src/assets/forums/7f1bc.svg create mode 100644 src/apps/opportunities/src/assets/forums/84bb6.svg create mode 100644 src/apps/opportunities/src/assets/forums/8ce62.svg create mode 100644 src/apps/opportunities/src/assets/forums/8f583.svg create mode 100644 src/apps/opportunities/src/assets/forums/9064c.svg create mode 100644 src/apps/opportunities/src/assets/forums/90f38.svg create mode 100644 src/apps/opportunities/src/assets/forums/94f98.svg create mode 100644 src/apps/opportunities/src/assets/forums/a02e3.svg create mode 100644 src/apps/opportunities/src/assets/forums/b697d.svg create mode 100644 src/apps/opportunities/src/assets/forums/baad6.svg create mode 100644 src/apps/opportunities/src/assets/forums/bad71.svg create mode 100644 src/apps/opportunities/src/assets/forums/bfe3c.svg create mode 100644 src/apps/opportunities/src/assets/forums/c7811.svg create mode 100644 src/apps/opportunities/src/assets/forums/ce6d4.svg create mode 100644 src/apps/opportunities/src/assets/forums/e203d.svg create mode 100644 src/apps/opportunities/src/assets/forums/f14f0.svg create mode 100644 src/apps/opportunities/src/assets/forums/f163b.svg create mode 100644 src/apps/opportunities/src/assets/forums/f8147.svg create mode 100644 src/apps/opportunities/src/components/ForumIcon.tsx create mode 100644 src/apps/opportunities/src/services/forum-attachments.service.spec.ts create mode 100644 src/apps/opportunities/src/services/forum-attachments.service.ts diff --git a/src/apps/opportunities/README.md b/src/apps/opportunities/README.md index a679b71b0..d2f3b5b58 100644 --- a/src/apps/opportunities/README.md +++ b/src/apps/opportunities/README.md @@ -761,3 +761,14 @@ disabled presentation. Fun-challenge prize copy is centered and wraps inside the prize card at every supported width. The prize summary uses the light second- and third-place card illustrations, while Winners continues to use the dark podium medals. + + +### Shared public forums + +The [Forums app](../forums/README.md) reuses the challenge forum member, topic, +post and Markdown editor components. `ForumTopicView` accepts optional +`contentOnly`, `onSignIn` and `onWatch` props for the public page layout and guest +login handoff. Topic detail may include API `permissions`; an explicit denied +reply permission hides the composer. Existing challenge callers need no changes. +The shared toolbar includes configured Filestack attachments through +`uploadForumAttachment`, preserving Markdown links and rated @mentions. diff --git a/src/apps/opportunities/src/assets/forums/076a6.svg b/src/apps/opportunities/src/assets/forums/076a6.svg new file mode 100644 index 000000000..417d8bbd0 --- /dev/null +++ b/src/apps/opportunities/src/assets/forums/076a6.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/apps/opportunities/src/assets/forums/07c9e.svg b/src/apps/opportunities/src/assets/forums/07c9e.svg new file mode 100644 index 000000000..1135f08b4 --- /dev/null +++ b/src/apps/opportunities/src/assets/forums/07c9e.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/apps/opportunities/src/assets/forums/0a628.svg b/src/apps/opportunities/src/assets/forums/0a628.svg new file mode 100644 index 000000000..2febbc06f --- /dev/null +++ b/src/apps/opportunities/src/assets/forums/0a628.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/apps/opportunities/src/assets/forums/0f9e6.svg b/src/apps/opportunities/src/assets/forums/0f9e6.svg new file mode 100644 index 000000000..288e3851d --- /dev/null +++ b/src/apps/opportunities/src/assets/forums/0f9e6.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/apps/opportunities/src/assets/forums/171f8.svg b/src/apps/opportunities/src/assets/forums/171f8.svg new file mode 100644 index 000000000..68297dafb --- /dev/null +++ b/src/apps/opportunities/src/assets/forums/171f8.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/apps/opportunities/src/assets/forums/1a172.svg b/src/apps/opportunities/src/assets/forums/1a172.svg new file mode 100644 index 000000000..eb5f26a12 --- /dev/null +++ b/src/apps/opportunities/src/assets/forums/1a172.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/apps/opportunities/src/assets/forums/1bd8b.svg b/src/apps/opportunities/src/assets/forums/1bd8b.svg new file mode 100644 index 000000000..c7e4c2cb8 --- /dev/null +++ b/src/apps/opportunities/src/assets/forums/1bd8b.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/apps/opportunities/src/assets/forums/1e461.svg b/src/apps/opportunities/src/assets/forums/1e461.svg new file mode 100644 index 000000000..ff09fff9f --- /dev/null +++ b/src/apps/opportunities/src/assets/forums/1e461.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/apps/opportunities/src/assets/forums/1eecc.svg b/src/apps/opportunities/src/assets/forums/1eecc.svg new file mode 100644 index 000000000..e4e1f395d --- /dev/null +++ b/src/apps/opportunities/src/assets/forums/1eecc.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/apps/opportunities/src/assets/forums/36954.svg b/src/apps/opportunities/src/assets/forums/36954.svg new file mode 100644 index 000000000..726c320c7 --- /dev/null +++ b/src/apps/opportunities/src/assets/forums/36954.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/apps/opportunities/src/assets/forums/381ef.svg b/src/apps/opportunities/src/assets/forums/381ef.svg new file mode 100644 index 000000000..079898403 --- /dev/null +++ b/src/apps/opportunities/src/assets/forums/381ef.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/apps/opportunities/src/assets/forums/3e0fd.svg b/src/apps/opportunities/src/assets/forums/3e0fd.svg new file mode 100644 index 000000000..0446b6aa2 --- /dev/null +++ b/src/apps/opportunities/src/assets/forums/3e0fd.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/apps/opportunities/src/assets/forums/3faa9.svg b/src/apps/opportunities/src/assets/forums/3faa9.svg new file mode 100644 index 000000000..10033142f --- /dev/null +++ b/src/apps/opportunities/src/assets/forums/3faa9.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/apps/opportunities/src/assets/forums/41360.svg b/src/apps/opportunities/src/assets/forums/41360.svg new file mode 100644 index 000000000..da93756ed --- /dev/null +++ b/src/apps/opportunities/src/assets/forums/41360.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/apps/opportunities/src/assets/forums/42478.svg b/src/apps/opportunities/src/assets/forums/42478.svg new file mode 100644 index 000000000..a7d37db15 --- /dev/null +++ b/src/apps/opportunities/src/assets/forums/42478.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/apps/opportunities/src/assets/forums/44ba2.svg b/src/apps/opportunities/src/assets/forums/44ba2.svg new file mode 100644 index 000000000..1fdc6e6b2 --- /dev/null +++ b/src/apps/opportunities/src/assets/forums/44ba2.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/apps/opportunities/src/assets/forums/476e5.svg b/src/apps/opportunities/src/assets/forums/476e5.svg new file mode 100644 index 000000000..a44918e71 --- /dev/null +++ b/src/apps/opportunities/src/assets/forums/476e5.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/apps/opportunities/src/assets/forums/4d85c.svg b/src/apps/opportunities/src/assets/forums/4d85c.svg new file mode 100644 index 000000000..66f60323d --- /dev/null +++ b/src/apps/opportunities/src/assets/forums/4d85c.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/apps/opportunities/src/assets/forums/54374.svg b/src/apps/opportunities/src/assets/forums/54374.svg new file mode 100644 index 000000000..e2dc3baca --- /dev/null +++ b/src/apps/opportunities/src/assets/forums/54374.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/apps/opportunities/src/assets/forums/5f27f.svg b/src/apps/opportunities/src/assets/forums/5f27f.svg new file mode 100644 index 000000000..b73055496 --- /dev/null +++ b/src/apps/opportunities/src/assets/forums/5f27f.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/apps/opportunities/src/assets/forums/6292b.svg b/src/apps/opportunities/src/assets/forums/6292b.svg new file mode 100644 index 000000000..a5093305c --- /dev/null +++ b/src/apps/opportunities/src/assets/forums/6292b.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/apps/opportunities/src/assets/forums/714f6.svg b/src/apps/opportunities/src/assets/forums/714f6.svg new file mode 100644 index 000000000..728818434 --- /dev/null +++ b/src/apps/opportunities/src/assets/forums/714f6.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/apps/opportunities/src/assets/forums/74494.svg b/src/apps/opportunities/src/assets/forums/74494.svg new file mode 100644 index 000000000..2febbc06f --- /dev/null +++ b/src/apps/opportunities/src/assets/forums/74494.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/apps/opportunities/src/assets/forums/78d47.svg b/src/apps/opportunities/src/assets/forums/78d47.svg new file mode 100644 index 000000000..d70e1b234 --- /dev/null +++ b/src/apps/opportunities/src/assets/forums/78d47.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/apps/opportunities/src/assets/forums/7f1bc.svg b/src/apps/opportunities/src/assets/forums/7f1bc.svg new file mode 100644 index 000000000..e20d8a7ed --- /dev/null +++ b/src/apps/opportunities/src/assets/forums/7f1bc.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/apps/opportunities/src/assets/forums/84bb6.svg b/src/apps/opportunities/src/assets/forums/84bb6.svg new file mode 100644 index 000000000..c9f276c4d --- /dev/null +++ b/src/apps/opportunities/src/assets/forums/84bb6.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/apps/opportunities/src/assets/forums/8ce62.svg b/src/apps/opportunities/src/assets/forums/8ce62.svg new file mode 100644 index 000000000..a0182d2d4 --- /dev/null +++ b/src/apps/opportunities/src/assets/forums/8ce62.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/apps/opportunities/src/assets/forums/8f583.svg b/src/apps/opportunities/src/assets/forums/8f583.svg new file mode 100644 index 000000000..2da55971a --- /dev/null +++ b/src/apps/opportunities/src/assets/forums/8f583.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/apps/opportunities/src/assets/forums/9064c.svg b/src/apps/opportunities/src/assets/forums/9064c.svg new file mode 100644 index 000000000..4b6befcb0 --- /dev/null +++ b/src/apps/opportunities/src/assets/forums/9064c.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/apps/opportunities/src/assets/forums/90f38.svg b/src/apps/opportunities/src/assets/forums/90f38.svg new file mode 100644 index 000000000..fbf5db3da --- /dev/null +++ b/src/apps/opportunities/src/assets/forums/90f38.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/apps/opportunities/src/assets/forums/94f98.svg b/src/apps/opportunities/src/assets/forums/94f98.svg new file mode 100644 index 000000000..99e6d35cf --- /dev/null +++ b/src/apps/opportunities/src/assets/forums/94f98.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/apps/opportunities/src/assets/forums/a02e3.svg b/src/apps/opportunities/src/assets/forums/a02e3.svg new file mode 100644 index 000000000..8ca6452e4 --- /dev/null +++ b/src/apps/opportunities/src/assets/forums/a02e3.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/apps/opportunities/src/assets/forums/b697d.svg b/src/apps/opportunities/src/assets/forums/b697d.svg new file mode 100644 index 000000000..c9bccb00f --- /dev/null +++ b/src/apps/opportunities/src/assets/forums/b697d.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/apps/opportunities/src/assets/forums/baad6.svg b/src/apps/opportunities/src/assets/forums/baad6.svg new file mode 100644 index 000000000..b0d578f89 --- /dev/null +++ b/src/apps/opportunities/src/assets/forums/baad6.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/apps/opportunities/src/assets/forums/bad71.svg b/src/apps/opportunities/src/assets/forums/bad71.svg new file mode 100644 index 000000000..020d4283d --- /dev/null +++ b/src/apps/opportunities/src/assets/forums/bad71.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/apps/opportunities/src/assets/forums/bfe3c.svg b/src/apps/opportunities/src/assets/forums/bfe3c.svg new file mode 100644 index 000000000..0da9efb30 --- /dev/null +++ b/src/apps/opportunities/src/assets/forums/bfe3c.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/apps/opportunities/src/assets/forums/c7811.svg b/src/apps/opportunities/src/assets/forums/c7811.svg new file mode 100644 index 000000000..5b2fc8189 --- /dev/null +++ b/src/apps/opportunities/src/assets/forums/c7811.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/apps/opportunities/src/assets/forums/ce6d4.svg b/src/apps/opportunities/src/assets/forums/ce6d4.svg new file mode 100644 index 000000000..5b33f6329 --- /dev/null +++ b/src/apps/opportunities/src/assets/forums/ce6d4.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/apps/opportunities/src/assets/forums/e203d.svg b/src/apps/opportunities/src/assets/forums/e203d.svg new file mode 100644 index 000000000..e394081a2 --- /dev/null +++ b/src/apps/opportunities/src/assets/forums/e203d.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/apps/opportunities/src/assets/forums/f14f0.svg b/src/apps/opportunities/src/assets/forums/f14f0.svg new file mode 100644 index 000000000..9691f9b38 --- /dev/null +++ b/src/apps/opportunities/src/assets/forums/f14f0.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/apps/opportunities/src/assets/forums/f163b.svg b/src/apps/opportunities/src/assets/forums/f163b.svg new file mode 100644 index 000000000..df2f491dc --- /dev/null +++ b/src/apps/opportunities/src/assets/forums/f163b.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/apps/opportunities/src/assets/forums/f8147.svg b/src/apps/opportunities/src/assets/forums/f8147.svg new file mode 100644 index 000000000..03d696f30 --- /dev/null +++ b/src/apps/opportunities/src/assets/forums/f8147.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/apps/opportunities/src/components/ChallengeForum.module.scss b/src/apps/opportunities/src/components/ChallengeForum.module.scss index c56031c04..f2b6d01f1 100644 --- a/src/apps/opportunities/src/components/ChallengeForum.module.scss +++ b/src/apps/opportunities/src/components/ChallengeForum.module.scss @@ -1594,3 +1594,29 @@ position: relative; z-index: 2; } + +/* Public forum pages supply the shared thread header/sidebar at page level. */ +.publicDetailLayout { + display: block; + min-width: 0; +} + +// Shared Figma editor controls retain intrinsic SVG dimensions. +.editorToolbar { + min-height: 38px; + padding: 0; + button { border-right: 0; height: 38px; min-width: 38px; padding: 7px; } + select { background: #fff; border: 0; border-left: 1px solid #e0e0e0; border-right: 1px solid #e0e0e0; + color: #6f6f6f; font: 14px/20px 'Nunito Sans', sans-serif; height: 38px; padding: 0 8px; } +} +.expandedEditor { background: white; inset: 24px; overflow: auto; padding: 24px; position: fixed; z-index: 1000; + textarea { min-height: 65vh; } +} +.publicDetailLayout { + .replyPost { margin-left: 40px; } + .post > header, .replyPost > header { margin-bottom: 24px; } + .editorShell { border-color: #e0e0e0; border-radius: 2px; } + .markdownField > label { display: none; } + .postActions { gap: 24px; } + .postActions button { gap: 4px; } +} diff --git a/src/apps/opportunities/src/components/ChallengeForum.spec.tsx b/src/apps/opportunities/src/components/ChallengeForum.spec.tsx index 4bd6d986d..c52a2f6b4 100644 --- a/src/apps/opportunities/src/components/ChallengeForum.spec.tsx +++ b/src/apps/opportunities/src/components/ChallengeForum.spec.tsx @@ -19,6 +19,7 @@ import { } from '../models' import { ChallengeForum, + ForumTopicView, continueMarkdownList, flattenForumPosts, forumRatingClass, @@ -48,6 +49,8 @@ jest.mock('~/libs/core', () => ({ .getRatingColor, }), { virtual: true }) +jest.mock('../services/forum-attachments.service', () => ({ uploadForumAttachment: jest.fn() })) + jest.mock('swr', () => ({ __esModule: true, default: (...args: unknown[]) => mockUseSWR(...args), @@ -260,6 +263,27 @@ describe('ChallengeForum', () => { }) }) + it('keeps guest threads read-only and routes replies and reactions to sign in', () => { + const onSignIn = jest.fn() + render() + expect(screen.queryByRole('button', { name: 'Post comment' })).not.toBeInTheDocument() + fireEvent.click(screen.getAllByRole('button', { name: 'Reply' })[0]) + fireEvent.click(screen.getAllByRole('button', { name: /Add thumbs up/ })[0]) + expect(onSignIn) + .toHaveBeenCalledTimes(2) + expect(mockCreateForumPost).not.toHaveBeenCalled() + expect(mockSetForumPostReaction).not.toHaveBeenCalled() + }) + it('renders Figma topic controls, truthful metrics, local search, and filters', () => { render() diff --git a/src/apps/opportunities/src/components/ChallengeForum.tsx b/src/apps/opportunities/src/components/ChallengeForum.tsx index 6e76323c0..15d1dae43 100644 --- a/src/apps/opportunities/src/components/ChallengeForum.tsx +++ b/src/apps/opportunities/src/components/ChallengeForum.tsx @@ -1,17 +1,10 @@ /* eslint-disable no-use-before-define, ordered-imports/ordered-imports, react/jsx-no-bind */ -import { - ChangeEvent, - FC, - FormEvent, - KeyboardEvent, - useMemo, - useRef, - useState, -} from 'react' +import { ChangeEvent, FC, FormEvent, Fragment, KeyboardEvent, useMemo, useRef, useState } from 'react' import useSWR, { SWRResponse } from 'swr' import { BaseModal, ConfirmModal, IconOutline } from '~/libs/ui' -import { ReactComponent as DeleteIcon } from '../assets/submission-delete.svg' +import { uploadForumAttachment } from '../services/forum-attachments.service' +import { ForumIcon } from './ForumIcon' import { ChallengeOpportunity, @@ -38,10 +31,7 @@ import { } from '../services' import { OpportunityTabLoading } from './OpportunityTabLoading' -import { - challengeForumUrl, - memberProfileUrl, -} from '../utils' +import { challengeForumUrl, memberProfileUrl } from '../utils' import { formatOpportunityDateTime } from '../utils/opportunity-date.utils' import { ForumMarkdown } from './ForumMarkdown' import { ForumMentionTextarea } from './ForumMentionTextarea' @@ -146,15 +136,13 @@ export function formatForumDate(value?: string): string { export function flattenForumPosts(posts: ForumPost[], depth: number = 0): FlatForumPost[] { return [...posts] .sort((left, right) => { - const createdAtDelta = new Date(left.createdAt) - .getTime() - new Date(right.createdAt) - .getTime() + const createdAtDelta + = new Date(left.createdAt) + .getTime() - new Date(right.createdAt) + .getTime() return createdAtDelta || left.id.localeCompare(right.id) }) - .flatMap(post => [ - { depth, post }, - ...flattenForumPosts(post.replies ?? [], depth + 1), - ]) + .flatMap(post => [{ depth, post }, ...flattenForumPosts(post.replies ?? [], depth + 1)]) } /** @@ -222,9 +210,11 @@ export function forumRatingClass(maxRating?: number): ForumRatingClass { * @throws Does not throw. */ export function forumErrorMessage(error: unknown): string { - const responseMessage = (error as { - response?: { data?: { message?: string | string[] } } - })?.response?.data?.message + const responseMessage = ( + error as { + response?: { data?: { message?: string | string[] } } + } + )?.response?.data?.message if (Array.isArray(responseMessage)) return responseMessage.join(' ') if (typeof responseMessage === 'string' && responseMessage.trim()) return responseMessage if (error instanceof Error && error.message.trim()) return error.message @@ -266,9 +256,11 @@ function topicParticipants(topic: ForumTopicSummary): ForumParticipant[] { : undefined, ].filter((participant): participant is ForumParticipant => !!participant) - return participants.filter((participant, index) => ( - participants.findIndex(candidate => candidate.memberId === participant.memberId) === index - )) + return participants.filter( + (participant, index) => participants.findIndex( + candidate => candidate.memberId === participant.memberId, + ) === index, + ) } /** @@ -278,7 +270,7 @@ function topicParticipants(topic: ForumTopicSummary): ForumParticipant[] { * @returns profile link with avatar and rating-colored canonical handle. * @throws Does not throw. */ -const ForumMember: FC<{ +export const ForumMember: FC<{ handle: string profile?: MemberProfileSummary }> = props => { @@ -288,7 +280,9 @@ const ForumMember: FC<{ return ( - {handle} + + {handle} + ) } @@ -300,14 +294,14 @@ const ForumMember: FC<{ * @returns accessible linked avatar group. * @throws Does not throw. */ -const ParticipantGroup: FC<{ +export const ParticipantGroup: FC<{ participants: ForumParticipant[] profilesByMemberId: MemberProfilesById total: number }> = props => { - const labels = props.participants.map(participant => ( - props.profilesByMemberId.get(participant.memberId)?.handle ?? participant.handle - )) + const labels = props.participants.map( + participant => props.profilesByMemberId.get(participant.memberId)?.handle ?? participant.handle, + ) const overflow = Math.max(0, props.total - props.participants.length) return ( @@ -318,7 +312,11 @@ const ParticipantGroup: FC<{ return ( - + ) })} @@ -347,7 +345,7 @@ interface ForumFallbackProps { */ const ForumFallback: FC = props => (
-
@@ -602,7 +602,7 @@ export const ForumTopicCard: FC<{ )}
- Last post at + {'Last post at '} {formatForumDate(props.topic.latestActivity?.createdAt)}
@@ -642,12 +642,14 @@ export const ForumTopicCard: FC<{ {' '} {props.topic.viewsCount ?? 0}

- Participants - +
+ Participants + +
) @@ -1250,17 +1252,19 @@ const ForumPostCard: FC<{ {post.authorPostsCount ?? 0} {' '} - posts + {post.authorPostsCount === 1 ? 'post' : 'posts'}
{!post.deleted && Date.parse(post.updatedAt) > Date.parse(post.createdAt) && ( )} @@ -1314,6 +1318,7 @@ const ForumPostCard: FC<{ {post.thumbsDownCount ?? 0} + {!props.detail.topic.locked && props.canReply !== false && ( <>