Compare commits

...

252 Commits

Author SHA1 Message Date
Awais Ansari
e216d847eb feat: added cohort update option in post edit 2025-12-18 16:39:24 +05:00
Awais Ansari
911b8b3fc5 fix: cohort name difference in filter and post view (#841)
* fix: cohort name difference in filter and post view

* test: fix cohort test cases

---------

Co-authored-by: Awais  Ansari <awais.ansari@A006-01824.local>
2025-12-16 20:07:40 +05:00
Awais Ansari
4917da3245 fix: fetch topics on MFE load (#840)
* fix: added fetch topic API call in learners tab

* refactor: fetch topics on MFE load

* test: added legacy param in legacy topic test cases

---------

Co-authored-by: Awais  Ansari <awais.ansari@A006-01824.local>
2025-12-16 19:39:36 +05:00
renovate[bot]
e5388690b2 fix(deps): update dependency core-js to v3.47.0 2025-12-10 12:05:24 +05:00
edX requirements bot
cefc8d9d35 chore: enable github action auto update in dependabot.yml (#737) 2025-12-10 10:40:23 +05:00
renovate[bot]
f5c5913d3f chore(deps): update dependency glob to v7.2.3 2025-12-09 20:50:15 +00:00
renovate[bot]
687dae6b21 fix(deps): update dependency tinymce to v5.10.9 (#587)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-09 15:26:31 +00:00
renovate[bot]
b812b96d77 fix(deps): update dependency formik to v2.4.9 2025-12-09 14:59:27 +00:00
renovate[bot]
142abd8dd4 fix(deps): update dependency @edx/openedx-atlas to ^0.7.0 (#815)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-09 14:47:28 +00:00
renovate[bot]
ece4432f58 chore(deps): update dependency @edx/browserslist-config to v1.5.0 (#747)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-09 14:17:40 +00:00
renovate[bot]
c599046813 chore(deps): update dependency axios to ^0.30.0 (#780)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-09 14:13:55 +00:00
renovate[bot]
c323c80bc8 chore(deps): update actions/checkout action to v6 (#833)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-09 19:02:49 +05:00
renovate[bot]
37cec76dcb chore(deps): update dependency @openedx/paragon to v23.18.1 (#814)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-09 18:50:31 +05:00
Awais Ansari
00440fc15a chore: update header version to 8 (#829) 2025-11-14 15:31:25 +05:00
Tobias Macey
a4826ae62d fix: prevent generatePath error when author is invalid in AuthorLabel (#821)
Wrap learnerPostsLink creation in useMemo with guard to prevent
'Missing :learnerUsername param' error. The generatePath function
was being called unconditionally during render even when the link
wouldn't be displayed, causing errors when author was null, undefined,
or the 'anonymous' string.

The fix ensures generatePath is only called when showUserNameAsLink
is true, which validates that author is a valid username.
2025-11-13 16:08:51 -05:00
renovate[bot]
16c49b2404 chore(deps): update dependency @edx/frontend-platform to v8.5.2 (#775)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-13 14:36:31 -05:00
Ehtesham Alam
231c15aa6d Merge pull request #808 from Alam-2U/ealam/LP-85
fix: resolve InContext Sidebar post menu dropdown clipping that triggered scroll
2025-10-06 19:30:21 +05:00
Kyle McCormick
4d51cf8855 revert: feat: added bulk delete user posts feature for privileged users (#818)
This reverts commit 909d133acc.

See https://github.com/openedx/edx-platform/issues/37402 for details

We leave the original PR's changes to Confirmation.jsx in place
because they are OK and are now tangled in with recent changes
and more difficult to revert.
2025-09-29 18:29:43 -04:00
Feanil Patel
df53c7cff8 build: remove unused @edx/reactifex package (#813)
* build: remove unused @edx/reactifex package

Remove @edx/reactifex package from devDependencies as it is no longer
needed. Translation extraction functionality has been verified to work
correctly without this dependency.

Co-Authored-By: Claude <noreply@anthropic.com>

* build: Drop the push_translations target.

That work is now done by the openedx-translations repo.

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Adolfo R. Brandes <adolfo@axim.org>
2025-09-26 14:22:10 -03:00
bydawen
902490c6e3 test: Remove support for Node 20 (#811)
Co-authored-by: Peter Kulko <93188219+PKulkoRaccoonGang@users.noreply.github.com>
2025-09-26 09:39:21 -03:00
vladislavkeblysh
899d1fafcd feat: Profile image on user posts (#574)
* feat: add env variable to display image

* feat: refactor after review, updated tests
2025-09-25 23:09:37 +05:00
oleksandr.buhaienko
7ca3d9bc32 build: Upgrade to Node 24 2025-09-23 15:46:55 -03:00
bydawen
d4a6c20075 test: Add Node 24 to CI matrix (#809) 2025-09-19 13:53:29 -04:00
Samuel Allan
3e1c95319a fix: update frontend-build to fix install issues (#806)
Earlier versions of @openedx/frontend-build used on older version of
'sharp', which caused intermittent installation issues. The version of
'sharp' was updated in @openedx/frontend-build to fix these issues, so
the frontend-build version can be updated here, to fix the issues in
this project too. See
https://github.com/openedx/frontend-build/issues/664 and
https://github.com/openedx/frontend-build/pull/665 for more information.

The frontend-build dependency was updated by:

```
npm install --package-lock-only @openedx/frontend-build
```

Private-ref: https://tasks.opencraft.com/browse/BB-9953
2025-09-05 11:57:49 -06:00
Diana Villalvazo
5bef624714 refactor: Replace injectIntl with the useIntl() hook (#798)
* refactor: Replace of injectIntl with useIntl

* test: improve coverage
2025-08-26 14:58:53 -04:00
Ahtisham Shahid
28b1b1973b feat: updated v2 captcha to v3 in post editor (#803)
* feat: updated v2 captcha to v3 in post editor

* feat: added google captcha v3 for comment

* test: added test cases

* test: added test case to update the post

* test: updated test case for preview node

* test: updated test case for comment error

* test: removed mock file

* fix: removed comments

---------

Co-authored-by: sundasnoreen12 <sundasnoreen12@gmail.com>
2025-08-22 16:54:52 +05:00
Eemaan Amir
76fabbf7a6 feat: added confirm email banner for unverified users (#801)
* feat: added confirm email banner for unverified users

* test: improved test coverage

* refactor: refactored code
2025-08-14 22:32:40 +05:00
sundasnoreen12
5d13622172 fix: make learner name capitalized (#800)
* fix: make learner name capitalized

* test: fixed test cases
2025-08-13 13:40:28 +05:00
sundasnoreen12
9e7b7ae7bb feat: added tooltips for like and follow button (#799)
* feat: added tooltips for like and follow button

* test: added test cases for like and follow buttons
2025-08-13 00:00:32 +05:00
sundasnoreen12
ec800dd048 fix: fixed rate limit dialogue for edit contents (#797) 2025-08-05 15:57:40 +05:00
sundasnoreen12
a0269115b4 feat: added rate limit dialogue (#796)
* feat: added rate limit dialogue

* test: added test cases for post comment

* test: added test for content creation dialogue

* test: added test cases for empty topics

* test: added test cases

* feat: added content rate limit dialogue on post API for post and comment

* fix: fixed editor close issue on submit

* test: addd test cases
2025-08-05 12:31:12 +05:00
sundasnoreen12
3b527d9e60 feat: added tooltip for author role (#795) 2025-07-30 15:37:58 +05:00
sundasnoreen12
d36c4af4e9 feat: captcha only for learners (#792)
* feat: captcha only for learnerS

* test: fixed test cases

* test: fixed test cases for post editor
2025-07-28 21:53:22 +05:00
sundasnoreen12
fa772053c4 fix: fixed email confirmation params issue (#791)
* fix: fixed email confirmation params issue
2025-07-23 20:32:11 +05:00
sundasnoreen12
76da74ae20 feat: Modified discussions FE so that ONLY users with verified emails (#789)
* feat: Modified discussions FE so that ONLY users with verified emails can create content

* feat: added comment and response functionality

* test: fixed test cases

* refactor: refactor code and added HOC

* test: added test cases

* test: added test cases for failed and denied

* feat: added states for button

* refactor: added callback function

* test: added test case to close the dialogue

* refactor: refactor function
2025-07-23 17:24:57 +05:00
Eemaan Amir
750720f648 fix: fixed disappering modal description while deleting (#790) 2025-07-23 12:16:31 +05:00
Eemaan Amir
909d133acc feat: added bulk delete user posts feature for privileged users (#788)
* feat: added bulk delete user posts feature for privileged users
2025-07-22 19:09:48 +05:00
Brayan Cerón
3cda02be76 fix: add replace prop to fallback route (#782)
This to avoid adding unnecesary logs to the history, so back button goes previous page properly
2025-07-21 16:01:19 -04:00
sundasnoreen12
806c989eac fix: fixed captcha issue with response (#787)
* fix: fixed captcha issue with response

* fix: fixed test case
2025-07-17 19:41:19 +05:00
Ahtisham Shahid
2241575cc0 feat: added captcha to discussion post creation (#785)
* feat: added captcha to discussion post creation

* feat: added captcha for comment and response

* fix: removed learner check

* test: fixed test cases

* fix: removed comment and added check for empty sitekey

* fix: fixed translation issue

* test: added test cases for recaptcha

* test: should allow posting a comment with CAPTCHA

* test: added submit post test cases

* test: test edge cases for api

* test: added test cases for react-google-recaptcha

* test: added test case for default values for captcha

* fix: removed unused catch

---------

Co-authored-by: sundasnoreen12 <sundasnoreen12@gmail.com>
Co-authored-by: Awais Ansari <awais.ansari63@gmail.com>
2025-07-17 18:55:16 +05:00
sundasnoreen12
8618e8cfe9 fix: fixed active state border issue for add a new post button (#784) 2025-07-15 14:05:42 +05:00
sundasnoreen12
3b7239d72c feat: added product tour to notify all learners (#783)
* feat: added product tour to notify all learners

* fix: removed unused function
2025-07-03 17:10:09 +05:00
ayesha waris
7ebdf1be3e feat: Add notify all learners discussion post checkbox (#779)
* feat: Add notify all learners discussion post checkbox

* refactor: refactored post editor

* test: added test cases

* fix: fixed lint errors

---------

Co-authored-by: Hassan Raza <h.raza@arbisoft.com>
Co-authored-by: Ayesha Waris <ayesha.waris@192.168.10.6>
2025-06-27 19:42:39 +05:00
Eemaan Amir
5d75e0361d fix: fixed camel case issue so profile image shows for all users (#781) 2025-06-24 13:54:34 +05:00
Eemaan Amir
edd3f73211 feat: made user profile image visible (#778)
* feat: made user profile image visible

* refactor: updated selector name

* refactor: updated selector name
2025-06-20 12:18:53 +05:00
Brian Smith
33375a51e0 feat!: add design tokens support (#777)
BREAKING CHANGE: Pre-design-tokens theming is no longer supported.

Co-authored-by: Diana Olarte <dcoa@live.com>
2025-06-18 15:45:26 -04:00
renovate[bot]
ac471e2dd7 fix(deps): update dependency @edx/frontend-platform to v8.3.7 (#768)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-19 09:44:05 -04:00
Zameel Hassan
f04429f6f7 fix: add null check for post objects in usePostList hook (#752)
Adds defensive null checks when accessing post properties in the posts
forEach loop to prevent potential errors in the MFE discussion sidebar.
This addresses the issue reported in #751.
2025-05-12 18:47:17 +05:00
Brian Smith
bad12462f5 feat: import FooterSlot from component package instead of slot package (#765) 2025-04-24 11:50:49 -04:00
renovate[bot]
ec915f622b fix(deps): update dependency @edx/frontend-component-header to v6.4.0 (#766)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-23 20:15:13 +00:00
Régis Behmo
60da5eafc4 chore: remove husky 🪓🐶 (#761) 2025-04-09 14:53:26 -04:00
Hunia Fatima
05cf174335 feat: upgrade react to v18 (#759) 2025-04-09 10:11:28 -04:00
Brian Smith
ff72dab001 chore(deps): update @openedx dependencies to versions that support React 18 (#758) 2025-03-27 16:16:13 -04:00
Sarina Canelake
c38887ec2b docs: Update edx.rtd links to docs.openedx.org 2025-03-25 14:42:44 -03:00
Feanil Patel
58aa512f47 Merge pull request #749 from salman2013/salman/update-catalog-info-file
Update catalog-info file for release data
2025-01-17 11:13:22 -05:00
salman2013
62a5c11f52 chore: Update catalog-info file for release data and remove openedx.yaml file 2025-01-15 16:54:09 +05:00
Ihor Romaniuk
3ef8515891 fix: block overflow when editing comment (#706) 2024-12-10 12:19:40 +05:00
Farhaan Bukhsh
3cc39d83c4 fix: Adds a fix to remove "Add a post" button when discussion is restricted (#742)
"Add a post" button  was visible even though the banner says that posting is
restricted. This change helps in removing the button when posting is restricted.

Signed-off-by: Farhaan Bukhsh <farhaan@opencraft.com>
2024-11-21 18:11:30 +05:30
Brian Smith
af6cd1853c revert: revert: "test: Remove support for Node 18 (#736)" (#740) (#744)
This reverts commit 472bbe2d96.
2024-11-01 09:38:17 -04:00
Brian Smith
79a2fa404b feat(deps): update header to 5.6.0 (#741) 2024-10-22 19:19:10 -04:00
Brian Smith
472bbe2d96 Revert "test: Remove support for Node 18 (#736)" (#740)
This reverts commit dc5f097736. Node 18 removal PRs should be merged after Sumac is cut.
2024-10-22 13:55:44 -04:00
Bilal Qamar
dc5f097736 test: Remove support for Node 18 (#736) 2024-09-10 14:38:24 +05:00
Bilal Qamar
5e8c8254b4 build: Upgrade to Node 20 (#734)
* feat: updated node to v20

* refactor: updated package-lock along with ci & lockfile version workflows

* refactor: updated lockfile version workflow

* refactor: updated package-lock
2024-09-03 12:21:05 -04:00
Bilal Qamar
0d6692cf8c test: Add Node 20 to CI matrix (#735) 2024-08-22 14:37:56 -04:00
sundasnoreen12
3391e966f3 feat: added help section for post documentation (#733)
* feat: added help section for post documentation

* refactor: refactor code
2024-08-08 18:13:08 +05:00
Bilal Qamar
4297a96102 feat: updated frontend-build & frontend-platform major versions (#626)
* chore: bumped jest to v29

* refactor: updated frontend-build

* refactor: updated package-lock

* feat: updated build and platform major versions, along with edx packages

* refactor: updated package-lock

* refactor: updated package-lock
2024-08-02 16:32:34 +05:00
sundasnoreen12
db883ca7cd feat: added draft functionality for comment and responses (#727)
* feat: added draft functionality for comment and responses

* fix: fixed comment update issue:

* test: added draft test case

* test: added mock conditions for tinymce

* refactor: refactor code

* test: added test cases

* refactor: refactor hook file

* refactor: fixed review issues

* refactor: memoize function

* refactor: refactor code

* test: added update comment test case

* refactor: refactor remove hook method

* test: fixed test cases issue
2024-07-24 17:24:23 +05:00
ayesha waris
422fbf6173 fix: fixed author liking its own post (#720) 2024-06-26 17:13:52 +05:00
Ahtisham Shahid
e862ee6fb1 fix: post editor breaking for moderator (#717)
fix: updated unit tests

fix: updated unit tests
2024-06-21 12:29:27 +05:00
Adolfo R. Brandes
eeae6d45ce build: Update codecov and use token
Update codecov to the latest version and start using the org-wide token for uploads.

See https://github.com/openedx/wg-frontend/issues/179
2024-06-17 12:02:50 -03:00
ayesha waris
71b88bcea3 fix: fixed sidebar inconsistent font size (#716) 2024-06-11 18:25:33 +05:00
Ahtisham Shahid
c808069fe1 Revert "feat: updated course config api version (#702)" (#715)
This reverts commit 8d86e6dcc0.
2024-06-11 15:25:41 +05:00
ayesha waris
b9543c6d9c fix: fixed font-size of load more buttons and add a post heading (#708)
* fix: fixed font-size of load more buttons and add a post heading

* fix: fixed font-size of time stamps

* fix: fixed width of actions dropdown modal
2024-05-28 18:13:03 +05:00
ayesha waris
a545d0b9f6 fix: responsiveness of MFE discussions (#697)
* fix: fixed sidebar xl screen width

* fix: fixed failing test runs

* refactor: removed unused css classes
2024-05-27 16:48:53 +05:00
Ahtisham Shahid
8d86e6dcc0 feat: updated course config api version (#702) 2024-05-24 05:38:30 -04:00
sundasnoreen12
37781566f5 fix: fix redirection to new tab issue (#704) 2024-05-22 12:59:45 +05:00
Brian Smith
50948acfeb feat: import FooterSlot from frontend-slot-footer package 2024-05-17 09:39:00 -03:00
Awais Ansari
4de1011780 Revert "feat: use frontend-plugin-framework to provide a FooterSlot" (#700)
This reverts commit d7474782b4.
2024-05-13 18:16:56 +05:00
Brian Smith
d7474782b4 feat: use frontend-plugin-framework to provide a FooterSlot 2024-05-10 11:21:26 -03:00
ayesha waris
e1c78dda6e fix: fixed limited content visibility due to oversized sidebar (#692)
* fix: fixed limited content visibility due to oversized sidebar

* refactor: changed names of constants

---------

Co-authored-by: Awais Ansari <79941147+awais-ansari@users.noreply.github.com>
2024-04-08 23:43:24 +05:00
ayesha waris
f282da52c1 fix: fixed commentsInThreads[s] is not iterable (#693) 2024-04-08 22:48:27 +05:00
sundasnoreen12
d7fcc86847 fix: fixed api calling issues for admin (#691)
* fix: fixed api calling issues for admin

* test: fixed test case

* refactor: fixed review issue

---------

Co-authored-by: Awais Ansari <79941147+awais-ansari@users.noreply.github.com>
2024-04-05 16:48:36 +05:00
Danyal Faheem
c0873df575 fix: render favicon and siteName in title (#668)
* fix: render favicon and siteName in title

* fix: remove translation files
2024-04-05 16:09:53 +05:00
Stanislav
12fbe7eebd fix: Discussions UI fixes on mobile resolutions (#689) 2024-04-04 17:46:01 +05:00
sundasnoreen12
7db4fde252 feat: restricted unnecessary api calls (#683)
* feat: restricted unnecessary api calls

* fix: fixed content unavailable issue for user admin

* refactor: refactor code for course status
2024-04-04 12:42:37 +05:00
sundasnoreen12
4914f51b6e fix: fixed crash issue for post in learner tab (#684) 2024-03-28 13:30:50 +05:00
ayesha waris
80073e3f83 feat: updated redux structure using updated comments api (#670)
* feat: updated redux structure and commentsview component

* test: fixed test cases

* fix: fixed lint error
2024-03-25 15:09:22 +05:00
sundasnoreen12
3aacdda7a1 feat: remove Transifex calls for OEP-58 for discussion MFE (#682) 2024-03-21 00:19:56 +05:00
Brian Smith
1a2068d52f chore(deps): update paragon and frontend-build to openedx scope 2024-02-28 12:55:13 -03:00
Awais Ansari
3a7b7054e7 fix: user content unavailable content issue for learner (#674) 2024-02-28 18:04:02 +05:00
sundasnoreen12
6875165eb3 fix: now content unavailable ui is only for learner (#672)
Co-authored-by: Awais Ansari <79941147+awais-ansari@users.noreply.github.com>
2024-02-27 14:25:40 +05:00
Awais Ansari
8fc666500a feat: add auto scroll to editor functionality (#671)
* feat: add auto scroll to editor functionality

* test: mocked scrollIntoView function in ThreadView test cases
2024-02-27 13:01:52 +05:00
sundasnoreen12
ddb6c96f1d fix: table is now showing properly in preview and post lists (#669)
* fix: table is now showing properly in preview and post lists

* refactor: added paragon color code
2024-02-23 21:14:38 +05:00
sundasnoreen12
ac17fd7294 feat: implemented error handling mockup (#663)
* feat: implemented error handling mockup

* fix: fixed incontextsidebar issues

* fix: fixed discussion home  test cases

* fix: fixed dicussion home test cases

* refactor: added  code review fixes

---------

Co-authored-by: Awais Ansari <79941147+awais-ansari@users.noreply.github.com>
2024-02-21 14:37:37 +05:00
ayesha waris
f0a4586eed feat: modified TA icon according to role (#665)
* feat: modified TA icon according to role

* fix: fixed icon and tooltip position

* fix: fixed failing testcase

* refactor: removed duplication

* test: added testcases to check for updated user labels

* refactor: updated variables names for clarity

* refactor: moved authorLabel selection logic to utils

---------

Co-authored-by: sohailfatima <23100065@lums.edu.pk>
2024-02-15 18:12:20 +05:00
ayesha waris
9eaed2b873 fix: fixed question icon and its position (#662)
* fix: fixed question icon background for read questions

* fix: fixed question icon position in post detailed view

---------

Co-authored-by: sohailfatima <23100065@lums.edu.pk>
2024-02-12 19:38:57 +05:00
Omar Al-Ithawi
71a18c532e feat: tutor-mfe compatiblilty for atlas pull (#660)
- install atlas
 - remove `--filter` to pull all languages by default
 - use ATLAS_OPTIONS to allow custom `--filter`
 - include frontend-platform in `atlas pull`
2024-02-02 16:58:53 -05:00
ayesha waris
e845804cce fix: fixed redirection to learners tab in inContext view (#659)
* fix: redirection to learners tab in inContext view

* fix: changed username to simple text for incontext view

* test: username is not clickable in incontext view

---------

Co-authored-by: sohailfatima <23100065@lums.edu.pk>
Co-authored-by: Fatima Sohail <68312464+sohailfatima@users.noreply.github.com>
Co-authored-by: Awais Ansari <79941147+awais-ansari@users.noreply.github.com>
2024-01-31 18:18:54 +05:00
ayesha waris
f69b2c118f chore: removed eslint-disable statements (#658)
* chore: removed eslint-disable statements

* refactor: removed unnecessary files and unintentional eslint-disable statements

* refactor: removed eslint import/no-cycle error

* fix: failing testcase

* style: removed comments

* fix: failing testcase

---------

Co-authored-by: sohailfatima <23100065@lums.edu.pk>
Co-authored-by: Fatima Sohail <68312464+sohailfatima@users.noreply.github.com>
2024-01-31 16:54:03 +05:00
ayesha waris
88a985da35 fix: updated css styling to prevent content spilling (#655)
* fix: updated css styling to prevent content spilling

* style: small property change

---------

Co-authored-by: eemaanamir <eemaan.amir@gmail.com>
2024-01-30 12:59:35 +05:00
Jenkins
3aef3b0c7e chore(i18n): update translations 2024-01-28 15:22:39 -05:00
sundasnoreen12
095d4296e3 fix: fixed Learner Post filter issue (#651)
* fix: fixed Learner Post filter issue

* refactor: removed different  variable declaration

---------

Co-authored-by: Awais Ansari <79941147+awais-ansari@users.noreply.github.com>
2024-01-25 18:56:25 +05:00
Muhammad Adeel Tajamul
fa8357035b fix: anonymous post should not be visible in my post (#654) 2024-01-25 14:48:25 +05:00
Jenkins
bb341df70e chore(i18n): update translations 2024-01-21 15:22:37 -05:00
sundasnoreen12
ffb386472d fix: sort menu collapse after filter selection (#645)
* fix: sort menu collapse after filter selection

* refactor: filter menu collapse logic

---------

Co-authored-by: Awais Ansari <awais.ansari63@gmail.com>
2024-01-17 16:28:54 +05:00
sundasnoreen12
5df51f2389 fix: fixed hover color issue throughout MFE (#644)
* fix: fixed hover color issue throughout MFE

* fix: added border around add comment button

* fix: removed add button radius
2024-01-16 12:12:02 +05:00
renovate[bot]
92adec3a2a chore(deps): update actions/setup-node action to v4 (#643)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-15 21:54:15 +05:00
renovate[bot]
ce786af2dc chore(deps): update actions/checkout action to v4 (#642)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-15 21:44:15 +05:00
Jenkins
4dbb4f5fea chore(i18n): update translations 2024-01-14 15:22:33 -05:00
Awais Ansari
733046f852 fix: fixed post card border color issue (#640) 2024-01-11 16:27:11 +05:00
renovate[bot]
6cacde4367 fix(deps): update dependency yup to v0.32.11 (#632)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-09 16:55:32 +05:00
renovate[bot]
8015f6c1c0 fix(deps): update dependency classnames to v2.5.1 (#636)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-09 16:47:33 +05:00
Awais Ansari
035d766886 feat: removed sort and filter tour (#638) 2024-01-09 12:30:18 +05:00
ayesha waris
ee61d1c95d chore: removed enable_learners_tab_in_discussions_mfe flag dependency (#637) 2024-01-05 17:38:34 +05:00
vladislavkeblysh
9e95458168 feat: Editor bar visibility (#582)
* feat: fixed editor bar visibility

* feat: fixed editor bar visibility

* feat: fixed z index
2024-01-05 01:24:12 +05:00
sundasnoreen12
b467298d9a fix: fixed UI issues of discussion for incontext sidebar (#633)
* fix: fixed UI issues of discussion for incontext sidebar

* refactor: added paragon class

* refactor: improved actionBar UI

---------

Co-authored-by: Awais Ansari <awais.ansari63@gmail.com>
2024-01-02 13:35:47 +05:00
renovate[bot]
b5d036a54d fix(deps): update dependency regenerator-runtime to v0.14.1 2023-12-18 15:05:01 +00:00
renovate[bot]
bc997108ef chore(deps): update dependency @edx/frontend-build to v13.0.14 (#628)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-18 18:33:51 +05:00
vladislavkeblysh
6ae5130c14 feat: fixed page styles (#577) 2023-12-18 16:26:38 +05:00
Jenkins
67d79cb3aa chore(i18n): update translations 2023-12-17 15:22:26 -05:00
renovate[bot]
f31a0e71f3 fix(deps): update dependency formik to v2.4.5 (#619)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-14 15:15:42 +05:00
renovate[bot]
9761787c89 fix(deps): update dependency @reduxjs/toolkit to v1.9.7 (#616)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-14 12:30:12 +05:00
renovate[bot]
e5a21f4a75 chore(deps): update dependency @edx/frontend-build to v13.0.12 (#624)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-12 14:16:54 +05:00
renovate[bot]
1d89e9556a fix(deps): update dependency @edx/frontend-component-footer to v12.6.1 (#625)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-12 14:11:01 +05:00
Syed Ali Abbas Zaidi
b35632df64 feat: upgrade react router to v6 (#542)
* feat: upgrade react router to v6

* fix: routing issues

* fix: category route should redirect to all posts

* fix: path error on routes
2023-12-07 18:10:48 +05:00
Kshitij Sobti
b36c0266fd fix: null error at useRouteMatch when running on tutor (#613)
tutor sets the PUBLIC_PATH to '/discussions' which causes frontend-platform to
treat all URLs for matching etc to be relative to this path. Since many places
include '/discussions' in the match it causes those matches to break.

This change makes the default PUBLIC_PATH in .env.development to match the one
set by tutor and removes it from the base path of the router letting frontend
platform handle the prefix.

This also allows for deployments to customise this path to be something other
than 'discussions'.
2023-12-06 17:20:28 +05:00
renovate[bot]
0d5df18ab2 fix(deps): update dependency redux to v4.2.1 (#621)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-05 12:21:46 +05:00
renovate[bot]
c61435546d fix(deps): update dependency regenerator-runtime to v0.14.0 (#622)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-05 12:03:29 +05:00
renovate[bot]
df4a3c2a73 chore(deps): update dependency rosie to v2.1.1 (#605)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-04 16:15:31 +05:00
renovate[bot]
ac635edcb8 chore(deps): update dependency @edx/frontend-build to v13.0.8 (#608)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-04 13:07:44 +05:00
renovate[bot]
c4f7115732 fix(deps): update dependency @edx/frontend-component-footer to v12.6.0 (#609)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-04 13:01:08 +05:00
renovate[bot]
5cc8ba43fe fix(deps): update dependency @edx/frontend-component-header to v4.10.1 (#610)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-04 12:51:30 +05:00
renovate[bot]
68505821bb fix(deps): update dependency @edx/paragon to v20.46.3 (#611)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-04 11:51:58 +05:00
Ahtisham Shahid
c6d953fe7b feat: removed enable_moderation_reason_codes flag (#615)
* chore: removed deprecated flag

fix: resolved linter error

fix: changed workflow

fix: changed workflow

fix: changed workflow

fix: changed workflow

fix: changed workflow

fix: changed workflow

fix: changed workflow

fix: changed workflow

fix: changed workflow

fix: changed workflow

fix: changed workflow

fix: changed workflow

* test: fixed postEditor test case

---------

Co-authored-by: Awais Ansari <awais.ansari63@gmail.com>
2023-12-03 22:49:08 +05:00
Jenkins
a479f5ae5b chore(i18n): update translations 2023-11-19 15:22:18 -05:00
Ihor Romaniuk
48b2b9de64 fix: container indents and style imports (#600) 2023-11-14 12:07:47 +05:00
renovate[bot]
352fa0eacf chore(deps): update dependency @edx/frontend-build to v13.0.5 (#604)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-13 14:45:32 +05:00
Mashal Malik
6930e90c78 refactor: update lock file version (#559) 2023-11-08 18:36:07 +05:00
Ihor Romaniuk
4994de9615 fix: unify font-family with paragon component styles (#598) 2023-11-08 16:43:18 +05:00
renovate[bot]
6d90da7aa2 chore(deps): update dependency @testing-library/jest-dom to v5.17.0 (#588)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-08 00:04:27 +05:00
Awais Ansari
bcb43dfdff fix: resolved load more posts delay issue (#602) 2023-11-07 11:48:19 +05:00
renovate[bot]
7d7221b144 fix(deps): update dependency @edx/frontend-component-footer to v12.5.1 (#590)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-06 18:30:33 +05:00
renovate[bot]
7cd93bd8d2 fix(deps): update dependency @edx/frontend-component-header to v4.9.1 (#594)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-06 18:15:15 +05:00
Jenkins
6afa9840aa chore(i18n): update translations 2023-11-05 15:23:37 -05:00
renovate[bot]
46ddd6d885 chore(deps): update dependency @edx/reactifex to v1.1.0 (#580)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-03 14:41:08 +05:00
renovate[bot]
5bd15655f6 chore(deps): update dependency axios-mock-adapter to v1.22.0 (#589)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-03 14:27:33 +05:00
Muhammad Abdullah Waheed
27f73e3c23 feat: babel-plugin-react-intl to babel-plugin-formatjs migration (#572) 2023-11-01 12:02:57 +05:00
Mashal Malik
67700e8974 refactor: updated README file to reflect template changes (#592) 2023-10-31 17:30:05 +05:00
Sarina Canelake
1cb8ad3018 Merge pull request #591 from openedx/feanil/update_security_emails
docs: Update the security e-mail address.
2023-10-30 18:04:44 -04:00
Feanil Patel
4068b9e46a docs: Update the security e-mail address.
This repository is now managed by the Axim Collaborative and security issues
with it should be reported to security@openedx.org instead of security@edx.org

This work is being done as a part of https://github.com/openedx/wg-security/issues/16
2023-10-30 17:33:10 -04:00
Emad Rad
5db3a18cb7 fix: Persian language (#555)
* fix: corrected typos

comfirm -> confirm
varaint -> variant
Privilaged -> Privileged
courseare, coursweare  -> courseware
Discssion -> Discussion
dimentions -> dimensions

* refactor: clean up language codes
2023-10-24 14:29:09 +05:00
Feanil Patel
3a6a783f21 chore: Update to the new version of brand-openedx in the new scope. (#585)
Part of https://github.com/openedx/axim-engineering/issues/23

This updates the `@edx/brand` alias to point to the `brand-openedx` package at
the `openedx` scope. This does not impact imports because this package is used
via an alias.
2023-10-20 15:59:01 -04:00
renovate[bot]
69e0689ab9 fix(deps): update dependency @edx/frontend-platform to v4.6.3 (#566)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-12 16:18:23 +05:00
renovate[bot]
f795d9f836 fix(deps): update dependency tinymce to v5.10.7 (#570)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-12 14:57:00 +05:00
renovate[bot]
68c18526fb fix(deps): update dependency classnames to v2.3.2 (#568)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-11 18:10:40 +05:00
renovate[bot]
f49f272afa chore(deps): update dependency @edx/browserslist-config to v1.2.0 (#571)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-11 14:06:11 +05:00
Jenkins
67212254f7 chore(i18n): update translations 2023-09-24 16:27:33 -04:00
sundasnoreen12
af5b10a575 fix: now discussion sidebar modal appears above the fold (#563)
Co-authored-by: SundasNoreen <sundas.noreen@arbisoft.com>
2023-09-15 17:25:04 +05:00
Jenkins
fb2be35d00 chore(i18n): update translations 2023-09-03 16:32:24 -04:00
Jenkins
10adf1171b chore(i18n): update translations 2023-08-27 16:27:23 -04:00
Awais Ansari
2609380bd8 fix: moved feedback widget behind the env variable (#557)
* feat: remove InformationBanner from Discussion MFE

* fix: moved feedback widget behind the env variable
2023-08-21 14:49:35 +05:00
Jenkins
61d0f9a7ea chore(i18n): update translations 2023-08-13 16:27:20 -04:00
sundasnoreen12
ea235cf6ca fix: fixed leak issue when checkpoint will be undefined (#553)
Co-authored-by: SundasNoreen <sundas.noreen@arbisoft.com>
Co-authored-by: Awais Ansari <79941147+awais-ansari@users.noreply.github.com>
2023-08-08 14:32:34 +05:00
Jenkins
b8f11c3046 chore(i18n): update translations 2023-08-06 16:32:19 -04:00
Jenkins
1769692d22 chore(i18n): update translations 2023-07-30 16:27:15 -04:00
Awais Ansari
3d6b71c247 fix: learning header constant height (#551) 2023-07-26 14:42:07 +05:00
Awais Ansari
bd42521f6b style: add important in post type card border (#550) 2023-07-18 14:23:27 +05:00
edX requirements bot
445caca4e4 Merge pull request #540 from DmytroAlipov/fix-discussion-search
Fix bug with a repeated search query
2023-07-13 06:04:53 -04:00
alipov_d
4a2b32494d fix: issue with a repeated search query 2023-07-12 18:19:56 +02:00
Mashal Malik
a16bd783a0 build: update react-redux (#549) 2023-07-12 19:37:03 +05:00
Mashal Malik
df1a16ee85 feat: update react & react-dom to v17 (#537)
* feat: update react & react-dom to v17

* build: update pkgs

* fix: fix test

* build: remove ^ from pkgs

---------

Co-authored-by: Bilal Qamar <59555732+BilalQamar95@users.noreply.github.com>
2023-07-12 15:04:13 +05:00
Jenkins
656935336e chore(i18n): update translations 2023-07-09 16:27:12 -04:00
sundasnoreen12
2498f74556 chore: added renovate file structure based on provided template (#546)
Co-authored-by: SundasNoreen <sundas.noreen@arbisoft.com>
2023-07-06 15:09:03 +05:00
ayesha waris
99ad3aff53 test: fixed postcommentsview test cases (#543)
* test: fixed postcommentsview test cases

* test: fixed hovercard failed tests
2023-06-21 18:21:11 +05:00
Awais Ansari
e2bb68a1cd chore: update codecov ref from edx to openedx (#544)
* chore: update codecov ref from edx to openedx

* refactor: fix typo
2023-06-20 17:50:42 +05:00
Jenkins
f694b480b5 chore(i18n): update translations 2023-06-18 16:27:07 -04:00
Dmytro
228a771a39 fix: error 400 editing comment (#533) 2023-06-12 17:03:45 +05:00
edX requirements bot
c821033a64 Merge pull request #505 from igobranco/igobranco/new-translation-languages
chore(i18n): add languages
2023-06-12 06:04:52 -04:00
Ivo Branco
7ce4566df3 chore(i18n): add languages
Add new languages: pt-PT, uk, ru,hi, cs, es-AR, es-ES, fa-IR
2023-06-12 10:57:05 +01:00
Jenkins
a02771f96f chore(i18n): update translations 2023-06-11 16:27:04 -04:00
ayesha waris
8c53a7a19e feat: integrated backend discussions restriction with MFE (#529)
* feat: integrated backend discussions restriction with MFE

* test: fixes failed test cases

* refactor: fixed lint issues

---------

Co-authored-by: ayesha waris <73840786+ayeshoali@users.noreply.github.com>
Co-authored-by: SundasNoreen <sundas.noreen@arbisoft.com>
2023-06-06 14:18:53 +05:00
Eugene Dyudyunov
c8500a0c1e fix: post sharing URL (#445) 2023-05-31 14:01:12 +05:00
Jenkins
f7ad94997d chore(i18n): update translations 2023-05-28 16:26:59 -04:00
Omar Al-Ithawi
733a74d9e4 feat: use atlas in make pull_translations (#502)
Changes
-------
 - Move all i18n imports into `src/i18n/index.js` so `intl-imports.js` can
   override it with latest translations
 - Add `atlas` into `make pull_translations` when `OPENEDX_ATLAS_PULL`
   environment variable is set.

Refs: [FC-0012 project](https://openedx.atlassian.net/l/cp/XGS0iCcQ) implementing Translation Infrastructure OEP-58.
2023-05-25 18:38:29 +05:00
Bilal Qamar
b2b33b76f7 feat: upgraded to node v18, added .nvmrc and updated workflows (#471)
* feat: upgraded to node v18, added .nvmrc and updated workflows

* refactor: updated packages

* refactor: resolved eslint issues
2023-05-25 13:16:53 +05:00
Bilal Qamar
70f6541585 build: edx namespace packages upgrade & resolved respective eslint issue (#508)
* refactor: updated frontend-build, frontend-platform, header & footer packages

* fix: resolved eslint issues post frontend-build upgrade

* refactor: resolved eslint issues

* refactor: pinned frontend-build & changed suggested function definitions
2023-05-24 11:55:28 +05:00
Muhammad Adeel Tajamul
822854953f fix: switch to use PUBLIC_PATH for routes (#525) 2023-05-23 12:21:25 +05:00
Awais Ansari
0854ee3a8b test: resolved console warnings in test cases (#523)
* test: resolve TopicsView test cases console errors

* test: resolve EmptyPage test cases console errors

* test: resolve inContext TopicsView test cases console errors

* test: resolve LearnerPostsView test cases console errors

* test: resolved TopicStats component console erros
2023-05-22 17:20:40 +05:00
ayesha waris
7aaa1fbd93 test: fixed test cases after MFE optimization (#522)
* test: fixed test cases after optimization

* test: fixed HoverCard and PostCommentsView failed test cases

* test: fixed PostCommentsView failed test cases

* test: remove spinner wait from PostCommentsView test case

* test: add await in PostCommentsView test cases

* refactor: updated variable name in HoverCard test cases

---------

Co-authored-by: ayesha waris <73840786+ayeshoali@users.noreply.github.com>
Co-authored-by: Awais Ansari <awais.ansari63@gmail.com>
2023-05-22 16:02:09 +05:00
Awais Ansari
4f0b5f9c3d test: fixed test cases after MFE optimization (#521)
* test: fixed ActionDropDown test cases

* test: fixed AlertBanner and EndorsedAlertBanner test cases

* test: fixed LearnerFooter test cases

* test: fixed LearnersView failed test cases

* test: fixed PostFooter failed test cases

* test: fixed PostLink failed test cases

* test: fixed LearnerPostsView failed test cases

* test: fixed console error and warnings in PostEditor

* test: fixed Post anonymously test condition
2023-05-16 14:45:40 +05:00
Awais Ansari
6d6c61dec2 fix: direct link thread no found issue (#516) 2023-05-10 15:01:28 +05:00
Awais Ansari
84e18b9ed6 fix: after optimization issues (#515)
* style: fix height and width style issues

* fix: endorse and answericon issue

* fix: like and unlike isssue

* chore: remove profiler configuration
2023-05-08 19:38:54 +05:00
Awais Ansari
3dc7f74fa4 fix: postLink footer icon size (#514) 2023-05-08 18:15:53 +05:00
Awais Ansari
0844ee6875 Perf: improved discussions MFE's components re-rendering and loading time (#513)
* chore: configure WDYR for react profiling

* perf: reduced post content re-rendering

* perf: post content view and it child optimization

* perf: add memoization in post editor

* perf: add memoization in postCommnetsView

* perf: improved endorsed comment view rendering

* perf: improved re-rendering in reply component

* fix: uncomment questionType commentsView

* fix: removed console errors in postContent area

* perf: reduced postType and postId dependancy

* perf: improved re-rendering in discussionHome

* perf: improved re-rendering of postsList and its child components

* perf: improved re-rendering of legacyTopic and learner sidebar

* fix: postFilterBar filter was not updating

* fix: resolve duplicate comment posts issue

* fix: memory leaking issue in comments view

* fix: duplicate topic posts in inContext sidebar

* perf: add lazy loading

* chore: remove WDYR configuration

* fix: alert banner padding

* chore: update package-lock file

* fix: bind tour API call with buttons
2023-05-08 16:21:29 +05:00
Awais Ansari
7b7c249abd Revert "Perf: improved discussions MFE's components re-rendering and loading time (#485)" (#512)
This reverts commit 59b4366edd.
2023-05-08 15:34:43 +05:00
Awais Ansari
59b4366edd Perf: improved discussions MFE's components re-rendering and loading time (#485)
* chore: configure WDYR for react profiling

* perf: reduced post content re-rendering

* perf: post content view and it child optimization

* perf: add memoization in post editor

* perf: add memoization in postCommnetsView

* perf: improved endorsed comment view rendering

* perf: improved re-rendering in reply component

* fix: uncomment questionType commentsView

* fix: removed console errors in postContent area

* perf: reduced postType and postId dependancy

* perf: improved re-rendering in discussionHome

* perf: improved re-rendering of postsList and its child components

* perf: improved re-rendering of legacyTopic and learner sidebar

* fix: postFilterBar filter was not updating

* fix: resolve duplicate comment posts issue

* fix: memory leaking issue in comments view

* fix: duplicate topic posts in inContext sidebar

* perf: add lazy loading

* chore: remove WDYR configuration

* fix: alert banner padding

* chore: update package-lock file
2023-05-08 15:14:53 +05:00
Awais Ansari
8e68d3ab60 chore: console react version (#511) 2023-05-08 14:53:05 +05:00
Awais Ansari
7ceaea1fc1 chore: enable profiler on production build (#510) 2023-05-08 12:28:38 +05:00
Awais Ansari
2bf608655c chore: enable profiler on production for data collection (#509) 2023-05-05 16:42:36 +05:00
Jenkins
59349f48bd chore(i18n): update translations 2023-04-30 16:26:56 -04:00
Awais Ansari
1eb4c653c5 Revert "[FC-0014] update frontend-platform version to v4.2.0" (#506) 2023-04-28 17:36:42 +05:00
Adolfo R. Brandes
7ef4d2dec7 Merge pull request #501 from raccoongang/sagirov/tCRIL_GA-58
[FC-0014] update frontend-platform version to v4.2.0
2023-04-27 13:53:35 -03:00
Sagirov Eugeniy
0871217f95 chore: update frontend-platform version to v4.2.0 2023-04-27 13:08:48 +03:00
sundasnoreen12
ee382b70af fix: action dropdown UI issues (#500)
* fix: action dropdown UI issues

* refactor: fixed comment sort dropdown issue

---------

Co-authored-by: sundasnoreen12 <sundasnoreen12@ggmail.com>
2023-04-20 17:05:31 +05:00
sundasnoreen12
f1a9922d29 fix: added role for editedby and closedby user (#498)
* fix: changed the title of new edx provider

* fix: added role for editedby and closedby user

* refactor: added left margin for endorsed time

* refactor: added AlertBar component to avoid duplicate code

---------

Co-authored-by: sundasnoreen12 <sundasnoreen12@ggmail.com>
2023-04-19 15:58:10 +05:00
sundasnoreen12
009417bb57 fix: fixed feedback widget position issue (#499)
Co-authored-by: sundasnoreen12 <sundasnoreen12@ggmail.com>
2023-04-17 13:04:21 +05:00
Muhammad Adeel Tajamul
f13b34c6c7 fix: updated post actions dropdown design (#495)
Co-authored-by: adeel.tajamul <adeel.tajamul@arbisoft.com>
2023-04-07 14:30:54 +05:00
ayesha waris
1ba5b938c4 fix: typeset failed: Cannot read properties of undefined (#496) 2023-04-05 17:22:19 +05:00
sundasnoreen12
c1478dbb41 fix: fixed dynamic learner profile issue (#493)
* fix: fixed dynamic URL issue

* refactor: removed unused selectors

---------

Co-authored-by: sundasnoreen12 <sundasnoreen12@ggmail.com>
2023-04-05 16:21:25 +05:00
sundasnoreen12
5cc5156b2b test: added test cases of navigation bar (#487) (#489)
* test: added test cases of navigation bar

* test: added test case for navigation bar api

---------

Co-authored-by: ayesha waris <73840786+ayeshoali@users.noreply.github.com>
Co-authored-by: sundasnoreen12 <sundasnoreen12@ggmail.com>
2023-04-04 16:22:13 +05:00
ayesha waris
8c28d46dce fix: typeset failed: window.MathJax.typesetClear is not a function (#494) 2023-04-04 15:48:42 +05:00
ayesha waris
c16843fd4c temp: fix tends to resolve increased script error (#492) 2023-04-04 12:09:49 +05:00
ayesha waris
1117ed0387 fix: changes empty state image and text to monochrome (#490) 2023-04-03 13:51:48 +05:00
Jenkins
242740da23 chore(i18n): update translations 2023-04-02 16:26:50 -04:00
ayesha waris
2013dcf7cb style: tooltips aligned to the side in topic stats (#488) 2023-03-30 14:59:16 +05:00
ayesha waris
3cdf0825b4 fix: removes outline around comment cards (#487) 2023-03-29 16:48:59 +05:00
ayesha waris
d0d80a2b17 fix: removes double line between tabs (#486) 2023-03-29 15:34:06 +05:00
Ahtisham Shahid
5240d0d5a4 feat: added support for dual feedback forms (#478)
* feat: added support for dual feedback forms

* fix: resolved linter errors
2023-03-29 14:08:24 +05:00
ayesha waris
b171de291e fix: lock is used for post close or reopen (#484) 2023-03-29 12:57:18 +05:00
ayesha waris
24163d15c0 fix: hover color removed when filter buttons are clicked (#483) 2023-03-28 21:00:50 +05:00
ayesha waris
58b6b69cb0 fix: typeset failed: window.MathJax.typesetClear is not a function (#482) 2023-03-28 16:30:34 +05:00
ayesha waris
025edf7b66 temp: fix for typeset failed:e.typesetPromise is not a function (#479) 2023-03-27 14:56:40 +05:00
sundasnoreen12
1e47d102a3 test: added newly cases to cover codeCov (#474)
* test: added newly cases to cover codeCov

* test: added new cases of empty posts

* test: added test cases of legacy topics

* refactor: removed extra lines and extra const objects

* test: added test cases for postPreviewpane

* test: added test cases for post comment view api

* refactor: removed extra lines and code

* refactor: fixed issues identified during code review

* refactor: changed description of one test case

---------

Co-authored-by: sundasnoreen12 <sundasnoreen12@ggmail.com>
2023-03-27 14:05:40 +05:00
ayesha waris
39da42ee3f fix: fix post height and remove overstate from author label (#472)
* fix: fixed post length according to figma

* fix: remove hoverstate from author label and author icon

* style: adds tooltip in hovercard for endorsemment icon

* test: fix test cases

* test: adds test cases for descreaased coverage

* refactor: updated api call to a method for reusability

* refactor: changed test descriptions
2023-03-22 17:19:15 +05:00
ayesha waris
15aee6a534 temp: fix for resize observer loop limit exceeded (#477) 2023-03-22 16:17:14 +05:00
ayesha waris
cd2d67e137 fix: fix typeset failed error (#475)
* fix: fix typeset failed error

* test: adds test cases for decreased coverage

* test: adds test cases for reply component
2023-03-21 19:17:48 +05:00
Jenkins
c4f861c24f chore(i18n): update translations 2023-03-19 16:26:48 -04:00
sundasnoreen12
78a85255e4 test: added test cases of learners and post (#470)
* test: added test cases of learners and post

* refactor: fixed reviewed issues

* refactor: changed data test id names for load more buttons

---------

Co-authored-by: sundasnoreen12 <sundasnoreen12@ggmail.com>
2023-03-16 14:48:00 +05:00
Stanislav
b21048e4e6 fix: Fix for code block formatting on the post preview and published post view (#450) 2023-03-14 12:18:33 +05:00
sundasnoreen12
45dea79a87 test: added test cases for learner post view (#466)
* test: added test cases for learner post view

* refactor: fixed requested changes for code optimization

* refactor: added url change

---------

Co-authored-by: sundasnoreen12 <sundasnoreen12@ggmail.com>
2023-03-13 16:44:46 +05:00
sundasnoreen12
530f2cec82 test: added test cases for learner view (#465)
* test: added test cases for learner view

* refactor: fixed changes for code optimization

* refactor: added url changes

---------

Co-authored-by: sundasnoreen12 <sundasnoreen12@ggmail.com>
2023-03-13 13:18:09 +05:00
Mashal Malik
91cb347456 refactor: remove unused tranisfex v2 url (#463) 2023-03-13 10:41:40 +05:00
Jenkins
04745d6429 chore(i18n): update translations 2023-03-12 16:26:50 -04:00
Muhammad Adeel Tajamul
aad6702339 feat: sort comments based on sort order dropdown (#468)
Co-authored-by: adeel.tajamul <adeel.tajamul@arbisoft.com>
2023-03-10 21:38:05 +05:00
Ahtisham Shahid
d39a196cdf feat: added product tour for response sort (#462)
* feat: added product tour for response sort
2023-03-10 12:23:13 +05:00
SaadYousaf
7b000f1974 feat: send enableInContextSidebar param to backend to identify source of content for events 2023-03-09 09:02:10 +05:00
sundasnoreen12
627390c4e3 test: added testcases of redux, selector and api (#459)
* test: added testcases of redux, selector and api

* refactor: fixe recommanded issue and improve code cov

* refactor: added cases for filter statuses

* refactor: updated test description

* refactor: add common method of mock data for learner and post

* refactor: code and moved test utils in learners folder

---------

Co-authored-by: sundasnoreen12 <sundasnoreen12@ggmail.com>
Co-authored-by: Awais Ansari <awais.ansari63@gmail.com>
2023-03-08 14:25:54 +05:00
Muhammad Adeel Tajamul
1db94718c8 fix: more actions dropdown was not visible (#461) 2023-03-07 06:22:31 +05:00
ayesha waris
24d02350a8 fix: fix topic info for course-wide discussion topics (#458)
* fix: fix topic info for course-wide discussion topics

* refactor: removed const and used url directly

* test: adds test cases for topic info

* test: updated test cases
2023-03-06 21:10:49 +05:00
Jenkins
f66cdda1b6 chore(i18n): update translations 2023-03-05 15:26:48 -05:00
ayesha waris
07b56e6070 style: add border on focused post (#460) 2023-03-03 16:14:52 +05:00
ayesha waris
be1a2ccaab fix: fixed post coment actions menu accessibilty for keyboard (#456) 2023-03-01 21:46:23 +05:00
Sarina Canelake
ed0c73e051 Merge pull request #454 from openedx/repo_checks/ensure_workflows
Update standard workflow files.
2023-02-28 09:40:08 -05:00
Feanil Patel
1041b3e45f build: Updating a missing workflow file add-depr-ticket-to-depr-board.yml.
The .github/workflows/add-depr-ticket-to-depr-board.yml workflow is missing or needs an update to stay in
sync with the current standard for this workflow as defined in the
`.github` repo of the `openedx` GitHub org.
2023-02-28 09:34:03 -05:00
Feanil Patel
493a0610ca build: Creating a missing workflow file add-remove-label-on-comment.yml.
The .github/workflows/add-remove-label-on-comment.yml workflow is missing or needs an update to stay in
sync with the current standard for this workflow as defined in the
`.github` repo of the `openedx` GitHub org.
2023-02-28 09:34:03 -05:00
Feanil Patel
679e21c270 build: Creating a missing workflow file self-assign-issue.yml.
The .github/workflows/self-assign-issue.yml workflow is missing or needs an update to stay in
sync with the current standard for this workflow as defined in the
`.github` repo of the `openedx` GitHub org.
2023-02-28 09:34:03 -05:00
sundasnoreen12
62eb9f5e02 test: Added test cases for noncourseware and courseware topic posts (#452)
* test: Added test cases for noncourseware and courseware topic posts

* refactor: optimized code for post view list

* refactor: updated selector tag

---------

Co-authored-by: sundasnoreen12 <sundasnoreen12@ggmail.com>
2023-02-24 12:39:46 +05:00
Muhammad Adeel Tajamul
dedbc25358 fix: incontext crashing (#453)
Co-authored-by: adeel.tajamul <adeel.tajamul@arbisoft.com>
2023-02-23 19:05:39 +05:00
Mehak Nasir
0f2ad8b7b4 fix: conditionally skipped some API calls and deffered script loading to improve performance 2023-02-23 14:26:02 +05:00
Ahtisham Shahid
61581ff474 fix: resolved data retention issue in add a post form (#451)
* fix: resolved data retention issue in adding a post form

* test: added unit test for post editor
2023-02-22 22:58:23 +05:00
Ahtisham Shahid
3afce17a32 feat: added event tracking on load more response (#442)
* feat: added event tracking on load more response
2023-02-21 16:30:30 +05:00
Muhammad Adeel Tajamul
7e36e9f14c fix: post loading slow (#447)
Co-authored-by: adeel.tajamul <adeel.tajamul@arbisoft.com>
2023-02-21 15:09:44 +05:00
285 changed files with 30084 additions and 48090 deletions

8
.env
View File

@@ -20,6 +20,8 @@ SEGMENT_KEY=''
SITE_NAME=''
USER_INFO_COOKIE_NAME=''
SUPPORT_URL=''
TA_FEEDBACK_FORM= ''
STAFF_FEEDBACK_FORM= ''
DISPLAY_FEEDBACK_BANNER='false'
LEARNER_FEEDBACK_URL=''
STAFF_FEEDBACK_URL=''
ENABLE_PROFILE_IMAGE=''
# Fallback in local style files
PARAGON_THEME_URLS={}

View File

@@ -21,6 +21,8 @@ SEGMENT_KEY=''
SITE_NAME=localhost
USER_INFO_COOKIE_NAME='edx-user-info'
SUPPORT_URL='https://support.edx.org'
TA_FEEDBACK_FORM='https://learner-form.test'
STAFF_FEEDBACK_FORM='https://staff-form.test'
DISPLAY_FEEDBACK_BANNER='false'
LEARNER_FEEDBACK_URL=''
STAFF_FEEDBACK_URL=''
ENABLE_PROFILE_IMAGE=''
# Fallback in local style files
PARAGON_THEME_URLS={}

View File

@@ -19,6 +19,6 @@ SEGMENT_KEY=''
SITE_NAME='localhost'
USER_INFO_COOKIE_NAME='edx-user-info'
SUPPORT_URL='https://support.edx.org'
TA_FEEDBACK_FORM='https://learner-form.test'
STAFF_FEEDBACK_FORM='https://staff-form.test'
DISPLAY_FEEDBACK_BANNER='false'
LEARNER_FEEDBACK_URL=''
STAFF_FEEDBACK_URL=''
ENABLE_PROFILE_IMAGE=''

View File

@@ -2,3 +2,4 @@ coverage/*
dist/
node_modules/
jest.config.js
src/i18n/messages/

View File

@@ -1,9 +1,10 @@
const { createConfig } = require('@edx/frontend-build');
const { createConfig } = require('@openedx/frontend-build');
module.exports = createConfig('eslint',
{
"plugins": ["simple-import-sort"],
"rules": {
module.exports = createConfig(
'eslint',
{
plugins: ['simple-import-sort'],
rules: {
'import/no-extraneous-dependencies': 'off',
'react-hooks/exhaustive-deps': 'off',
'jsx-a11y/no-noninteractive-element-interactions': 'off',
@@ -25,7 +26,6 @@ module.exports = createConfig('eslint',
},
],
'simple-import-sort/exports': 'error',
}
}
},
},
);

7
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,7 @@
version: 2
updates:
# Adding new check for github-actions
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"

View File

@@ -16,4 +16,4 @@ jobs:
secrets:
GITHUB_APP_ID: ${{ secrets.GRAPHQL_AUTH_APP_ID }}
GITHUB_APP_PRIVATE_KEY: ${{ secrets.GRAPHQL_AUTH_APP_PEM }}
SLACK_BOT_TOKEN: ${{ secrets.SLACK_ISSUE_BOT_TOKEN }}
SLACK_BOT_TOKEN: ${{ secrets.SLACK_ISSUE_BOT_TOKEN }}

View File

@@ -0,0 +1,20 @@
# This workflow runs when a comment is made on the ticket
# If the comment starts with "label: " it tries to apply
# the label indicated in rest of comment.
# If the comment starts with "remove label: ", it tries
# to remove the indicated label.
# Note: Labels are allowed to have spaces and this script does
# not parse spaces (as often a space is legitimate), so the command
# "label: really long lots of words label" will apply the
# label "really long lots of words label"
name: Allows for the adding and removing of labels via comment
on:
issue_comment:
types: [created]
jobs:
add_remove_labels:
uses: openedx/.github/.github/workflows/add-remove-label-on-comment.yml@master

View File

@@ -9,18 +9,16 @@ on:
jobs:
tests:
runs-on: ubuntu-latest
strategy:
matrix:
node: [16]
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Setup Nodejs
uses: actions/setup-node@v2
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
node-version-file: '.nvmrc'
- name: Install dependencies
run: npm ci
- name: Validate package-lock.json changes
@@ -34,4 +32,7 @@ jobs:
- name: i18n_extract
run: npm run i18n_extract
- name: Coverage
uses: codecov/codecov-action@v3
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: true

View File

@@ -10,4 +10,4 @@ on:
jobs:
version-check:
uses: openedx/.github/.github/workflows/lockfileversion-check.yml@master
uses: openedx/.github/.github/workflows/lockfileversion-check-v3.yml@master

12
.github/workflows/self-assign-issue.yml vendored Normal file
View File

@@ -0,0 +1,12 @@
# This workflow runs when a comment is made on the ticket
# If the comment starts with "assign me" it assigns the author to the
# ticket (case insensitive)
name: Assign comment author to ticket if they say "assign me"
on:
issue_comment:
types: [created]
jobs:
self_assign_by_comment:
uses: openedx/.github/.github/workflows/self-assign-issue.yml@master

1
.gitignore vendored
View File

@@ -6,6 +6,7 @@ node_modules
npm-debug.log
coverage
module.config.js
env.config.*
dist/
src/i18n/transifex_input.json

View File

@@ -1,2 +0,0 @@
process.env.TA_FEEDBACK_FORM= 'https://learner-form.test';
process.env.STAFF_FEEDBACK_FORM= 'https://staff-form.test';

2
.nvmrc
View File

@@ -1 +1 @@
16
24

View File

@@ -1,8 +0,0 @@
[main]
host = https://www.transifex.com
[o:open-edx:p:edx-platform:r:frontend-app-discussions]
file_filter = src/i18n/messages/<lang>.json
source_file = src/i18n/transifex_input.json
source_lang = en
type = KEYVALUEJSON

View File

@@ -1,15 +1,10 @@
export TRANSIFEX_RESOURCE = frontend-app-discussions
transifex_resource = frontend-app-discussions
transifex_langs = "ar,fr,es_419,zh_CN,tr_TR,pl,fr_CA,fr_FR,de_DE,it_IT"
intl_imports = ./node_modules/.bin/intl-imports.js
transifex_utils = ./node_modules/.bin/transifex-utils.js
i18n = ./src/i18n
transifex_input = $(i18n)/transifex_input.json
tx_url1 = https://www.transifex.com/api/2/project/edx-platform/resource/$(transifex_resource)/translation/en/strings/
tx_url2 = https://www.transifex.com/api/2/project/edx-platform/resource/$(transifex_resource)/source/
# This directory must match .babelrc .
transifex_temp = ./temp/babel-plugin-react-intl
transifex_temp = ./temp/babel-plugin-formatjs
NPM_TESTS=build i18n_extract lint test
@@ -46,22 +41,21 @@ detect_changed_source_translations:
# Checking for changed translations...
git diff --exit-code $(i18n)
# Pushes translations to Transifex. You must run make extract_translations first.
push_translations:
# Pushing strings to Transifex...
tx push -s
# Fetching hashes from Transifex...
./node_modules/@edx/reactifex/bash_scripts/get_hashed_strings_v3.sh
# Writing out comments to file...
$(transifex_utils) $(transifex_temp) --comments --v3-scripts-path
# Pushing comments to Transifex...
./node_modules/@edx/reactifex/bash_scripts/put_comments_v3.sh
# Pulls translations from Transifex.
pull_translations:
tx pull -t -f --mode reviewed --languages=$(transifex_langs)
rm -rf src/i18n/messages
mkdir src/i18n/messages
cd src/i18n/messages \
&& atlas pull $(ATLAS_OPTIONS) \
translations/frontend-component-header/src/i18n/messages:frontend-component-header \
translations/frontend-component-footer/src/i18n/messages:frontend-component-footer \
translations/frontend-platform/src/i18n/messages:frontend-platform \
translations/paragon/src/i18n/messages:paragon \
translations/frontend-app-discussions/src/i18n/messages:frontend-app-discussions
$(intl_imports) frontend-component-header frontend-component-footer frontend-platform paragon frontend-app-discussions
# endif
# This target is used by Travis.
validate-no-uncommitted-package-lock-changes:
# Checking for package-lock.json changes...
git diff --exit-code package-lock.json
git diff --exit-code package-lock.json

View File

@@ -1,15 +1,42 @@
########################
frontend-app-discussions
========================
########################
|Build Status| |Codecov| |license|
|Codecov| |license|
.. |Codecov| image:: https://codecov.io/gh/openedx/frontend-app-discussions/branch/master/graph/badge.svg?token=3z7XvuzTq3
:target: https://codecov.io/gh/openedx/frontend-app-discussions
.. |license| image:: https://img.shields.io/badge/license-AGPL-informational
:target: https://github.com/openedx/frontend-app-discussions/blob/master/LICENSE
********
Purpose
-------
********
This repository is a React-based micro frontend for the Open edX discussion forums.
***************
Getting Started
---------------
***************
Prerequisites
=============
The `devstack`_ is currently recommended as a development environment for your
new MFE. If you start it with ``make dev.up.lms`` that should give you
everything you need as a companion to this frontend.
Note that it is also possible to use `Tutor`_ to develop an MFE. You can refer
to the `relevant tutor-mfe documentation`_ to get started using it.
.. _Devstack: https://github.com/openedx/devstack
.. _Tutor: https://github.com/overhangio/tutor
.. _relevant tutor-mfe documentation: https://github.com/overhangio/tutor-mfe#mfe-development
Cloning and Startup
===================
1. Clone your new repo:
@@ -25,8 +52,14 @@ Getting Started
The dev server is running at `http://localhost:2002 <http://localhost:2002>`_.
Plugins
=======
This MFE can be customized using `Frontend Plugin Framework <https://github.com/openedx/frontend-plugin-framework>`_.
The parts of this MFE that can be customized in that manner are documented `here </src/plugin-slots>`_.
Getting Help
------------
============
Please tag **@openedx/edx-infinity ** on any PRs or issues. Thanks.
If you're having trouble, we have discussion forums at https://discuss.openedx.org where you can connect with others in the community.
@@ -39,10 +72,11 @@ For more information about these options, see the `Getting Help`_ page.
.. _Getting Help: https://openedx.org/getting-help
How to Contribute
-----------------
=================
Details about how to become a contributor to the Open edX project may be found in the wiki at `How to contribute`_
.. _How to contribute: https://edx.readthedocs.io/projects/edx-developer-guide/en/latest/process/index.html
.. _How to contribute: https://docs.openedx.org/en/latest/developers/references/developer_guide/process/index.html
PR description template should be automatically applied if you are sending PR from github interface; otherwise you
can find it it at `PULL_REQUEST_TEMPLATE.md <https://github.com/openedx/frontend-app-discussions/blob/master/.github/pull_request_template.md>`_
@@ -50,39 +84,45 @@ can find it it at `PULL_REQUEST_TEMPLATE.md <https://github.com/openedx/frontend
This project is currently accepting all types of contributions, bug fixes and security fixes
The Open edX Code of Conduct
----------------------------
============================
All community members should familiarize themselves with the `Open edX Code of Conduct`_.
.. _Open edX Code of Conduct: https://openedx.org/code-of-conduct/
People
------
======
The assigned maintainers for this component and other project details may be found in Backstage or from inspecting catalog-info.yaml.
Reporting Security Issues
-------------------------
Please do not report security issues in public. Please email security@edx.org.
Please do not report security issues in public. Please email security@openedx.org.
Project Structure
-----------------
=================
The source for this project is organized into nested submodules according to the ADR `Feature-based Application Organization <https://github.com/openedx/frontend-app-discussions/blob/master/docs/decisions/0002-feature-based-application-organization.rst>`_.
Build Process Notes
-------------------
===================
**Production Build**
The production build is created with ``npm run build``.
License
=======
The code in this repository is licensed under the AGPLv3 unless otherwise
noted.
Please see `LICENSE <LICENSE>`_ for details.
Internationalization
--------------------
====================
Please see `edx/frontend-platform's i18n module <https://edx.github.io/frontend-platform/module-Internationalization.html>`_ for documentation on internationalization. The documentation explains how to use it, and the `How To <https://github.com/openedx/frontend-i18n/blob/master/docs/how_tos/i18n.rst>`_ has more detail.
.. |Build Status| image:: https://api.travis-ci.org/edx/frontend-app-discussions.svg?branch=master
:target: https://travis-ci.org/edx/frontend-app-discussions
.. |Codecov| image:: https://codecov.io/gh/edx/frontend-app-discussions/branch/master/graph/badge.svg
:target: https://codecov.io/gh/edx/frontend-app-discussions
.. |license| image:: https://img.shields.io/npm/l/@edx/frontend-app-discussions.svg
:target: @edx/frontend-app-discussions
Reporting Security Issues
=========================
Please do not report security issues in public. Please email security@openedx.org.

View File

@@ -12,6 +12,7 @@ metadata:
icon: "Web"
annotations:
openedx.org/arch-interest-groups: ""
openedx.org/release: "master"
spec:
owner: group:edx-infinity
type: 'website'

View File

@@ -1,14 +1,14 @@
const { createConfig } = require('@edx/frontend-build');
const { createConfig } = require('@openedx/frontend-build');
module.exports = createConfig('jest', {
// setupFilesAfterEnv is used after the jest environment has been loaded. In general this is what you want.
// setupFilesAfterEnv is used after the jest environment has been loaded. In general this is what you want.
// If you want to add config BEFORE jest loads, use setupFiles instead.
setupFiles: ['<rootDir>/.jest/setEnvVars.js'],
setupFiles: ['<rootDir>/.env.test'],
setupFilesAfterEnv: [
'<rootDir>/src/setupTest.js',
'<rootDir>/src/setupTest.jsx',
],
coveragePathIgnorePatterns: [
'src/setupTest.js',
'src/setupTest.jsx',
'src/i18n',
],
});

View File

@@ -1,11 +0,0 @@
# This file describes this Open edX repo, as described in OEP-2:
# http://open-edx-proposals.readthedocs.io/en/latest/oeps/oep-0002.html#specification
nick: tmpa
oeps: {}
owner: edx/arch-team
openedx-release:
# The openedx-release key is described in OEP-10:
# https://open-edx-proposals.readthedocs.io/en/latest/oep-0010-proc-openedx-releases.html
# The FAQ might also be helpful: https://openedx.atlassian.net/wiki/spaces/COMM/pages/1331268879/Open+edX+Release+FAQ
ref: master

58775
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -11,18 +11,14 @@
],
"scripts": {
"build": "fedx-scripts webpack",
"i18n_extract": "BABEL_ENV=i18n fedx-scripts babel src --quiet > /dev/null",
"i18n_extract": "fedx-scripts formatjs extract",
"lint": "fedx-scripts eslint --ext .js --ext .jsx .",
"lint:fix": "fedx-scripts eslint --ext .js --ext .jsx . --fix",
"snapshot": "fedx-scripts jest --updateSnapshot",
"start": "fedx-scripts webpack-dev-server --progress",
"dev": "PUBLIC_PATH=/discussions/ MFE_CONFIG_API_URL='http://localhost:8000/api/mfe_config/v1' fedx-scripts webpack-dev-server --progress --host apps.local.openedx.io",
"test": "fedx-scripts jest --coverage --passWithNoTests"
},
"husky": {
"hooks": {
"pre-commit": "npm run lint"
}
},
"author": "edX",
"license": "AGPL-3.0",
"homepage": "https://github.com/openedx/frontend-app-discussions#readme",
@@ -33,45 +29,48 @@
"url": "https://github.com/openedx/frontend-app-discussions/issues"
},
"dependencies": {
"@edx/brand": "npm:@edx/brand-openedx@1.1.0",
"@edx/frontend-component-footer": "11.2.0",
"@edx/frontend-component-header": "3.2.0",
"@edx/frontend-platform": "2.6.1",
"@edx/paragon": "20.15.0",
"@reduxjs/toolkit": "1.8.0",
"@tinymce/tinymce-react": "3.13.1",
"@edx/brand": "npm:@openedx/brand-openedx@^1.2.2",
"@edx/frontend-component-footer": "^14.6.0",
"@edx/frontend-component-header": "^8.1.0",
"@edx/frontend-platform": "^8.3.3",
"@edx/openedx-atlas": "^0.7.0",
"@openedx/paragon": "^23.4.5",
"@reduxjs/toolkit": "1.9.7",
"@tinymce/tinymce-react": "5.1.1",
"babel-polyfill": "6.26.0",
"classnames": "2.3.1",
"core-js": "3.21.1",
"classnames": "2.5.1",
"core-js": "3.47.0",
"dompurify": "^2.4.3",
"formik": "2.2.9",
"formik": "2.4.9",
"lodash.snakecase": "4.1.1",
"prop-types": "15.8.1",
"raw-loader": "4.0.2",
"react": "16.14.0",
"react-dom": "16.14.0",
"react-redux": "7.2.6",
"react-router": "5.2.1",
"react-router-dom": "5.3.0",
"redux": "4.1.2",
"regenerator-runtime": "0.13.9",
"react": "18.3.1",
"react-dom": "18.3.1",
"react-google-recaptcha": "^3.1.0",
"react-google-recaptcha-v3": "^1.11.0",
"react-helmet": "6.1.0",
"react-redux": "7.2.9",
"react-router": "6.18.0",
"react-router-dom": "6.18.0",
"redux": "4.2.1",
"regenerator-runtime": "0.14.1",
"timeago.js": "4.0.2",
"tinymce": "5.10.2",
"yup": "0.31.1"
"tinymce": "5.10.9",
"yup": "0.32.11"
},
"devDependencies": {
"@edx/browserslist-config": "1.1.0",
"@edx/frontend-build": "11.0.1",
"@edx/reactifex": "1.0.3",
"@testing-library/jest-dom": "5.16.2",
"@testing-library/react": "12.1.4",
"@edx/browserslist-config": "1.5.0",
"@openedx/frontend-build": "^14.6.2",
"@testing-library/jest-dom": "5.17.0",
"@testing-library/react": "14.3.1",
"@testing-library/user-event": "13.5.0",
"axios-mock-adapter": "1.20.0",
"axios": "^0.30.0",
"axios-mock-adapter": "1.22.0",
"babel-plugin-react-intl": "8.2.25",
"eslint-plugin-simple-import-sort": "7.0.0",
"glob": "7.2.0",
"husky": "7.0.4",
"jest": "27.5.1",
"rosie": "2.1.0"
"glob": "7.2.3",
"jest": "29.7.0",
"rosie": "2.1.1"
}
}

View File

@@ -9,7 +9,7 @@
href="<%=htmlWebpackPlugin.options.FAVICON_URL%>"
type="image/x-icon"
/>
<script>
<script defer>
window.MathJax = {
tex: {
inlineMath: [
@@ -45,7 +45,7 @@
};
</script>
<script
async
defer
src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"
id="MathJax-script"
></script>
@@ -54,7 +54,7 @@
<div id="root" class="small"></div>
<!-- begin usabilla live embed code -->
<script type="text/javascript">
<script defer type="text/javascript">
window.lightningjs ||
(function (n) {
var e = "lightningjs";
@@ -180,10 +180,6 @@
var r = (window.lightningjs = t(e));
(r.require = t), (r.modules = n);
})({});
window.usabilla_live = lightningjs.require(
"usabilla_live",
"//w.usabilla.com/9e6036348fa1.js"
);
</script>
<!-- end usabilla live embed code -->
</body>

View File

@@ -1,9 +1,33 @@
{
"extends": [
"config:base"
"config:base",
"schedule:weekly",
":automergeLinters",
":automergeMinor",
":automergeTesters",
":enableVulnerabilityAlerts",
":rebaseStalePrs",
":semanticCommits",
":updateNotScheduled"
],
"patch": {
"automerge": true
},
"rebaseStalePrs": true
"packageRules": [
{
"matchDepTypes": [
"devDependencies"
],
"matchUpdateTypes": [
"lockFileMaintenance",
"minor",
"patch",
"pin"
],
"automerge": true
},
{
"matchPackagePatterns": ["@edx", "@openedx"],
"matchUpdateTypes": ["minor", "patch"],
"automerge": true
}
],
"timezone": "America/New_York"
}

View File

@@ -0,0 +1,14 @@
const ContentUnavailable = () => (
<svg width="229" height="167" viewBox="0 0 229 167" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15.9664 67.649C1.9299 88.4776 -5.31519 112.805 4.55784 135.123C22.5467 175.788 120.573 164.359 163.26 148.39C283.487 103.415 225.675 -14.6 95.6636 14.5816C59.2626 22.7519 30.003 46.8204 15.9664 67.649Z" fill="#E1DDDB" fillOpacity="0.3" />
<path d="M101.264 120.672L101.13 120.486H100.9H58.4969C54.0932 120.486 50.45 116.531 50.45 111.548V60.3944C50.45 55.4164 54.0937 51.45 58.4969 51.45H170.468C174.872 51.45 178.522 55.4171 178.55 60.3969V111.548C178.55 116.531 174.901 120.486 170.497 120.486H126.838H126.636L126.502 120.637L112.568 136.283L101.264 120.672Z" fill="white" stroke="#454545" strokeWidth="0.9" />
<path d="M99.363 99.6098L93.9175 94.6162L82.0459 107.565L87.4913 112.558L99.363 99.6098Z" fill="#002121" />
<path d="M87.3976 112.877C87.3486 112.862 87.3041 112.836 87.268 112.8L81.7927 107.803C81.76 107.774 81.7334 107.739 81.7145 107.7C81.6956 107.661 81.6848 107.619 81.6828 107.575C81.6807 107.532 81.6874 107.488 81.7025 107.447C81.7175 107.407 81.7407 107.369 81.7705 107.338L93.645 94.3885C93.6737 94.3558 93.7088 94.3292 93.7481 94.3106C93.7875 94.292 93.8302 94.2817 93.8737 94.2803C93.9601 94.2699 94.047 94.2939 94.1158 94.3472L99.5894 99.3501C99.6214 99.3792 99.6472 99.4144 99.6654 99.4537C99.6835 99.4929 99.6937 99.5354 99.6951 99.5786C99.6966 99.6219 99.6894 99.665 99.6739 99.7054C99.6585 99.7458 99.6351 99.7827 99.6052 99.8139L87.7445 112.787C87.6839 112.848 87.6031 112.884 87.5175 112.889C87.4771 112.894 87.4361 112.89 87.3976 112.877ZM82.5076 107.548L87.4698 112.094L98.9201 99.6383L93.966 95.0875L82.5076 107.548Z" fill="#002121" />
<path d="M90.5786 108.62L85.6982 104.144C85.0283 103.53 83.9874 103.575 83.3732 104.245L62.9753 126.494C62.3611 127.164 62.4062 128.205 63.076 128.819L67.9565 133.294C68.6263 133.909 69.6672 133.863 70.2814 133.193L90.6793 110.945C91.2935 110.275 91.2484 109.234 90.5786 108.62Z" fill="#03C7E8" />
<path d="M68.543 133.99C68.2434 133.908 67.9682 133.754 67.7412 133.542L62.8495 129.063C62.4689 128.709 62.2426 128.22 62.2195 127.7C62.1963 127.181 62.3781 126.673 62.7256 126.286L83.1283 104.037C83.4824 103.656 83.9719 103.43 84.4913 103.407C85.0107 103.384 85.5184 103.565 85.905 103.913L90.7904 108.39C91.171 108.744 91.3972 109.234 91.4204 109.753C91.4435 110.273 91.2618 110.78 90.9142 111.167L70.5243 133.42C70.2781 133.687 69.963 133.882 69.6136 133.983C69.2641 134.083 68.8938 134.086 68.543 133.99ZM84.937 104.091C84.8052 104.054 84.6678 104.039 84.5309 104.048C84.3574 104.056 84.1873 104.099 84.0304 104.173C83.8734 104.247 83.7325 104.352 83.616 104.48L63.2151 126.723C62.9827 126.981 62.8613 127.321 62.8771 127.668C62.8928 128.015 63.0444 128.341 63.2992 128.577L68.1845 133.054C68.4417 133.287 68.78 133.409 69.1265 133.395C69.473 133.38 69.7998 133.23 70.0366 132.976L90.434 110.746C90.6663 110.488 90.7878 110.149 90.772 109.802C90.7563 109.455 90.6046 109.128 90.3499 108.892L85.4645 104.415C85.3185 104.265 85.1372 104.154 84.937 104.091Z" fill="#002121" />
<path d="M119.367 71.6959C116.6 69.1548 113.141 67.492 109.428 66.9178C105.715 66.3436 101.916 66.8839 98.51 68.4703C95.1043 70.0567 92.2457 72.6179 90.296 75.8298C88.3463 79.0416 87.3932 82.7597 87.5572 86.5134C87.7212 90.2671 88.9951 93.8877 91.2175 96.9169C93.44 99.9461 96.5111 102.248 100.042 103.531C103.573 104.813 107.406 105.02 111.054 104.123C114.702 103.227 118.003 101.268 120.538 98.4946C123.931 94.7829 125.713 89.8768 125.494 84.8527C125.274 79.8287 123.071 75.097 119.367 71.6959ZM96.9839 96.0996C94.9233 94.2099 93.4694 91.7515 92.8059 89.0353C92.1424 86.3191 92.2992 83.467 93.2565 80.8399C94.2138 78.2127 95.9285 75.9283 98.1839 74.2757C100.439 72.6231 103.134 71.6765 105.927 71.5556C108.72 71.4346 111.487 72.1447 113.876 73.5962C116.266 75.0477 118.171 77.1752 119.352 79.7098C120.532 82.2445 120.934 85.0723 120.508 87.8357C120.081 90.5991 118.845 93.174 116.955 95.2348C114.421 97.9979 110.893 99.6412 107.148 99.8034C103.403 99.9656 99.7468 98.6333 96.9839 96.0996Z" fill="white" />
<path d="M101.371 104.313C96.6651 103.025 92.6175 100.01 90.0365 95.8695C87.4556 91.7285 86.5312 86.7663 87.4479 81.9735C88.3647 77.1806 91.055 72.9097 94.982 70.0134C98.9089 67.117 103.784 65.8084 108.633 66.3486C113.482 66.8888 117.949 69.2382 121.142 72.9277C124.335 76.6173 126.019 81.3755 125.858 86.2526C125.697 91.1296 123.703 95.7667 120.273 99.2381C116.844 102.709 112.232 104.76 107.358 104.98C105.34 105.074 103.319 104.848 101.371 104.313ZM111.529 67.7055C109.642 67.1867 107.687 66.9654 105.732 67.0496C101.063 67.2601 96.6449 69.2229 93.3586 72.5465C90.0724 75.8701 88.1594 80.3103 88.0012 84.9817C87.843 89.653 89.4512 94.2123 92.505 97.7502C95.5589 101.288 99.834 103.545 104.478 104.07C109.122 104.596 113.793 103.351 117.56 100.585C121.328 97.8185 123.914 93.7337 124.804 89.1451C125.694 84.5565 124.821 79.8012 122.361 75.8275C119.9 71.8538 116.033 68.9537 111.529 67.7055ZM102.661 99.6222C100.464 99.0148 98.4423 97.8945 96.7628 96.3534C94.1466 93.9593 92.5 90.6882 92.1352 87.1605C91.7704 83.6327 92.7128 80.0937 94.7836 77.2145C96.8544 74.3353 99.9096 72.3161 103.37 71.54C106.83 70.7638 110.455 71.2847 113.556 73.0037C116.658 74.7227 119.021 77.5203 120.197 80.8661C121.373 84.2118 121.281 87.873 119.937 91.1553C118.594 94.4375 116.093 97.1126 112.908 98.6733C109.724 100.234 106.077 100.572 102.661 99.6222ZM97.2188 95.8692C99.2303 97.7107 101.742 98.9152 104.437 99.3306C107.133 99.746 109.89 99.3537 112.363 98.2033C114.836 97.0529 116.912 95.1958 118.331 92.8664C119.75 90.5371 120.447 87.8397 120.334 85.1146C120.215 82.3875 119.291 79.7568 117.678 77.5551C116.064 75.3534 113.835 73.6794 111.27 72.7447C108.706 71.81 105.922 71.6565 103.27 72.3037C100.618 72.9509 98.2181 74.3696 96.3723 76.3807C93.9071 79.0806 92.612 82.6474 92.7707 86.3C92.9294 89.9527 94.5288 93.3936 97.2188 95.8692Z" fill="#002121" />
</svg>
);
export default ContentUnavailable;

48
src/assets/Empty.jsx Normal file
View File

@@ -0,0 +1,48 @@
const Empty = () => (
<svg width="246" height="204" viewBox="0 0 246 204" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15.966 80.706C1.93 101.534-5.315 125.86 4.558 148.18c17.989 40.665 116.015 29.236 158.702 13.267 120.227-44.976 62.415-162.99-67.596-133.809-36.401 8.17-65.661 32.239-79.698 53.068Z" fill="#D7D3D1" fillOpacity="0.3" />
<path d="M95.6307 23.4976C101.102 13.3476 109.932 6.31365 123.036 10.536C124.191 11.0005 125.403 11.311 126.639 11.4597C127.895 11.386 129.146 11.2369 130.384 11.0131C136.292 10.5259 140.656 16.2505 143.529 21.427C148.867 31.0796 158.622 44.1833 156.724 55.7847C155.252 64.7776 146.797 71.0097 138.261 74.1562C128.636 77.7063 118.244 78.655 108.136 76.9069C103.061 76.034 97.813 74.3288 94.159 70.6443C89.084 65.5084 88.069 57.6016 88.4547 50.3443C88.9926 42.2345 91.1749 31.8104 95.6307 23.4976Z" fill="#EEEAE9" />
<path d="M117.991 78.2569C114.66 78.2567 111.336 77.9715 108.054 77.4043C101.649 76.3081 97.0006 74.2172 93.8439 71.0098C89.4084 66.554 87.4393 59.652 87.9772 50.3952C88.5558 40.7933 91.2557 30.6534 95.2142 23.2845C99.8528 14.657 108.46 5.35964 123.137 10.0895L123.837 10.3331C124.73 10.6795 125.665 10.9015 126.618 10.9929C127.348 10.9968 128.077 10.9219 128.79 10.7696C129.278 10.6884 129.805 10.597 130.303 10.5564C135.165 10.1403 139.752 13.7334 143.894 21.2038C144.848 22.9293 145.924 24.7563 147.091 26.6949C152.278 35.3732 158.733 46.1728 157.139 55.866C155.871 63.712 148.877 70.7155 138.392 74.5928C131.864 77.0187 124.955 78.2596 117.991 78.2569ZM96.0972 23.721C92.1996 30.9782 89.5302 40.976 88.9922 50.4459C88.4543 59.3982 90.332 66.0972 94.5544 70.3602C97.5994 73.4053 102.076 75.4352 108.277 76.4502C118.3 78.1754 128.602 77.223 138.139 73.6894C148.289 69.9238 155.079 63.1842 156.307 55.6732C157.84 46.325 151.486 35.6777 146.38 27.1212C145.223 25.1826 144.127 23.3556 143.163 21.6199C139.214 14.5149 134.941 11.0741 130.465 11.4699C129.988 11.4699 129.521 11.5917 129.034 11.6729C128.218 11.8545 127.384 11.9464 126.547 11.947C125.503 11.8549 124.477 11.6122 123.502 11.2263L122.812 10.9929C108.744 6.45585 100.482 15.4284 95.9957 23.7413L96.0972 23.721Z" fill="#454545" />
<path d="M182.845 167.75C182.845 167.75 178.596 94.152 167.857 84.3978C152.409 70.4314 97.5788 67.5184 86.4443 70.3909C66.7533 75.4659 53.0508 106.22 53.0508 106.22L85.7135 115.985C85.7845 141.248 88.3119 167.75 88.3119 167.75H182.845Z" fill="#B5AFAB" />
<path d="M84.2307 146.058C84.1627 146.053 84.0963 146.035 84.0354 146.004C83.9746 145.973 83.9204 145.93 83.8761 145.878C83.8317 145.827 83.7981 145.766 83.7771 145.702C83.7561 145.637 83.7481 145.568 83.7537 145.5L88.1689 87.6451C88.1741 87.5757 88.1928 87.5081 88.2242 87.446C88.2556 87.3839 88.299 87.3286 88.3518 87.2833C88.4046 87.238 88.4659 87.2037 88.532 87.1821C88.5982 87.1606 88.668 87.1524 88.7373 87.1579C88.8063 87.163 88.8736 87.1819 88.9352 87.2134C88.9967 87.2449 89.0514 87.2884 89.0959 87.3414C89.1403 87.3944 89.1737 87.4557 89.1941 87.5219C89.2144 87.588 89.2213 87.6575 89.2144 87.7263L84.7991 145.581C84.794 145.65 84.7752 145.718 84.7437 145.779C84.7122 145.841 84.6686 145.895 84.6156 145.94C84.5627 145.984 84.5013 146.018 84.4352 146.038C84.3691 146.058 84.2995 146.065 84.2307 146.058Z" fill="#5C524D" />
<path d="M114.163 90.7217C117.828 95.279 123.298 98.527 129.196 98.6184C129.959 98.6607 130.724 98.5574 131.449 98.3139C132.442 97.8894 133.288 97.1838 133.885 96.2839C136.544 92.6299 136.656 87.7071 135.986 83.2411C135.509 80.1048 134.595 76.8162 132.22 74.7151C129.429 72.2487 125.379 72.0152 121.654 71.9137C117.929 71.8122 109.291 70.7262 107.911 75.314C106.622 79.5059 111.707 87.6665 114.163 90.7217Z" fill="#5C524D" />
<path d="M129.42 99.0646H129.187C123.553 98.9732 117.809 95.9587 113.82 91.0055C111.17 87.7169 106.146 79.536 107.476 75.1816C108.805 70.8273 115.9 71.1216 120.123 71.3855L121.676 71.4566C125.786 71.5784 129.704 71.8829 132.516 74.3696C135.327 76.8564 136.028 80.5307 136.424 83.1697C137.286 88.874 136.556 93.3197 134.252 96.5372C133.611 97.5159 132.692 98.2797 131.613 98.7296C130.908 98.9745 130.165 99.0881 129.42 99.0646ZM116.662 72.1671C113.089 72.1671 109.181 72.6847 108.339 75.4455C107.11 79.4142 112.064 87.4022 114.51 90.4472C118.336 95.1974 123.827 98.08 129.197 98.1714C129.908 98.2085 130.62 98.1157 131.298 97.8973C132.211 97.5007 132.988 96.8442 133.531 96.0094C135.683 93.0456 136.363 88.7623 135.561 83.3016C135.185 80.7946 134.404 77.2116 131.947 75.0395C129.491 72.8674 125.604 72.4614 121.666 72.3498C121.209 72.3498 120.65 72.3498 120.102 72.2787C119.067 72.228 117.88 72.1671 116.662 72.1671Z" fill="#454545" />
<path d="M137.874 43.808C138.249 42.5429 138.885 41.3706 139.742 40.3671C140.181 39.8733 140.761 39.5266 141.404 39.374C142.047 39.2215 142.721 39.2706 143.335 39.5145C143.762 39.7688 144.12 40.1237 144.379 40.5484C144.637 40.973 144.787 41.4544 144.817 41.9505C144.867 42.9487 144.701 43.9458 144.33 44.8737C143.765 46.6382 142.787 48.2423 141.477 49.5529C140.827 50.204 140.05 50.7154 139.195 51.0558C138.34 51.3961 137.424 51.5582 136.504 51.5321" fill="#EEEAE9" />
<path d="M136.89 51.9282H136.474C136.367 51.9179 136.268 51.8667 136.198 51.7854C136.128 51.7041 136.093 51.5989 136.098 51.4917C136.101 51.4379 136.114 51.3851 136.138 51.3366C136.161 51.2881 136.195 51.245 136.235 51.2098C136.276 51.1747 136.324 51.1482 136.375 51.1321C136.427 51.116 136.481 51.1106 136.535 51.1162C137.403 51.1458 138.268 50.9971 139.076 50.6794C139.884 50.3617 140.619 49.8816 141.234 49.2689C142.496 48.0042 143.436 46.4553 143.975 44.7521C144.329 43.8802 144.488 42.9414 144.442 42.0015C144.417 41.5693 144.287 41.1497 144.062 40.7798C143.837 40.4098 143.525 40.1009 143.152 39.8801C142.615 39.6774 142.028 39.6427 141.47 39.7804C140.911 39.9181 140.408 40.2218 140.026 40.6515C139.218 41.6241 138.614 42.7494 138.25 43.9604C138.232 44.0102 138.204 44.0559 138.168 44.0947C138.132 44.1336 138.089 44.1648 138.041 44.1866C137.992 44.2083 137.94 44.2202 137.887 44.2214C137.834 44.2227 137.782 44.2133 137.732 44.1939C137.632 44.1572 137.55 44.0822 137.504 43.9852C137.458 43.8883 137.453 43.7772 137.489 43.6762C137.886 42.3539 138.557 41.1298 139.458 40.0831C139.958 39.5304 140.615 39.1452 141.342 38.9799C142.069 38.8146 142.828 38.8773 143.518 39.1595C144.012 39.44 144.429 39.8394 144.73 40.3214C145.031 40.8034 145.208 41.353 145.243 41.9203C145.299 42.9602 145.126 43.9997 144.736 44.9653C144.16 46.8097 143.143 48.4856 141.772 49.8474C140.468 51.1359 138.723 51.8799 136.89 51.9282Z" fill="#454545" />
<path d="M130.902 61.4082L132.475 77.0595C132.475 77.0595 131.714 85.961 121.929 84.1645C119.055 83.6096 116.34 82.4241 113.979 80.6936C111.619 78.963 109.671 76.7304 108.277 74.1566L108.643 46.0715C108.582 44.9246 110.967 40.9965 109.84 31.0698C113.195 30.7268 116.441 29.6886 119.371 28.0214C122.302 26.3542 124.853 24.0946 126.862 21.3867C126.862 21.3867 131.399 26.9895 133.307 28.7252C134.839 30.0073 136.203 31.477 137.367 33.0998C140.412 37.6673 140.28 45.1174 139.549 50.3548C138.839 54.6584 136.342 61.5198 130.902 61.4082Z" fill="#EEEAE9" />
<path d="M124.375 84.8953C123.527 84.8897 122.681 84.8082 121.847 84.6517C118.893 84.0752 116.102 82.8527 113.676 81.0717C111.249 79.2906 109.246 76.9951 107.81 74.3495C107.795 74.2826 107.795 74.2133 107.81 74.1465C107.81 74.1465 108.175 46.2441 108.175 46.0716C108.218 45.5697 108.323 45.0751 108.49 44.5998C109.058 42.651 110.226 38.591 109.383 31.1206C109.377 31.0608 109.382 31.0004 109.4 30.9428C109.417 30.8852 109.446 30.8318 109.485 30.7857C109.519 30.736 109.565 30.6948 109.617 30.6649C109.67 30.6351 109.729 30.6174 109.789 30.6131C113.072 30.2679 116.248 29.2475 119.118 27.6161C121.988 25.9847 124.49 23.7774 126.466 21.133C126.507 21.0737 126.562 21.0246 126.625 20.9894C126.688 20.9542 126.759 20.9339 126.831 20.93C126.904 20.9272 126.977 20.9414 127.044 20.9713C127.111 21.0012 127.17 21.0461 127.217 21.1026C127.217 21.1026 131.754 26.6952 133.621 28.3801C135.158 29.6918 136.522 31.1924 137.681 32.8461C141.193 38.1647 140.422 46.8836 139.935 50.3853C139.346 54.5773 136.89 61.5503 131.439 61.865L132.952 77.0189C132.709 79.4527 131.521 81.6941 129.643 83.2612C128.121 84.3844 126.265 84.9603 124.375 84.8953ZM108.693 74.045C110.072 76.5357 111.98 78.6941 114.282 80.3687C116.585 82.0433 119.226 83.1935 122.02 83.7382C124.943 84.266 127.308 83.86 129.054 82.5304C130.698 81.131 131.753 79.1626 132.008 77.0189L130.444 61.459C130.439 61.393 130.447 61.3266 130.468 61.2637C130.489 61.2009 130.522 61.143 130.566 61.0936C130.611 61.0479 130.665 61.0118 130.724 60.9874C130.784 60.963 130.847 60.9508 130.911 60.9515H131.033C136.108 60.9515 138.433 54.2423 138.991 50.2534C139.478 46.8531 140.229 38.4185 136.89 33.3638C135.757 31.7708 134.431 30.3252 132.941 29.0602C130.797 26.8597 128.764 24.5527 126.851 22.148C122.766 27.2984 116.828 30.6462 110.307 31.4759C111.068 38.8346 109.901 42.8845 109.292 44.8536C109.159 45.2422 109.067 45.6436 109.018 46.0513C109.018 46.0513 109.058 46.0817 108.693 74.045Z" fill="#454545" />
<path d="M131.438 66.5538C128.868 67.0035 126.223 66.7221 123.805 65.7418C118.233 63.7828 117.908 59.9766 117.908 59.9766C117.908 59.9766 122.597 62.9201 130.9 61.3773L131.438 66.5538Z" fill="#5C524D" />
<path d="M129.176 67.2139C127.292 67.1821 125.427 66.8391 123.655 66.1989C117.879 64.1689 117.483 60.2206 117.463 60.1089C117.455 60.025 117.471 59.9405 117.508 59.8652C117.546 59.7899 117.605 59.7268 117.677 59.6833C117.749 59.6397 117.832 59.6175 117.916 59.6192C118.001 59.6209 118.083 59.6464 118.153 59.6928C118.153 59.6928 122.812 62.5246 130.82 61.0326C130.945 61.0116 131.073 61.0408 131.176 61.1138C131.226 61.15 131.268 61.1971 131.298 61.2516C131.327 61.306 131.345 61.3664 131.348 61.4284L131.876 66.5034C131.898 66.5877 131.895 66.6764 131.868 66.7591C131.841 66.8418 131.791 66.9151 131.724 66.9703C131.68 67.0105 131.629 67.0411 131.573 67.0603C131.517 67.0795 131.458 67.0869 131.399 67.082C130.664 67.1971 129.92 67.2413 129.176 67.2139ZM118.59 60.86C119.036 62.0273 120.315 64.0674 123.959 65.3463C126.186 66.196 128.589 66.4786 130.952 66.1685L130.516 61.9359C124.406 62.9611 120.315 61.6822 118.59 60.86Z" fill="#454545" />
<path d="M124.061 36.0742C124.061 36.0742 129.846 43.0879 128.78 44.519C127.715 45.9502 124.284 45.4122 124.284 45.4122" fill="#EEEAE9" />
<path d="M125.714 45.9592C125.211 45.9586 124.71 45.9247 124.212 45.8577C124.106 45.8253 124.017 45.7561 123.958 45.6627C123.9 45.5693 123.878 45.4581 123.895 45.3494C123.912 45.2408 123.968 45.142 124.052 45.0713C124.137 45.0006 124.244 44.9627 124.354 44.9645C125.237 45.1066 127.683 45.2385 128.414 44.2438C128.87 43.6348 126.668 39.9504 123.694 36.3573C123.657 36.312 123.628 36.2598 123.611 36.2036C123.594 36.1475 123.588 36.0885 123.594 36.03C123.6 35.9716 123.617 35.9148 123.644 35.863C123.672 35.8112 123.709 35.7653 123.755 35.728C123.849 35.6544 123.968 35.6197 124.087 35.631C124.206 35.6423 124.316 35.6988 124.394 35.7889C126.201 37.9813 130.281 43.239 129.134 44.7818C128.414 45.7562 126.881 45.9592 125.714 45.9592Z" fill="#454545" />
<path d="M131.302 37.6836C131.774 37.6204 132.06 36.8441 131.941 35.9495C131.821 35.055 131.341 34.381 130.869 34.4442C130.397 34.5074 130.111 35.2838 130.23 36.1783C130.35 37.0729 130.83 37.7468 131.302 37.6836Z" fill="#454545" />
<path d="M117.067 37.7043C117.543 37.6987 117.921 36.9626 117.911 36.0602C117.9 35.1577 117.505 34.4306 117.029 34.4362C116.552 34.4418 116.175 35.1779 116.185 36.0803C116.196 36.9828 116.59 37.7099 117.067 37.7043Z" fill="#454545" />
<path d="M111.798 41.8686C111.449 40.5288 110.853 39.2657 110.042 38.1436C109.575 37.5067 108.964 36.9903 108.258 36.6373C107.551 36.2842 106.771 36.1047 105.982 36.1136C105.494 36.1326 105.016 36.2622 104.586 36.4925C104.155 36.7228 103.782 37.0479 103.495 37.4432C103.153 38.0128 102.938 38.6501 102.866 39.3108C102.348 42.7923 103.515 47.3293 106.794 49.1259C107.347 49.4737 107.988 49.6582 108.641 49.6582C109.295 49.6582 109.935 49.4737 110.488 49.1259" fill="#" />
<path d="M108.865 50.0604C108.072 50.0422 107.296 49.8333 106.601 49.4514C103.171 47.5635 101.932 42.8844 102.47 39.2202C102.547 38.5008 102.783 37.8074 103.161 37.1902C103.481 36.7428 103.898 36.3741 104.382 36.1121C104.865 35.8501 105.402 35.7016 105.952 35.6779C106.81 35.6717 107.657 35.8702 108.423 36.2567C109.189 36.6433 109.852 37.2069 110.357 37.9007C111.203 39.0603 111.823 40.3687 112.184 41.7577C112.212 41.86 112.2 41.9693 112.148 42.0623C112.097 42.1552 112.012 42.2244 111.91 42.2551C111.86 42.2722 111.807 42.2789 111.754 42.2747C111.702 42.2706 111.651 42.2557 111.604 42.2309C111.557 42.2062 111.516 42.1721 111.483 42.1309C111.45 42.0897 111.426 42.0421 111.413 41.9912C111.077 40.6998 110.506 39.4817 109.728 38.3981C109.306 37.8037 108.747 37.3198 108.099 36.9876C107.45 36.6554 106.731 36.4847 106.003 36.4899C105.578 36.5057 105.162 36.617 104.786 36.8156C104.41 37.0141 104.083 37.2948 103.83 37.6368C103.52 38.1565 103.329 38.7391 103.272 39.342C102.765 42.722 103.881 47.0357 106.987 48.7409C107.466 49.0597 108.025 49.2365 108.599 49.2509C109.174 49.2653 109.741 49.1166 110.235 48.8221C110.321 48.7589 110.428 48.7311 110.534 48.7443C110.639 48.7575 110.736 48.8108 110.804 48.8932C110.869 48.9781 110.899 49.0855 110.885 49.192C110.872 49.2986 110.817 49.3955 110.732 49.4616C110.193 49.8617 109.536 50.0722 108.865 50.0604Z" fill="#454545" />
<path d="M166.029 161.964C165.906 161.964 165.786 161.92 165.691 161.84C165.597 161.76 165.533 161.65 165.512 161.528C162.03 139.797 155.362 98.3744 155.057 97.1158C155.023 96.9813 155.043 96.8387 155.114 96.719C155.184 96.5994 155.299 96.5122 155.433 96.4764C155.568 96.4454 155.711 96.4683 155.83 96.5402C155.95 96.6121 156.036 96.7275 156.072 96.8621C156.468 98.4759 166.131 158.777 166.547 161.355C166.567 161.492 166.533 161.632 166.451 161.744C166.37 161.856 166.247 161.932 166.11 161.954L166.029 161.964Z" fill="#454545" />
<path d="M103.112 184.842L102.977 184.655H102.747H55.0401C50.0527 184.655 45.9305 180.138 45.9305 174.453V116.416C45.9305 110.737 50.0532 106.208 55.0401 106.208H181.016C186.003 106.208 190.134 110.738 190.165 116.419V174.453C190.165 180.138 186.037 184.655 181.049 184.655H131.929H131.726L131.592 184.807L115.869 202.609L103.112 184.842Z" fill="#EEEAE9" stroke="#454545" strokeWidth="0.9" />
<path d="M126.486 55.034C118.985 55.034 116.59 45.1377 116.488 44.7114C116.464 44.5944 116.486 44.4726 116.551 44.372C116.615 44.2715 116.717 44.2002 116.833 44.1735C116.89 44.1599 116.95 44.1579 117.008 44.1674C117.066 44.1769 117.122 44.1979 117.172 44.229C117.222 44.2601 117.265 44.3009 117.299 44.3488C117.334 44.3968 117.358 44.451 117.371 44.5084C117.371 44.5998 119.695 54.1408 126.506 54.1408C127.212 54.168 127.916 54.0394 128.567 53.764C129.217 53.4885 129.8 53.0731 130.272 52.5472C132.616 49.8778 131.987 44.7013 131.977 44.6505C131.968 44.5918 131.972 44.532 131.986 44.4745C132.001 44.417 132.027 44.3631 132.063 44.3158C132.099 44.2686 132.144 44.229 132.196 44.1993C132.247 44.1696 132.304 44.1505 132.363 44.143C132.48 44.131 132.598 44.1642 132.692 44.2358C132.787 44.3074 132.85 44.4121 132.87 44.5287C132.87 44.7622 133.57 50.162 130.952 53.1359C130.395 53.7634 129.705 54.2592 128.932 54.5875C128.16 54.9157 127.324 55.0682 126.486 55.034Z" fill="#454545" />
<path d="M70.1771 103.176C73.7637 101.377 78.12 99.9134 81.6644 101.817C83.3455 102.866 84.7632 104.287 85.8078 105.971C88.0669 109.136 90.1082 112.451 91.9175 115.893C92.3914 116.848 92.7954 118.019 92.0581 118.706C91.16 119.551 89.7377 118.638 88.9482 117.701C87.5236 116.028 86.3296 114.173 85.3981 112.183C87.2805 114.936 88.7735 117.936 89.8348 121.097C90.0177 121.471 90.0817 121.891 90.0181 122.302C89.8722 122.667 89.588 122.96 89.2274 123.116C88.8667 123.273 88.4587 123.281 88.0922 123.139C87.3497 122.858 86.7046 122.367 86.2347 121.728C84.1638 119.239 82.3741 116.53 80.8983 113.649C82.3828 116.657 83.8796 119.745 84.2431 123.078C84.3088 123.353 84.3162 123.639 84.2649 123.917C84.2135 124.196 84.1045 124.46 83.9449 124.694C83.7065 124.904 83.4062 125.031 83.0894 125.055C82.7726 125.079 82.4565 124.999 82.1891 124.828C81.6793 124.464 81.2568 123.992 80.952 123.445L75.6783 115.778C77.0141 118.725 77.9793 121.827 78.5518 125.012C78.6234 125.274 78.623 125.551 78.5505 125.813C78.2358 126.713 76.8342 126.341 76.1662 125.662C75.1107 124.468 74.2905 123.084 73.7489 121.585C72.0643 117.734 70.4974 113.712 68.6538 109.896C67.1293 106.761 66.4761 105.033 70.1771 103.176Z" fill="#EEEAE9" />
<path d="M77.958 126.765L78.0684 126.749C78.2758 126.704 78.4681 126.606 78.6266 126.465C78.785 126.324 78.9042 126.144 78.9726 125.944C79.0614 125.607 79.0675 125.254 78.9905 124.914C78.6521 123.052 78.1839 121.216 77.5894 119.419L80.5096 123.697C80.8645 124.309 81.3549 124.832 81.9432 125.225C82.3005 125.453 82.7244 125.554 83.1464 125.512C83.5684 125.471 83.964 125.288 84.2695 124.994C84.4767 124.712 84.621 124.389 84.6929 124.047C84.7648 123.705 84.7626 123.352 84.6863 123.01C84.5274 121.793 84.252 120.594 83.864 119.429C84.4984 120.359 85.1577 121.183 85.8586 122.011C86.357 122.728 87.0556 123.282 87.867 123.604C88.1454 123.691 88.4383 123.721 88.7284 123.693C89.0186 123.665 89.3002 123.578 89.5565 123.44C89.7497 123.337 89.9205 123.198 90.0587 123.029C90.197 122.859 90.2999 122.664 90.3614 122.455C90.4574 121.958 90.4043 121.445 90.2087 120.979C89.9753 120.255 89.7117 119.535 89.4297 118.828C89.9032 119.214 90.4782 119.455 91.0854 119.521C91.3167 119.539 91.5492 119.507 91.7672 119.428C91.9853 119.348 92.184 119.224 92.3501 119.062C92.871 118.572 93.2855 117.585 92.2992 115.692C90.4856 112.233 88.4385 108.901 86.1719 105.721C85.0837 103.975 83.6092 102.503 81.8619 101.418C78.8572 99.8217 74.971 100.259 69.9541 102.778C65.9002 104.812 66.657 106.884 68.2137 110.097C69.4397 112.62 70.5521 115.275 71.623 117.791C72.1819 119.102 72.7308 120.415 73.2481 121.722C73.8155 123.272 74.6668 124.702 75.7585 125.94C76.0393 126.234 76.3829 126.461 76.7638 126.604C77.1447 126.747 77.5529 126.802 77.958 126.765ZM75.6082 115.306C75.5536 115.304 75.4993 115.313 75.4476 115.33C75.3469 115.387 75.271 115.48 75.2346 115.59C75.1982 115.7 75.2039 115.819 75.2507 115.925C76.5744 118.833 77.527 121.896 78.0858 125.042C78.1393 125.224 78.1475 125.416 78.1096 125.603C78.0928 125.657 78.061 125.706 78.0176 125.744C77.9743 125.781 77.9213 125.806 77.8647 125.814C77.6113 125.827 77.3581 125.786 77.1214 125.695C76.8847 125.604 76.6697 125.464 76.4903 125.284C75.481 124.129 74.6936 122.798 74.1677 121.357C73.6003 120.057 73.0514 118.744 72.5442 117.435C71.4641 114.858 70.3387 112.186 69.0996 109.643C67.6013 106.545 67.1227 105.222 70.4321 103.558C75.1432 101.188 78.7608 100.74 81.5158 102.179C83.1359 103.197 84.5009 104.573 85.5059 106.202C87.7534 109.348 89.7816 112.644 91.5763 116.068C91.8963 116.687 92.3545 117.808 91.808 118.333C91.7304 118.406 91.6381 118.462 91.5372 118.497C91.4363 118.532 91.3291 118.545 91.2228 118.535C90.4804 118.382 89.8188 117.964 89.3603 117.361C88.9613 116.898 88.5854 116.452 88.2259 115.911C87.5179 114.519 86.7231 113.172 85.8463 111.879C85.7786 111.784 85.6785 111.717 85.5648 111.691C85.451 111.665 85.3317 111.681 85.2292 111.737C85.1284 111.796 85.0535 111.891 85.0192 112.002C84.9848 112.114 84.9936 112.234 85.0438 112.34C85.7112 113.761 86.5088 115.118 87.4264 116.392C88.209 117.951 88.8768 119.565 89.4243 121.221C89.5572 121.517 89.6069 121.843 89.5678 122.165C89.5075 122.344 89.3789 122.493 89.2099 122.579C89.0614 122.67 88.8961 122.731 88.7237 122.757C88.5513 122.783 88.3754 122.774 88.2064 122.731C87.5704 122.454 87.0251 122.003 86.6327 121.431C84.5887 118.966 82.8207 116.284 81.3603 113.434C81.2978 113.338 81.2021 113.269 81.0916 113.239C80.981 113.209 80.8633 113.22 80.7607 113.271C80.6582 113.322 80.578 113.41 80.5355 113.516C80.493 113.622 80.491 113.741 80.53 113.848C81.9552 116.804 83.4258 119.855 83.8218 123.132C83.8773 123.341 83.89 123.56 83.8592 123.774C83.8284 123.989 83.7546 124.195 83.6423 124.381C83.4755 124.509 83.2729 124.582 83.0626 124.59C82.8523 124.598 82.6447 124.541 82.4685 124.426C82.0062 124.101 81.6199 123.68 81.3361 123.192L76.0639 115.534C76.0203 115.454 75.9533 115.389 75.8718 115.349C75.7904 115.308 75.6984 115.293 75.6082 115.306Z" fill="#454545" />
<path d="M207.372 99.7322C207.43 99.7275 207.486 99.7122 207.538 99.6872C215.214 94.2396 220.02 88.2106 221.846 81.7353C229.021 77.1588 232.984 70.6532 232.606 63.8795C231.975 51.8119 217.685 42.7708 200.833 43.6732C183.98 44.6076 170.809 55.1665 171.449 67.2315C172.089 79.2964 186.367 88.3304 203.184 87.4163C205.507 87.2898 207.816 86.9722 210.087 86.4668C209.922 90.8412 208.779 95.1232 206.742 98.998C206.69 99.085 206.665 99.1856 206.671 99.2869C206.677 99.3882 206.712 99.4854 206.774 99.5662C206.835 99.6469 206.919 99.7074 207.016 99.7398C207.112 99.7722 207.215 99.7751 207.313 99.7481L207.372 99.7322ZM200.922 44.7007C217.2 43.8164 230.955 52.4343 231.59 63.933C231.952 70.3746 228.141 76.5868 221.138 80.9804C221.033 81.051 220.954 81.1535 220.912 81.2727C219.332 87.1032 215.203 92.6024 208.622 97.6282C210.263 93.9118 211.129 89.9003 211.169 85.8383C211.169 85.7615 211.151 85.6857 211.117 85.6166C211.084 85.5475 211.035 85.4869 210.975 85.4395C210.914 85.392 210.844 85.3589 210.769 85.3426C210.694 85.3263 210.616 85.3272 210.541 85.3453C208.13 85.9195 205.672 86.2772 203.197 86.4139C186.902 87.3133 173.124 78.6909 172.49 67.1922C171.855 55.6935 184.6 45.6178 200.892 44.7086L200.922 44.7007Z" fill="#D3CECB" />
<path d="M75.9248 170.151C75.6912 170.236 75.5706 170.494 75.6553 170.728C75.7401 170.961 75.9982 171.082 76.2319 170.997L75.9248 170.151ZM105.694 146.36L105.325 146.103L105.694 146.36ZM108.515 125.736L108.549 126.184L108.515 125.736ZM96.4601 134.933L96.8292 135.19L96.4601 134.933ZM151.042 128.024L151.44 128.233L151.042 128.024ZM149.712 113.888L149.745 114.337L149.712 113.888ZM133.632 131.3L133.202 131.17L133.202 131.17L133.632 131.3ZM133.617 131.352L134.047 131.482L134.047 131.482L133.617 131.352ZM157.054 147.878C157.291 147.802 157.421 147.548 157.345 147.312C157.268 147.075 157.015 146.945 156.778 147.021L157.054 147.878ZM133.202 131.17L133.186 131.221L134.047 131.482L134.063 131.431L133.202 131.17ZM136.528 147.943C142.241 142.272 147.672 135.394 151.44 128.233L150.643 127.814C146.927 134.878 141.557 141.683 135.893 147.305L136.528 147.943ZM151.44 128.233C152.08 127.017 153.347 123.307 153.658 119.949C153.814 118.276 153.74 116.614 153.172 115.382C152.884 114.756 152.461 114.228 151.866 113.877C151.271 113.526 150.542 113.375 149.678 113.44L149.745 114.337C150.465 114.283 151.003 114.413 151.409 114.652C151.815 114.892 152.126 115.263 152.355 115.759C152.821 116.77 152.913 118.236 152.762 119.866C152.461 123.114 151.225 126.708 150.643 127.814L151.44 128.233ZM149.678 113.44C145.534 113.75 141.938 116.475 139.139 119.927C136.333 123.388 134.271 127.646 133.202 131.17L134.063 131.431C135.102 128.005 137.114 123.854 139.838 120.494C142.568 117.126 145.959 114.62 149.745 114.337L149.678 113.44ZM133.186 131.221C132.448 133.654 131.751 137 131.92 140.149C132.09 143.295 133.133 146.344 135.983 148.012L136.438 147.236C133.964 145.788 132.981 143.108 132.819 140.101C132.657 137.097 133.325 133.865 134.047 131.482L133.186 131.221ZM96.091 134.675C93.9783 137.703 91.552 142.707 90.6804 147.575C89.8122 152.425 90.4654 157.33 94.7866 159.86L95.2412 159.083C91.4248 156.849 90.7184 152.47 91.5663 147.734C92.411 143.016 94.7766 138.132 96.8292 135.19L96.091 134.675ZM108.481 125.287C105.71 125.494 103.304 126.859 101.256 128.647C99.2081 130.434 97.4895 132.671 96.091 134.675L96.8292 135.19C98.2148 133.204 99.8836 131.039 101.848 129.325C103.811 127.611 106.04 126.372 108.549 126.184L108.481 125.287ZM106.063 146.617C107.294 144.848 110.281 139.483 111.829 134.629C112.599 132.216 113.042 129.845 112.667 128.091C112.477 127.198 112.067 126.431 111.349 125.919C110.635 125.41 109.678 125.197 108.481 125.287L108.549 126.184C109.611 126.105 110.337 126.302 110.827 126.652C111.313 126.999 111.63 127.541 111.787 128.279C112.109 129.786 111.735 131.961 110.972 134.355C109.454 139.114 106.511 144.399 105.325 146.103L106.063 146.617ZM95.3309 159.791C99.3824 155.769 102.824 151.27 106.063 146.617L105.325 146.103C102.099 150.737 98.6929 155.185 94.6969 159.152L95.3309 159.791ZM76.2319 170.997C83.5488 168.342 89.6767 165.404 95.3309 159.791L94.6969 159.152C89.1766 164.632 83.1888 167.515 75.9248 170.151L76.2319 170.997ZM94.7866 159.86C101.288 163.666 109.486 162.902 117.124 160.117C124.773 157.327 131.961 152.476 136.528 147.943L135.893 147.305C131.416 151.749 124.338 156.528 116.816 159.271C109.282 162.019 101.403 162.69 95.2412 159.083L94.7866 159.86ZM135.983 148.012C139.071 149.82 142.811 150.274 146.52 150.039C150.233 149.803 153.958 148.876 157.054 147.878L156.778 147.021C153.72 148.007 150.074 148.912 146.463 149.14C142.848 149.369 139.312 148.918 136.438 147.236L135.983 148.012Z" fill="#D7D3D1" />
<path d="M34.1208 1.67272L34.1208 1.67271C39.9029 0.930545 45.3366 1.92561 49.4422 4.11327C53.5488 6.30152 56.2929 9.65956 56.8045 13.6458C57.3161 17.632 55.5083 21.5751 52.0884 24.7305C48.6694 27.8851 43.6661 30.2212 37.8913 30.9639L37.8912 30.9639C36.1449 31.1889 34.3819 31.2561 32.6236 31.1648L32.0474 31.1349L32.1587 31.7011C32.6982 34.4435 33.7686 37.0476 35.3035 39.3693C29.2428 36.2215 26.3184 32.6297 24.9143 29.7128L24.8379 29.5541L24.6742 29.4894C19.3949 27.4043 15.8095 23.6168 15.215 18.9913L15.2149 18.991C14.6999 15.0042 16.5008 11.0607 19.9169 7.90532C23.3321 4.7507 28.3352 2.4146 34.1208 1.67272Z" stroke="#D3CECB" strokeWidth="0.9" />
<path d="M61.0231 135.858L48.305 133.863L49.2502 146.414L57.0239 149.239L65.7092 152.397L63.3431 144.054L61.0231 135.858Z" fill="#EEEAE9" />
<path d="M66.0579 152.457C66.0648 152.408 66.0613 152.357 66.0477 152.309L61.3616 135.77C61.3421 135.706 61.305 135.649 61.2544 135.604C61.2038 135.56 61.1417 135.529 61.075 135.517L48.3569 133.522C48.3039 133.516 48.2503 133.521 48.1995 133.537C48.1487 133.552 48.1018 133.578 48.0616 133.612C48.0239 133.647 47.9944 133.69 47.9751 133.737C47.9558 133.784 47.9471 133.835 47.9496 133.887L48.9053 146.418C48.9097 146.484 48.9336 146.547 48.9742 146.6C49.0148 146.653 49.0702 146.693 49.1337 146.716L65.5927 152.7C65.6534 152.724 65.7197 152.73 65.7838 152.719C65.848 152.708 65.9075 152.679 65.9554 152.636C66.0052 152.587 66.0406 152.525 66.0579 152.457ZM60.7765 136.167L65.2501 151.856L49.6494 146.181L48.7496 134.284L60.7765 136.167Z" fill="#454545" />
<path d="M55.3166 137.493L51.3945 141.013L15.4608 102.75L19.3828 99.2312L55.3166 137.493Z" fill="#EEEAE9" />
<path d="M55.6719 137.552L55.6801 137.504C55.6838 137.459 55.6774 137.413 55.6612 137.37C55.645 137.327 55.6195 137.289 55.5863 137.256L19.6493 98.9922C19.6177 98.9586 19.5797 98.9313 19.5375 98.912C19.4953 98.8927 19.4496 98.8817 19.4032 98.8797C19.3104 98.8779 19.2205 98.9099 19.1509 98.9694L15.2269 102.5C15.1925 102.529 15.1645 102.565 15.1448 102.606C15.1251 102.646 15.1141 102.69 15.1124 102.735C15.1107 102.78 15.1183 102.825 15.1349 102.867C15.1515 102.909 15.1766 102.948 15.2087 102.98L51.1388 141.243C51.2014 141.311 51.2888 141.353 51.382 141.358C51.4752 141.364 51.5665 141.333 51.6361 141.273L55.56 137.743C55.6186 137.693 55.6581 137.626 55.6719 137.552ZM19.3681 99.7079L54.811 137.466L51.4142 140.519L15.9632 102.767L19.3681 99.7079Z" fill="#5C524D" />
<path d="M15.465 102.744L13.3205 108.151L49.2506 146.414L51.4021 141.008L15.465 102.744Z" fill="#5C524D" />
<path d="M51.7505 141.068C51.7591 141.015 51.7552 140.961 51.7392 140.91C51.7231 140.858 51.6954 140.811 51.658 140.772L15.728 102.509C15.6862 102.466 15.6344 102.434 15.5772 102.416C15.52 102.398 15.4592 102.395 15.4004 102.405C15.3415 102.416 15.2864 102.441 15.2401 102.478C15.1938 102.515 15.1578 102.563 15.1352 102.617L12.9907 108.024C12.966 108.084 12.9591 108.15 12.9709 108.214C12.9827 108.278 13.0127 108.337 13.0575 108.386L48.9945 146.65C49.0356 146.693 49.0869 146.724 49.1438 146.742C49.2006 146.761 49.261 146.764 49.3195 146.753C49.3779 146.742 49.4325 146.717 49.4781 146.68C49.5237 146.643 49.5589 146.595 49.5803 146.541L51.7318 141.135L51.7505 141.068ZM15.5906 103.389L50.994 141.085L49.1238 145.775L13.7226 108.108L15.5906 103.389Z" fill="#5C524D" />
<path d="M25.0873 97.5939L19.3945 99.228L55.3245 137.491L61.0174 135.857L25.0873 97.5939Z" fill="#5C524D" />
<path d="M61.367 135.946C61.3741 135.893 61.3695 135.839 61.3535 135.788C61.3376 135.737 61.3107 135.69 61.2745 135.65L25.3492 97.3593C25.3025 97.3132 25.2443 97.2799 25.1805 97.2627C25.1166 97.2455 25.0494 97.2452 24.9856 97.2616L19.2858 98.8944C19.2279 98.9097 19.1755 98.9401 19.1341 98.9824C19.0927 99.0246 19.0639 99.0771 19.0507 99.1344C19.0349 99.1901 19.034 99.2491 19.0482 99.3055C19.0623 99.362 19.091 99.414 19.1315 99.4567L55.0616 137.72C55.1107 137.764 55.1717 137.793 55.2371 137.805C55.3026 137.816 55.3699 137.809 55.431 137.783L61.1296 136.157C61.1865 136.14 61.2379 136.109 61.279 136.067C61.3201 136.025 61.3496 135.974 61.3647 135.918L61.367 135.946ZM25.01 97.9949L60.4135 135.691L55.4693 137.103L20.0345 99.381L25.01 97.9949Z" fill="#454545" />
<path d="M63.3443 144.054L60.8645 144.117L57.3153 147.305L57.0251 149.239L65.7104 152.397L63.3443 144.054Z" fill="#B5AFAB" />
<path d="M66.0577 152.457C66.0646 152.408 66.0611 152.357 66.0475 152.308L63.6825 143.958C63.6602 143.885 63.6136 143.82 63.5499 143.775C63.4863 143.73 63.4092 143.706 63.3308 143.708L60.8499 143.779C60.7667 143.779 60.687 143.811 60.6267 143.866L57.0786 147.048C57.0218 147.104 56.9823 147.175 56.9644 147.252L56.6801 149.194C56.6674 149.271 56.6828 149.35 56.7237 149.418C56.7646 149.485 56.8284 149.536 56.9037 149.562L65.589 152.72C65.6497 152.744 65.716 152.751 65.7801 152.74C65.8443 152.728 65.9038 152.7 65.9517 152.657C66.0061 152.602 66.043 152.533 66.0577 152.457ZM63.0822 144.402L65.1872 151.845L57.4136 149.019L57.6438 147.475L61.0336 144.462L63.0822 144.402Z" fill="#454545" />
<path d="M73.8776 59.2742L68.5595 58.375L64.808 62.2459L64.0142 56.9138L59.1738 54.5463L64.0001 52.1437L64.7589 46.8115L68.5384 50.6543L73.8425 49.727L71.3485 54.5112L73.8776 59.2742Z" fill="#D3CECB" />
<path d="M31.6856 197.783L25.1642 196.68L24.9049 196.636L24.7218 196.825L20.1199 201.574L19.146 195.033L19.1073 194.773L18.8712 194.657L12.9346 191.753L18.8544 188.806L19.0896 188.689L19.1266 188.429L20.0574 181.888L24.6946 186.603L24.8786 186.79L25.1372 186.745L31.6422 185.607L28.5826 191.477L28.461 191.71L28.5844 191.942L31.6856 197.783Z" stroke="#D3CECB" />
<path d="M245.799 173.629L238.276 172.357L232.97 177.832L231.847 170.29L225 166.941L231.827 163.542L232.9 156L238.246 161.436L245.749 160.124L242.221 166.891L245.799 173.629Z" fill="#BAB5B1" />
<path d="M220.924 155.81L219.388 153.342L219.249 153.119L218.986 153.107L216.081 152.974L217.952 150.748L218.121 150.547L218.051 150.293L217.279 147.493L219.973 148.584L220.217 148.683L220.436 148.538L222.862 146.939L222.66 149.84L222.642 150.102L222.847 150.266L225.116 152.077L222.291 152.779L222.036 152.842L221.943 153.089L220.924 155.81Z" stroke="#D3CECB" />
<path d="M46.3063 94.4625L41.8463 90.7153L41.665 90.5631L41.4369 90.6262L35.8235 92.1786L38.0027 86.777L38.0912 86.5576L37.9606 86.3603L34.7482 81.5065L40.5584 81.9101L40.7943 81.9264L40.9416 81.7415L44.5671 77.1896L45.9847 82.8389L46.0422 83.068L46.2635 83.1509L51.7106 85.1897L46.7684 88.2814L46.5677 88.407L46.5575 88.6435L46.3063 94.4625Z" stroke="#D3CECB" strokeWidth="0.9" />
</svg>
);
export default Empty;

View File

@@ -0,0 +1,76 @@
<svg width="406" height="302" viewBox="0 0 406 302" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_4274_29461)">
<path d="M160.119 242.657L291.085 242.808C292.951 242.713 294.731 241.999 296.145 240.779C297.56 239.559 298.527 237.903 298.895 236.071L323.256 107.464C323.471 106.663 323.497 105.823 323.332 105.01C323.167 104.197 322.816 103.433 322.306 102.779C321.796 102.125 321.142 101.598 320.394 101.24C319.646 100.881 318.825 100.702 317.995 100.714L187.029 100.563C185.163 100.658 183.383 101.372 181.969 102.592C180.554 103.812 179.587 105.469 179.219 107.3L154.858 235.907C154.643 236.709 154.617 237.549 154.782 238.361C154.947 239.174 155.298 239.938 155.808 240.592C156.318 241.246 156.972 241.773 157.72 242.131C158.468 242.49 159.289 242.67 160.119 242.657Z" fill="white"/>
<path d="M291.048 243.446L160.081 243.295C159.199 243.311 158.325 243.131 157.522 242.766C156.718 242.402 156.007 241.863 155.438 241.188C154.837 240.442 154.412 239.571 154.194 238.638C153.975 237.706 153.969 236.736 154.177 235.801L178.601 107.181C179.004 105.21 180.051 103.43 181.577 102.119C183.102 100.808 185.02 100.041 187.029 99.9393L317.996 100.091C318.868 100.082 319.731 100.265 320.525 100.627C321.318 100.989 322.023 101.521 322.588 102.185C323.188 102.934 323.613 103.807 323.832 104.742C324.05 105.676 324.056 106.647 323.85 107.585L299.513 236.205C299.109 238.182 298.056 239.967 296.523 241.278C294.99 242.59 293.063 243.353 291.048 243.446ZM160.081 242.033L291.048 242.185C292.767 242.095 294.408 241.434 295.71 240.308C297.012 239.182 297.902 237.654 298.239 235.965L322.588 107.357C322.762 106.604 322.765 105.82 322.597 105.065C322.429 104.309 322.094 103.601 321.617 102.992C321.163 102.466 320.598 102.046 319.964 101.763C319.329 101.479 318.64 101.339 317.945 101.352L186.978 101.201C185.259 101.291 183.619 101.951 182.316 103.078C181.014 104.204 180.124 105.732 179.787 107.421L155.476 236.028C155.303 236.782 155.3 237.565 155.468 238.321C155.636 239.076 155.971 239.784 156.448 240.393C156.902 240.92 157.466 241.339 158.1 241.623C158.735 241.906 159.424 242.047 160.119 242.033H160.081Z" fill="#231F20"/>
<path d="M320.973 120.187L176.936 120.011C176.769 120.011 176.608 119.944 176.49 119.826C176.372 119.708 176.305 119.547 176.305 119.38C176.308 119.213 176.376 119.055 176.494 118.937C176.611 118.82 176.77 118.752 176.936 118.749L320.986 118.926C321.153 118.926 321.313 118.992 321.432 119.11C321.55 119.229 321.616 119.389 321.616 119.556C321.613 119.725 321.544 119.885 321.424 120.003C321.303 120.121 321.142 120.187 320.973 120.187Z" fill="#231F20"/>
<path d="M311.308 112.057C312.689 110.592 312.831 108.482 311.624 107.345C310.418 106.207 308.32 106.473 306.938 107.938C305.557 109.403 305.416 111.513 306.622 112.651C307.829 113.788 309.927 113.523 311.308 112.057Z" fill="#D1D3D4"/>
<path d="M299.838 112.036C301.219 110.57 301.36 108.46 300.154 107.323C298.947 106.185 296.849 106.451 295.468 107.916C294.087 109.382 293.945 111.491 295.152 112.629C296.358 113.766 298.456 113.501 299.838 112.036Z" fill="#D1D3D4"/>
<path d="M288.366 112.022C289.747 110.557 289.889 108.447 288.682 107.31C287.475 106.172 285.377 106.438 283.996 107.903C282.615 109.368 282.473 111.478 283.68 112.616C284.887 113.753 286.985 113.488 288.366 112.022Z" fill="#D1D3D4"/>
<path d="M252.859 130.098L240.483 195.412L292.966 195.475L305.342 130.161L252.859 130.098Z" fill="white"/>
<path d="M252.847 130.1H305.342L292.978 195.413H240.483L252.847 130.1ZM252.847 128.839C252.55 128.833 252.26 128.933 252.03 129.12C251.799 129.307 251.641 129.569 251.585 129.86L239.221 195.174C239.186 195.358 239.191 195.548 239.239 195.73C239.286 195.912 239.373 196.081 239.494 196.225C239.615 196.369 239.766 196.484 239.938 196.562C240.109 196.64 240.295 196.678 240.483 196.675H292.965C293.264 196.68 293.555 196.578 293.786 196.389C294.017 196.2 294.173 195.934 294.227 195.64L306.591 130.327C306.625 130.146 306.619 129.959 306.573 129.781C306.527 129.602 306.443 129.435 306.326 129.293C306.207 129.15 306.059 129.036 305.891 128.957C305.723 128.879 305.54 128.838 305.354 128.839H252.872H252.847Z" fill="#231F20"/>
<path d="M290.177 147.233C290.075 148.083 290.161 148.944 290.43 149.757C290.669 150.23 290.957 150.678 291.288 151.094C292.328 152.684 292.928 154.521 293.029 156.418C293.168 157.702 292.995 159 292.524 160.203C292.145 161.086 291.54 161.868 291.187 162.726C290.766 163.769 290.699 164.921 290.997 166.006C291.288 167.028 291.868 168.138 291.338 169.046C291.13 169.343 290.845 169.578 290.514 169.727C290.183 169.876 289.819 169.933 289.458 169.892C288.738 169.8 288.032 169.627 287.351 169.374C283.503 168.264 278.659 168.794 274.622 168.517L259.483 167.457C257.426 167.305 254.878 166.788 254.335 164.795C253.881 163.117 255.521 160.316 256.177 158.739C257.187 156.02 258.513 153.429 260.126 151.018C261.057 149.917 261.85 148.705 262.485 147.41C262.769 146.552 263.009 145.681 263.204 144.798C264.07 142.261 265.568 139.986 267.557 138.188C268.841 136.861 270.317 135.734 271.935 134.844C277.423 131.993 286.544 133.923 289.042 139.992C289.943 142.294 290.331 144.766 290.177 147.233Z" fill="white"/>
<path d="M289.584 170.56H289.42C288.813 170.509 288.214 170.377 287.641 170.169L287.175 170.03C284.44 169.415 281.636 169.164 278.835 169.285C277.334 169.285 275.934 169.285 274.571 169.185L259.432 168.125C256.089 167.898 254.222 166.863 253.717 164.996C253.288 163.432 254.285 161.312 255.079 159.609C255.269 159.205 255.445 158.839 255.584 158.524C256.61 155.757 257.957 153.119 259.596 150.664C259.798 150.374 260.012 150.097 260.227 149.819C260.893 149.023 261.449 148.14 261.88 147.195C262.064 146.689 262.212 146.17 262.321 145.643C262.397 145.302 262.485 144.974 262.573 144.634C263.464 141.989 265.025 139.621 267.103 137.758C268.424 136.375 269.952 135.205 271.632 134.289C273.401 133.466 275.317 133.005 277.267 132.934C279.217 132.862 281.161 133.181 282.986 133.872C284.438 134.339 285.777 135.104 286.915 136.118C288.054 137.133 288.967 138.375 289.597 139.764C290.518 142.154 290.905 144.716 290.732 147.271V147.384C290.618 147.906 290.628 148.447 290.762 148.965C290.896 149.482 291.149 149.96 291.502 150.361L291.729 150.689C292.843 152.374 293.487 154.325 293.596 156.341C293.747 157.742 293.552 159.159 293.028 160.467C292.828 160.901 292.601 161.323 292.347 161.728C292.101 162.134 291.882 162.556 291.691 162.99C291.333 163.899 291.28 164.9 291.54 165.841C291.54 166.043 291.666 166.245 291.729 166.447C291.956 166.901 292.081 167.4 292.094 167.908C292.107 168.416 292.008 168.921 291.805 169.386C291.564 169.754 291.234 170.054 290.846 170.259C290.457 170.464 290.023 170.568 289.584 170.56ZM277.725 134.226C275.838 134.208 273.974 134.641 272.288 135.487C270.722 136.341 269.297 137.431 268.061 138.717C266.144 140.42 264.705 142.595 263.885 145.025C263.785 145.34 263.721 145.656 263.646 145.971C263.517 146.558 263.348 147.135 263.141 147.699C262.673 148.757 262.057 149.743 261.312 150.626L260.694 151.446C259.126 153.816 257.831 156.356 256.833 159.016C256.694 159.357 256.505 159.735 256.316 160.164C255.635 161.59 254.714 163.558 255.054 164.655C255.395 165.753 256.934 166.636 259.609 166.825L274.748 167.885C276.073 167.973 277.46 167.986 278.924 167.986C281.848 167.861 284.776 168.129 287.629 168.781L288.121 168.932C288.594 169.099 289.085 169.209 289.584 169.26C289.821 169.291 290.063 169.26 290.284 169.169C290.505 169.078 290.699 168.931 290.846 168.743C291.123 168.276 290.846 167.595 290.657 166.876C290.581 166.649 290.505 166.422 290.442 166.207C290.108 164.985 290.183 163.686 290.657 162.511C290.865 162.018 291.11 161.542 291.388 161.085C291.615 160.694 291.83 160.328 292.007 159.95C292.435 158.829 292.59 157.622 292.461 156.43C292.378 154.633 291.816 152.89 290.833 151.383L290.606 151.081C290.331 150.726 290.098 150.341 289.912 149.933C289.612 149.084 289.504 148.179 289.597 147.283L290.253 147.195H289.61C289.778 144.83 289.424 142.457 288.575 140.243C288.014 139.019 287.201 137.925 286.191 137.034C285.18 136.143 283.994 135.474 282.709 135.071C281.111 134.498 279.423 134.216 277.725 134.238V134.226Z" fill="#231F20"/>
<path d="M244.053 176.552L240.483 195.476H292.978L296.548 176.615C292.221 171.43 284.954 169.639 278.558 169.046C276.035 168.819 273.764 168.755 271.783 168.755H271.316C269.462 168.755 267.317 168.755 265.008 169.02H264.895C258.246 169.563 250.336 171.38 244.053 176.552Z" fill="#F0CC00"/>
<path d="M264.049 172.641L265.652 190.303L266.131 195.451H267.317L269.739 190.291L278.003 172.729C274.874 171.632 267.594 171.632 264.049 172.641Z" fill="#231F20"/>
<path d="M278.028 172.667C277.168 173.873 276.032 174.857 274.716 175.537C273.4 176.218 271.94 176.574 270.458 176.578C269.115 176.67 267.776 176.342 266.627 175.639C265.479 174.936 264.578 173.893 264.049 172.654L267.632 157.275H280.248L278.028 172.667Z" fill="white"/>
<path d="M270.471 177.224C268.993 177.31 267.524 176.937 266.266 176.155C265.008 175.374 264.022 174.222 263.444 172.858C263.407 172.744 263.407 172.62 263.444 172.505L267.027 157.139C267.061 156.996 267.141 156.869 267.256 156.778C267.37 156.687 267.512 156.636 267.658 156.634H280.274C280.368 156.633 280.461 156.653 280.546 156.692C280.631 156.732 280.706 156.789 280.766 156.861C280.828 156.931 280.874 157.014 280.9 157.103C280.926 157.192 280.932 157.287 280.917 157.379L278.684 172.758C278.674 172.847 278.644 172.933 278.596 173.01C277.679 174.312 276.462 175.375 275.048 176.109C273.634 176.842 272.064 177.225 270.471 177.224ZM264.731 172.606C265.244 173.669 266.064 174.554 267.085 175.146C268.106 175.737 269.281 176.008 270.459 175.924C271.807 175.92 273.136 175.602 274.34 174.995C275.545 174.389 276.591 173.51 277.398 172.43L279.492 157.946H268.137L264.731 172.606Z" fill="#231F20"/>
<path d="M279.012 163.875C277.777 165.319 276.256 166.491 274.546 167.319C271.783 168.745 267.733 170.334 264.794 169.842L266.522 163.345C268.743 163.611 270.988 163.611 273.209 163.345C275.252 163.261 277.272 162.874 279.201 162.197C279.542 162.121 279.517 163.257 279.012 163.875Z" fill="#231F20"/>
<path d="M265.929 170.574C265.507 170.575 265.085 170.541 264.668 170.473C264.58 170.46 264.497 170.429 264.423 170.381C264.349 170.333 264.286 170.27 264.239 170.196C264.189 170.123 264.157 170.039 264.144 169.952C264.131 169.865 264.137 169.776 264.163 169.691L265.891 163.194C265.93 163.046 266.023 162.917 266.151 162.833C266.279 162.749 266.434 162.716 266.585 162.74C268.755 163.01 270.95 163.01 273.12 162.74C275.107 162.663 277.071 162.293 278.949 161.642C279.103 161.58 279.274 161.57 279.434 161.616C279.594 161.662 279.734 161.76 279.832 161.895C279.991 162.279 280.043 162.699 279.98 163.111C279.918 163.522 279.745 163.909 279.479 164.229C278.194 165.746 276.603 166.976 274.811 167.837C272.767 168.934 269.071 170.574 265.929 170.574ZM265.589 169.313C268.629 169.426 272.603 167.61 274.256 166.79C275.894 166.017 277.348 164.902 278.52 163.522C278.598 163.404 278.662 163.277 278.709 163.144C276.931 163.668 275.098 163.981 273.247 164.077C271.15 164.308 269.035 164.308 266.939 164.077L265.589 169.313Z" fill="#231F20"/>
<path d="M284.475 196.119H284.273C284.191 196.094 284.116 196.052 284.05 195.996C283.985 195.941 283.931 195.873 283.892 195.797C283.854 195.72 283.831 195.637 283.824 195.552C283.818 195.466 283.829 195.38 283.857 195.299L288.903 179.453C288.93 179.371 288.972 179.294 289.028 179.227C289.085 179.161 289.153 179.106 289.231 179.067C289.308 179.027 289.392 179.003 289.479 178.995C289.566 178.988 289.653 178.998 289.736 179.025C289.819 179.051 289.895 179.094 289.962 179.15C290.028 179.206 290.083 179.275 290.123 179.352C290.163 179.429 290.187 179.514 290.194 179.6C290.201 179.687 290.191 179.774 290.165 179.857L285.118 195.703C285.07 195.832 284.981 195.942 284.865 196.017C284.75 196.092 284.613 196.128 284.475 196.119Z" fill="#231F20"/>
<path d="M248.973 196.058C248.801 196.048 248.64 195.971 248.525 195.844C248.409 195.716 248.348 195.549 248.355 195.377L249.314 179.518C249.361 179.383 249.45 179.266 249.569 179.187C249.688 179.107 249.83 179.069 249.973 179.077C250.116 179.086 250.253 179.141 250.361 179.234C250.47 179.328 250.545 179.454 250.575 179.594L249.617 195.452C249.607 195.616 249.535 195.77 249.415 195.883C249.296 195.996 249.137 196.058 248.973 196.058Z" fill="#231F20"/>
<path d="M266.661 153.741C266.926 150.133 262.763 149.237 262.183 153.489C261.92 154.832 262.056 156.222 262.574 157.488C262.679 157.739 262.836 157.964 263.035 158.15C263.234 158.336 263.47 158.477 263.727 158.565C263.984 158.653 264.257 158.686 264.528 158.662C264.799 158.637 265.062 158.555 265.299 158.422" fill="white"/>
<path d="M264.365 159.334C264.13 159.339 263.895 159.305 263.671 159.233C263.307 159.121 262.972 158.934 262.685 158.684C262.398 158.434 262.167 158.127 262.006 157.783C261.423 156.401 261.256 154.88 261.526 153.405C261.88 150.882 263.406 149.885 264.769 150.024C265.603 150.194 266.337 150.688 266.809 151.397C267.282 152.106 267.455 152.972 267.292 153.809C267.243 153.944 267.151 154.059 267.03 154.137C266.909 154.215 266.766 154.251 266.622 154.24C266.479 154.228 266.343 154.17 266.236 154.074C266.129 153.978 266.057 153.849 266.03 153.708C266.131 152.257 265.412 151.412 264.642 151.336C263.873 151.26 263.078 151.803 262.838 153.594C262.592 154.803 262.707 156.058 263.166 157.202C263.254 157.387 263.377 157.552 263.528 157.688C263.68 157.825 263.857 157.93 264.049 157.997C264.207 158.044 264.373 158.058 264.536 158.036C264.699 158.014 264.856 157.958 264.996 157.871C265.148 157.788 265.327 157.769 265.495 157.816C265.662 157.863 265.804 157.973 265.891 158.123C265.972 158.276 265.99 158.454 265.94 158.619C265.891 158.785 265.778 158.924 265.626 159.006C265.242 159.224 264.807 159.337 264.365 159.334Z" fill="#231F20"/>
<path d="M282.986 153.769C284.084 150.161 288.588 149.278 287.566 153.53C287.26 154.992 286.606 156.359 285.661 157.516C284.715 158.551 283.428 159.043 282.57 158.45" fill="white"/>
<path d="M283.478 159.355C283.028 159.368 282.585 159.235 282.216 158.977C282.076 158.878 281.98 158.727 281.951 158.558C281.921 158.389 281.96 158.215 282.059 158.075C282.157 157.934 282.308 157.838 282.477 157.809C282.646 157.779 282.82 157.818 282.961 157.917C283.478 158.27 284.437 157.917 285.194 157.072C286.066 155.998 286.667 154.73 286.947 153.375C287.048 152.946 287.313 151.571 286.556 151.357C285.799 151.142 284.197 152.063 283.617 153.955C283.591 154.038 283.549 154.115 283.494 154.182C283.438 154.249 283.37 154.304 283.294 154.344C283.139 154.426 282.958 154.443 282.79 154.391C282.623 154.339 282.483 154.223 282.402 154.068C282.32 153.913 282.303 153.732 282.355 153.564C283.137 151.041 285.358 149.691 286.872 150.108C287.351 150.234 288.865 150.877 288.133 153.678C287.808 155.246 287.102 156.711 286.077 157.942C285.761 158.34 285.368 158.67 284.921 158.913C284.475 159.156 283.984 159.306 283.478 159.355Z" fill="#231F20"/>
<path d="M266.787 145.82L264.807 156.279C263.861 161.325 267.04 165.362 271.91 165.375H272.931C275.447 165.247 277.848 164.286 279.757 162.644C281.666 161.002 282.975 158.772 283.478 156.304C283.958 153.781 285.699 149.845 283.857 147.952C280.917 144.924 278.066 142.906 276.439 138.806C272.704 139.588 266.787 140.622 266.787 145.82Z" fill="white"/>
<path d="M272.944 166.018H271.909C270.751 166.043 269.602 165.809 268.548 165.33C267.493 164.852 266.559 164.143 265.816 163.255C265.029 162.266 264.475 161.113 264.193 159.882C263.911 158.65 263.91 157.371 264.188 156.139L266.156 145.756C266.156 140.256 272.035 139.044 275.908 138.25L276.325 138.161C276.473 138.13 276.628 138.152 276.762 138.223C276.896 138.295 277 138.411 277.057 138.552C278.25 141.273 280.07 143.672 282.368 145.554C283.011 146.173 283.629 146.816 284.336 147.485C286.026 149.213 285.219 152.178 284.576 154.562C284.399 155.218 284.235 155.824 284.134 156.404C283.602 159.022 282.213 161.388 280.187 163.129C278.16 164.87 275.612 165.886 272.944 166.018ZM276.047 139.524C272.376 140.293 267.443 141.366 267.443 145.832C267.45 145.874 267.45 145.916 267.443 145.958L265.462 156.404C265.221 157.448 265.217 158.533 265.451 159.579C265.685 160.625 266.15 161.605 266.812 162.447C267.438 163.187 268.222 163.777 269.106 164.172C269.991 164.568 270.953 164.758 271.922 164.731H272.956C275.319 164.606 277.573 163.7 279.364 162.153C281.155 160.607 282.38 158.509 282.847 156.19C282.961 155.609 283.137 154.928 283.327 154.247C283.907 152.077 284.588 149.617 283.402 148.418C282.746 147.737 282.141 147.157 281.472 146.526C279.186 144.615 277.336 142.236 276.047 139.549V139.524Z" fill="#231F20"/>
<path d="M273.852 157.794C273.384 157.792 272.916 157.763 272.452 157.706C272.359 157.708 272.267 157.69 272.182 157.653C272.097 157.616 272.021 157.561 271.959 157.492C271.897 157.423 271.851 157.342 271.823 157.253C271.796 157.165 271.788 157.071 271.8 156.979C271.812 156.887 271.843 156.799 271.893 156.721C271.942 156.642 272.007 156.575 272.085 156.524C272.162 156.474 272.25 156.44 272.342 156.426C272.433 156.412 272.527 156.418 272.616 156.444C273.375 156.521 274.14 156.521 274.899 156.444C275.136 154.424 275.016 152.379 274.546 150.401C274.517 150.23 274.556 150.055 274.655 149.913C274.754 149.772 274.906 149.675 275.076 149.644C275.246 149.618 275.42 149.658 275.561 149.757C275.702 149.856 275.799 150.005 275.833 150.174C276.073 151.524 276.817 156.04 275.909 157.277C275.825 157.398 275.717 157.5 275.59 157.577C275.464 157.653 275.323 157.701 275.177 157.718C274.737 157.773 274.295 157.798 273.852 157.794Z" fill="#231F20"/>
<path d="M239.083 143.674H185.187C185.087 143.688 184.986 143.68 184.889 143.651C184.793 143.622 184.703 143.572 184.628 143.506C184.552 143.44 184.491 143.358 184.449 143.266C184.408 143.174 184.386 143.075 184.386 142.974C184.386 142.873 184.408 142.774 184.449 142.682C184.491 142.59 184.552 142.509 184.628 142.442C184.703 142.376 184.793 142.327 184.889 142.298C184.986 142.269 185.087 142.26 185.187 142.274L239.083 142.337C239.252 142.36 239.408 142.443 239.52 142.572C239.633 142.701 239.695 142.866 239.695 143.037C239.695 143.208 239.633 143.373 239.52 143.502C239.408 143.631 239.252 143.715 239.083 143.737V143.674Z" fill="#D1D3D4"/>
<path d="M237.253 153.288H183.37C183.27 153.302 183.169 153.294 183.072 153.265C182.976 153.236 182.887 153.186 182.811 153.12C182.735 153.054 182.674 152.972 182.633 152.88C182.591 152.788 182.569 152.689 182.569 152.588C182.569 152.487 182.591 152.388 182.633 152.296C182.674 152.204 182.735 152.123 182.811 152.056C182.887 151.99 182.976 151.941 183.072 151.912C183.169 151.883 183.27 151.875 183.37 151.888L237.266 151.951C237.435 151.974 237.591 152.057 237.703 152.186C237.816 152.315 237.878 152.48 237.878 152.651C237.878 152.822 237.816 152.988 237.703 153.116C237.591 153.245 237.435 153.329 237.266 153.351L237.253 153.288Z" fill="#D1D3D4"/>
<path d="M235.436 162.903L181.541 162.839C181.355 162.836 181.179 162.76 181.049 162.628C180.919 162.496 180.847 162.318 180.847 162.133C180.847 161.949 180.92 161.772 181.05 161.642C181.18 161.512 181.357 161.439 181.541 161.439L235.436 161.502C235.536 161.489 235.638 161.497 235.734 161.526C235.831 161.555 235.92 161.604 235.995 161.671C236.071 161.737 236.132 161.819 236.174 161.91C236.215 162.002 236.237 162.102 236.237 162.202C236.237 162.303 236.215 162.403 236.174 162.494C236.132 162.586 236.071 162.668 235.995 162.734C235.92 162.8 235.831 162.85 235.734 162.879C235.638 162.908 235.536 162.916 235.436 162.903Z" fill="#D1D3D4"/>
<path d="M233.619 172.516L179.724 172.441C179.632 172.442 179.541 172.425 179.456 172.391C179.371 172.357 179.294 172.306 179.229 172.241C179.165 172.176 179.113 172.099 179.079 172.014C179.045 171.929 179.028 171.838 179.03 171.747C179.028 171.654 179.045 171.563 179.079 171.477C179.113 171.392 179.164 171.314 179.229 171.248C179.293 171.182 179.37 171.13 179.455 171.094C179.54 171.059 179.632 171.04 179.724 171.04L233.619 171.103C233.807 171.103 233.986 171.178 234.119 171.31C234.251 171.443 234.326 171.622 234.326 171.81C234.323 171.996 234.247 172.174 234.115 172.306C233.984 172.438 233.806 172.513 233.619 172.516Z" fill="#D1D3D4"/>
<path d="M231.752 182.177H193.753C193.583 182.154 193.428 182.07 193.315 181.942C193.203 181.813 193.141 181.648 193.141 181.477C193.141 181.306 193.203 181.14 193.315 181.011C193.428 180.883 193.583 180.799 193.753 180.776H231.752C231.922 180.799 232.077 180.883 232.19 181.011C232.303 181.14 232.365 181.306 232.365 181.477C232.365 181.648 232.303 181.813 232.19 181.942C232.077 182.07 231.922 182.154 231.752 182.177Z" fill="#D1D3D4"/>
<path d="M286.291 233.808L168.584 233.669C168.48 233.671 168.378 233.649 168.284 233.605C168.19 233.562 168.107 233.497 168.042 233.417C167.975 233.337 167.926 233.244 167.9 233.144C167.874 233.043 167.87 232.938 167.89 232.836L173.239 204.614C173.271 204.456 173.356 204.313 173.48 204.21C173.604 204.107 173.759 204.049 173.921 204.046L291.628 204.185C291.732 204.184 291.834 204.206 291.928 204.25C292.022 204.294 292.105 204.358 292.171 204.438C292.235 204.518 292.282 204.611 292.308 204.711C292.335 204.812 292.339 204.916 292.322 205.018L286.973 233.24C286.941 233.398 286.856 233.541 286.732 233.644C286.608 233.747 286.453 233.805 286.291 233.808ZM169.429 232.268L285.711 232.407L290.758 205.586L174.463 205.447L169.429 232.268Z" fill="#D1D3D4"/>
<path d="M289.521 214.412L172.192 214.274C172.092 214.287 171.991 214.279 171.894 214.25C171.798 214.221 171.709 214.172 171.633 214.105C171.557 214.039 171.496 213.957 171.455 213.866C171.413 213.774 171.392 213.674 171.392 213.573C171.392 213.473 171.413 213.373 171.455 213.281C171.496 213.19 171.557 213.108 171.633 213.042C171.709 212.975 171.798 212.926 171.894 212.897C171.991 212.868 172.092 212.86 172.192 212.873L289.521 213.012C289.691 213.035 289.846 213.118 289.959 213.247C290.071 213.376 290.134 213.541 290.134 213.712C290.134 213.883 290.071 214.049 289.959 214.177C289.846 214.306 289.691 214.39 289.521 214.412Z" fill="#D1D3D4"/>
<path d="M254.461 233.764H254.335C254.154 233.728 253.994 233.621 253.89 233.468C253.786 233.314 253.747 233.126 253.78 232.944L259.117 204.722C259.153 204.541 259.26 204.381 259.413 204.277C259.567 204.173 259.755 204.134 259.937 204.167C260.119 204.204 260.28 204.31 260.386 204.463C260.492 204.616 260.534 204.804 260.505 204.987L255.155 233.209C255.121 233.367 255.033 233.508 254.907 233.609C254.78 233.71 254.623 233.765 254.461 233.764Z" fill="#D1D3D4"/>
<path d="M195.078 233.689H194.951C194.768 233.655 194.605 233.55 194.498 233.396C194.392 233.243 194.351 233.053 194.384 232.869L199.733 204.634C199.739 204.534 199.766 204.436 199.813 204.346C199.859 204.257 199.925 204.179 200.004 204.117C200.084 204.055 200.175 204.011 200.273 203.988C200.371 203.964 200.473 203.962 200.572 203.981C200.671 204 200.765 204.04 200.847 204.098C200.929 204.156 200.998 204.231 201.048 204.318C201.099 204.405 201.131 204.502 201.141 204.602C201.151 204.702 201.14 204.804 201.108 204.899L195.771 233.121C195.739 233.282 195.653 233.426 195.526 233.529C195.4 233.633 195.241 233.689 195.078 233.689Z" fill="#D1D3D4"/>
<path d="M96.1052 191.426L227.072 191.577C228.937 191.487 230.718 190.776 232.133 189.558C233.548 188.34 234.515 186.684 234.881 184.853L259.243 56.2451C259.949 52.4603 257.59 49.4956 253.982 49.4956L123.015 49.3442C121.157 49.4318 119.381 50.1352 117.967 51.3436C116.553 52.552 115.582 54.1965 115.206 56.0181L90.8443 184.701C90.1378 188.398 92.497 191.426 96.1052 191.426Z" fill="white"/>
<path d="M2492.26 247.582L2623.23 247.733C2625.09 247.643 2626.87 246.932 2628.29 245.714C2629.7 244.496 2630.67 242.84 2631.04 241.009L2655.4 112.401C2656.11 108.617 2653.75 105.652 2650.14 105.652L2519.17 105.5C2517.31 105.588 2515.54 106.291 2514.12 107.5C2512.71 108.708 2511.74 110.353 2511.36 112.174L2487 240.858C2486.29 244.554 2488.65 247.582 2492.26 247.582Z" fill="white"/>
<path d="M227.072 192.272L96.1051 192.121C95.2239 192.141 94.3493 191.963 93.5458 191.601C92.7422 191.239 92.0303 190.7 91.4624 190.026C90.8612 189.278 90.4358 188.405 90.2174 187.47C89.999 186.535 89.9934 185.564 90.2008 184.627L114.588 56.0189C114.987 54.0464 116.032 52.2632 117.558 50.9517C119.085 49.6401 121.005 48.8749 123.015 48.7773L253.982 48.9287C254.859 48.9012 255.732 49.0705 256.535 49.4241C257.338 49.7778 258.052 50.3069 258.625 50.9725C259.225 51.7211 259.65 52.5947 259.868 53.5292C260.087 54.4636 260.093 55.4351 259.886 56.3722L235.537 184.967C235.149 186.957 234.104 188.76 232.568 190.084C231.033 191.409 229.098 192.179 227.072 192.272ZM96.1051 190.859L227.072 191.01C228.807 190.921 230.461 190.25 231.767 189.105C233.074 187.961 233.958 186.41 234.276 184.702L258.625 56.1199C258.798 55.3679 258.8 54.5867 258.632 53.8335C258.464 53.0804 258.13 52.3744 257.653 51.7673C257.204 51.2356 256.641 50.8119 256.005 50.528C255.369 50.244 254.678 50.1071 253.982 50.1273L123.015 49.9759C121.296 50.0681 119.657 50.7292 118.355 51.8552C117.053 52.9812 116.163 54.5081 115.824 56.1956L91.4624 184.803C91.2921 185.555 91.2909 186.336 91.4589 187.089C91.6269 187.841 91.9599 188.547 92.4338 189.156C92.8837 189.687 93.4472 190.11 94.0827 190.394C94.7182 190.678 95.4093 190.815 96.1051 190.796V190.859Z" fill="#231F20"/>
<path d="M256.984 68.9472L112.947 68.7832C112.78 68.7832 112.619 68.7167 112.501 68.5985C112.383 68.4802 112.316 68.3197 112.316 68.1524C112.316 67.9851 112.383 67.8247 112.501 67.7064C112.619 67.5881 112.78 67.5216 112.947 67.5216L256.984 67.6856C257.151 67.6888 257.309 67.7563 257.427 67.8739C257.544 67.9915 257.612 68.1501 257.615 68.3164C257.615 68.4837 257.549 68.6442 257.43 68.7625C257.312 68.8808 257.152 68.9472 256.984 68.9472Z" fill="#231F20"/>
<path d="M247.297 60.8387C248.679 59.3735 248.82 57.2635 247.614 56.126C246.407 54.9885 244.309 55.2542 242.928 56.7195C241.546 58.1847 241.405 60.2946 242.612 61.4321C243.818 62.5696 245.916 62.304 247.297 60.8387Z" fill="#D1D3D4"/>
<path d="M235.827 60.8169C237.208 59.3517 237.35 57.2418 236.143 56.1042C234.936 54.9667 232.838 55.2324 231.457 56.6977C230.076 58.1629 229.934 60.2728 231.141 61.4103C232.348 62.5478 234.445 62.2822 235.827 60.8169Z" fill="#D1D3D4"/>
<path d="M224.355 60.8038C225.736 59.3386 225.878 57.2286 224.671 56.0911C223.465 54.9536 221.367 55.2193 219.985 56.6846C218.604 58.1498 218.463 60.2597 219.669 61.3972C220.876 62.5347 222.974 62.269 224.355 60.8038Z" fill="#D1D3D4"/>
<path d="M188.846 78.8798L176.469 144.193L228.965 144.243L241.328 78.9428L188.846 78.8798Z" fill="white"/>
<path d="M188.858 78.8815H241.341L228.977 144.195L176.482 144.132L188.858 78.8815ZM188.858 77.6199C188.561 77.6145 188.272 77.714 188.041 77.9009C187.81 78.0879 187.653 78.3502 187.596 78.6418L175.233 143.955C175.199 144.136 175.205 144.323 175.25 144.502C175.296 144.68 175.381 144.847 175.498 144.989C175.616 145.132 175.765 145.246 175.933 145.325C176.101 145.403 176.284 145.444 176.469 145.444L228.964 145.507C229.261 145.512 229.551 145.413 229.781 145.226C230.012 145.039 230.17 144.776 230.226 144.485L242.59 79.1717C242.624 78.9903 242.618 78.8037 242.572 78.625C242.526 78.4463 242.442 78.2797 242.325 78.1372C242.207 77.9924 242.059 77.8755 241.891 77.7948C241.723 77.7141 241.54 77.6716 241.353 77.6704H188.871L188.858 77.6199Z" fill="#231F20"/>
<path d="M180.027 125.321L176.457 144.182L228.952 144.245L232.535 125.41C228.207 120.212 220.941 118.433 214.544 117.84C212.021 117.613 209.75 117.55 207.77 117.55H207.303C205.448 117.55 203.303 117.55 200.995 117.815H200.881C194.258 118.383 186.335 120.161 180.027 125.321Z" fill="#D23228"/>
<path d="M200.036 121.419L201.65 139.081L202.117 144.228H203.303L205.725 139.068L213.989 121.507C209.4 120.459 204.637 120.429 200.036 121.419Z" fill="#231F20"/>
<path d="M214.014 121.448C213.154 122.654 212.018 123.637 210.702 124.315C209.385 124.993 207.926 125.346 206.445 125.346C205.101 125.441 203.761 125.113 202.612 124.41C201.464 123.707 200.563 122.663 200.036 121.423L203.631 106.057H216.247L214.014 121.448Z" fill="white"/>
<path d="M206.521 126C205.043 126.081 203.576 125.705 202.319 124.924C201.062 124.144 200.076 122.995 199.493 121.635C199.462 121.519 199.462 121.397 199.493 121.282L203.076 105.903C203.113 105.759 203.196 105.632 203.312 105.541C203.428 105.449 203.572 105.399 203.72 105.398H216.336C216.428 105.397 216.52 105.416 216.605 105.453C216.69 105.49 216.766 105.544 216.828 105.613C216.889 105.686 216.933 105.77 216.96 105.862C216.986 105.953 216.992 106.049 216.979 106.143L214.746 121.509C214.736 121.599 214.706 121.685 214.658 121.761C213.745 123.073 212.528 124.143 211.111 124.881C209.693 125.62 208.118 126.003 206.521 126ZM200.78 121.37C201.291 122.436 202.11 123.323 203.132 123.917C204.153 124.511 205.329 124.784 206.508 124.701C207.857 124.702 209.188 124.387 210.394 123.78C211.599 123.173 212.645 122.291 213.447 121.206L215.554 106.723H204.199L200.78 121.37Z" fill="#231F20"/>
<path d="M214.999 112.653C213.766 114.097 212.245 115.266 210.532 116.085C207.77 117.511 203.72 119.113 200.78 118.608L202.509 112.098C204.729 112.371 206.975 112.371 209.195 112.098C211.239 112.014 213.258 111.628 215.188 110.95C215.528 110.9 215.516 112.023 214.999 112.653Z" fill="#231F20"/>
<path d="M201.916 119.34C201.493 119.344 201.072 119.314 200.654 119.252C200.48 119.219 200.326 119.12 200.225 118.974C200.179 118.899 200.149 118.816 200.136 118.729C200.123 118.643 200.128 118.554 200.149 118.469L201.878 111.972C201.917 111.821 202.011 111.689 202.141 111.603C202.272 111.516 202.43 111.482 202.584 111.505C204.755 111.77 206.949 111.77 209.119 111.505C211.101 111.421 213.06 111.051 214.935 110.408C215.088 110.341 215.258 110.329 215.419 110.372C215.58 110.416 215.72 110.513 215.818 110.647C215.977 111.034 216.029 111.456 215.966 111.87C215.904 112.283 215.731 112.671 215.465 112.994C214.183 114.515 212.592 115.745 210.797 116.602C208.066 118.138 205.038 119.071 201.916 119.34ZM201.575 118.078C204.615 118.217 208.602 116.375 210.255 115.555C211.887 114.778 213.335 113.664 214.506 112.287C214.594 112.18 214.658 112.055 214.696 111.922C212.916 112.436 211.083 112.745 209.233 112.843C207.136 113.066 205.022 113.066 202.925 112.843L201.575 118.078Z" fill="#231F20"/>
<path d="M220.461 144.886H220.272C220.189 144.862 220.112 144.821 220.045 144.766C219.978 144.711 219.923 144.644 219.883 144.567C219.843 144.491 219.819 144.407 219.812 144.321C219.805 144.235 219.816 144.148 219.843 144.066L224.889 128.22C224.915 128.139 224.957 128.063 225.012 127.998C225.067 127.932 225.135 127.879 225.212 127.84C225.288 127.801 225.371 127.778 225.457 127.772C225.542 127.766 225.628 127.777 225.709 127.804C225.791 127.828 225.866 127.869 225.931 127.923C225.997 127.977 226.05 128.044 226.089 128.119C226.128 128.195 226.151 128.277 226.157 128.362C226.164 128.446 226.153 128.531 226.126 128.611L221.079 144.457C221.034 144.585 220.95 144.694 220.839 144.771C220.728 144.848 220.596 144.889 220.461 144.886Z" fill="#231F20"/>
<path d="M184.96 144.851C184.873 144.846 184.789 144.824 184.711 144.787C184.634 144.749 184.564 144.697 184.507 144.632C184.449 144.568 184.405 144.493 184.377 144.411C184.348 144.33 184.336 144.243 184.341 144.157L185.3 128.311C185.279 128.212 185.281 128.11 185.306 128.012C185.331 127.914 185.378 127.823 185.444 127.746C185.51 127.669 185.593 127.609 185.686 127.569C185.779 127.53 185.88 127.512 185.981 127.518C186.082 127.524 186.18 127.554 186.268 127.604C186.356 127.654 186.431 127.724 186.487 127.809C186.543 127.893 186.579 127.989 186.592 128.089C186.605 128.189 186.595 128.291 186.562 128.387L185.603 144.233C185.597 144.399 185.526 144.556 185.406 144.671C185.286 144.787 185.126 144.851 184.96 144.851Z" fill="#231F20"/>
<path d="M202.66 102.523C202.925 98.9145 198.762 98.0187 198.169 102.27C197.906 103.609 198.042 104.995 198.56 106.257C198.665 106.509 198.823 106.736 199.023 106.923C199.222 107.11 199.459 107.253 199.717 107.342C199.975 107.432 200.25 107.465 200.522 107.441C200.794 107.417 201.059 107.336 201.297 107.203" fill="white"/>
<path d="M200.351 108.111C200.116 108.11 199.883 108.076 199.657 108.011C199.297 107.892 198.964 107.703 198.678 107.454C198.392 107.205 198.159 106.901 197.992 106.56C197.406 105.179 197.244 103.655 197.525 102.182C197.878 99.6587 199.392 98.6621 200.755 98.8008C201.59 98.9713 202.323 99.4648 202.796 100.174C203.268 100.883 203.442 101.75 203.278 102.586C203.298 102.685 203.295 102.787 203.269 102.885C203.243 102.982 203.195 103.073 203.128 103.149C203.061 103.225 202.978 103.284 202.885 103.322C202.791 103.361 202.69 103.377 202.589 103.37C202.488 103.363 202.391 103.333 202.303 103.282C202.216 103.231 202.142 103.16 202.087 103.076C202.031 102.991 201.996 102.895 201.984 102.795C201.972 102.694 201.983 102.593 202.016 102.497C202.13 101.046 201.398 100.201 200.629 100.113C199.859 100.025 199.077 100.592 198.825 102.384C198.581 103.589 198.695 104.838 199.153 105.979C199.328 106.355 199.646 106.645 200.036 106.787C200.196 106.83 200.363 106.841 200.528 106.819C200.692 106.798 200.851 106.744 200.995 106.661C201.069 106.618 201.151 106.591 201.236 106.581C201.321 106.571 201.407 106.578 201.489 106.601C201.571 106.625 201.648 106.665 201.715 106.718C201.782 106.772 201.837 106.838 201.878 106.913C201.96 107.064 201.979 107.241 201.932 107.406C201.885 107.571 201.775 107.711 201.625 107.796C201.234 108.007 200.796 108.115 200.351 108.111Z" fill="#231F20"/>
<path d="M218.972 102.533C220.082 98.9249 224.574 98.0418 223.552 102.293C223.26 103.758 222.61 105.128 221.659 106.28C220.726 107.327 219.426 107.819 218.581 107.226" fill="white"/>
<path d="M219.414 108.138C218.964 108.149 218.522 108.016 218.152 107.759C218.072 107.715 218.002 107.654 217.947 107.581C217.891 107.508 217.852 107.424 217.831 107.334C217.81 107.245 217.808 107.152 217.826 107.062C217.843 106.972 217.879 106.886 217.931 106.811C217.983 106.736 218.051 106.672 218.129 106.625C218.208 106.577 218.295 106.546 218.386 106.535C218.477 106.523 218.57 106.53 218.657 106.557C218.745 106.583 218.827 106.627 218.897 106.687C219.414 107.053 220.373 106.687 221.13 105.854C222.011 104.776 222.625 103.505 222.921 102.145C223.022 101.729 223.3 100.341 222.53 100.139C221.76 99.9372 220.171 100.846 219.603 102.662C219.578 102.745 219.537 102.822 219.483 102.889C219.428 102.956 219.361 103.012 219.284 103.053C219.208 103.094 219.125 103.119 219.039 103.128C218.953 103.137 218.866 103.129 218.783 103.104C218.7 103.079 218.623 103.038 218.556 102.983C218.489 102.929 218.433 102.861 218.392 102.785C218.351 102.709 218.326 102.626 218.317 102.54C218.308 102.453 218.317 102.367 218.341 102.284C219.124 99.7605 221.344 98.398 222.871 98.8143C223.337 98.9531 224.851 99.5965 224.132 102.385C223.793 103.955 223.083 105.421 222.063 106.662C221.747 107.077 221.349 107.422 220.893 107.677C220.437 107.931 219.933 108.088 219.414 108.138Z" fill="#231F20"/>
<path d="M202.735 94.5984L200.755 105.044C199.796 110.091 202.975 114.141 207.845 114.141H208.879C211.392 114.01 213.79 113.048 215.697 111.406C217.604 109.764 218.911 107.535 219.414 105.07L221.344 95.1282C221.344 95.1282 222.997 85.0354 213.598 86.0447C204.199 87.054 202.735 94.5984 202.735 94.5984Z" fill="white"/>
<path d="M208.93 114.798H207.896C206.737 114.825 205.587 114.591 204.531 114.115C203.474 113.64 202.537 112.933 201.79 112.048C201.012 111.055 200.467 109.9 200.194 108.669C199.921 107.437 199.927 106.161 200.213 104.932L202.193 94.4735C202.193 94.3978 203.808 86.5001 213.548 85.4152C214.708 85.1879 215.906 85.2398 217.042 85.5667C218.178 85.8935 219.221 86.4859 220.083 87.2949C222.795 90.1588 222.026 95.0538 222.001 95.2683L220.108 105.222C219.569 107.831 218.179 110.187 216.156 111.92C214.133 113.654 211.591 114.666 208.93 114.798ZM203.417 94.7258L201.474 105.222C201.239 106.267 201.237 107.35 201.471 108.395C201.704 109.44 202.166 110.421 202.824 111.265C203.453 112.001 204.238 112.588 205.122 112.983C206.005 113.378 206.966 113.571 207.934 113.549H208.968C211.332 113.427 213.587 112.521 215.378 110.974C217.169 109.428 218.394 107.328 218.859 105.008L220.739 95.0916C220.739 95.0916 221.433 90.6382 219.149 88.2412C218.424 87.5836 217.553 87.1071 216.608 86.8506C215.663 86.5941 214.67 86.5648 213.712 86.7651C204.893 87.686 203.48 94.423 203.417 94.7258Z" fill="#231F20"/>
<path d="M209.839 106.557C209.371 106.555 208.903 106.53 208.438 106.481C208.268 106.456 208.115 106.365 208.011 106.229C207.908 106.092 207.862 105.92 207.883 105.749C207.905 105.58 207.993 105.425 208.127 105.319C208.262 105.213 208.432 105.164 208.602 105.182C209.361 105.27 210.127 105.27 210.886 105.182C211.189 104.576 211.025 101.838 210.533 99.1386C210.503 98.968 210.542 98.7928 210.642 98.651C210.741 98.5092 210.892 98.4124 211.062 98.3816C211.146 98.3652 211.232 98.3656 211.316 98.3828C211.399 98.4 211.479 98.4337 211.549 98.4818C211.62 98.5299 211.68 98.5916 211.726 98.6632C211.773 98.7348 211.804 98.8149 211.819 98.8989C212.059 100.261 212.803 104.778 211.895 106.002C211.812 106.124 211.703 106.228 211.577 106.306C211.451 106.385 211.31 106.436 211.163 106.456C210.725 106.519 210.282 106.553 209.839 106.557Z" fill="#231F20"/>
<path d="M175.068 92.4537H121.173C121.003 92.4309 120.848 92.3473 120.735 92.2185C120.623 92.0898 120.561 91.9245 120.561 91.7535C120.561 91.5824 120.623 91.4172 120.735 91.2884C120.848 91.1596 121.003 91.0761 121.173 91.0533H175.068C175.238 91.0761 175.393 91.1596 175.506 91.2884C175.619 91.4172 175.681 91.5824 175.681 91.7535C175.681 91.9245 175.619 92.0898 175.506 92.2185C175.393 92.3473 175.238 92.4309 175.068 92.4537Z" fill="#D1D3D4"/>
<path d="M173.252 102.067H119.356C119.169 102.067 118.989 101.993 118.857 101.86C118.724 101.728 118.65 101.548 118.65 101.361C118.65 101.173 118.724 100.994 118.857 100.861C118.989 100.729 119.169 100.654 119.356 100.654L173.252 100.73C173.436 100.73 173.613 100.803 173.743 100.933C173.873 101.063 173.946 101.24 173.946 101.424C173.93 101.598 173.851 101.76 173.723 101.879C173.594 101.998 173.427 102.065 173.252 102.067Z" fill="#D1D3D4"/>
<path d="M171.423 111.666H117.54C117.44 111.68 117.339 111.672 117.242 111.643C117.146 111.614 117.057 111.564 116.981 111.498C116.905 111.432 116.844 111.35 116.802 111.258C116.761 111.167 116.739 111.067 116.739 110.966C116.739 110.866 116.761 110.766 116.802 110.674C116.844 110.583 116.905 110.501 116.981 110.434C117.057 110.368 117.146 110.319 117.242 110.29C117.339 110.261 117.44 110.253 117.54 110.266H171.423C171.61 110.266 171.79 110.341 171.923 110.473C172.055 110.605 172.129 110.785 172.129 110.973C172.129 111.065 172.111 111.156 172.075 111.241C172.04 111.326 171.988 111.403 171.922 111.468C171.856 111.532 171.778 111.583 171.692 111.617C171.607 111.651 171.515 111.668 171.423 111.666Z" fill="#D1D3D4"/>
<path d="M169.606 121.283H115.71C115.541 121.26 115.385 121.177 115.273 121.048C115.16 120.919 115.098 120.754 115.098 120.583C115.098 120.412 115.16 120.246 115.273 120.118C115.385 119.989 115.541 119.905 115.71 119.883L169.606 119.946C169.706 119.932 169.807 119.94 169.904 119.969C170 119.998 170.09 120.048 170.165 120.114C170.241 120.18 170.302 120.262 170.344 120.354C170.385 120.446 170.407 120.545 170.407 120.646C170.407 120.747 170.385 120.846 170.344 120.938C170.302 121.03 170.241 121.111 170.165 121.178C170.09 121.244 170 121.293 169.904 121.322C169.807 121.351 169.706 121.359 169.606 121.346V121.283Z" fill="#D1D3D4"/>
<path d="M167.789 130.897H129.79C129.62 130.874 129.465 130.791 129.352 130.662C129.24 130.533 129.178 130.368 129.178 130.197C129.178 130.026 129.24 129.861 129.352 129.732C129.465 129.603 129.62 129.52 129.79 129.497H167.789C167.973 129.497 168.15 129.57 168.28 129.7C168.41 129.83 168.483 130.007 168.483 130.191C168.483 130.376 168.411 130.554 168.281 130.686C168.151 130.818 167.975 130.894 167.789 130.897Z" fill="#D1D3D4"/>
<path d="M222.278 182.58L104.571 182.441C104.468 182.439 104.367 182.414 104.274 182.37C104.182 182.325 104.1 182.261 104.034 182.182C103.969 182.104 103.921 182.012 103.893 181.913C103.866 181.813 103.86 181.71 103.877 181.608L109.226 153.386C109.255 153.224 109.341 153.077 109.468 152.971C109.595 152.865 109.755 152.806 109.92 152.806L227.627 152.945C227.732 152.946 227.834 152.971 227.928 153.016C228.022 153.062 228.104 153.128 228.17 153.21C228.237 153.286 228.286 153.377 228.313 153.476C228.339 153.574 228.342 153.678 228.321 153.777L222.921 181.999C222.894 182.154 222.816 182.295 222.699 182.4C222.583 182.505 222.434 182.568 222.278 182.58ZM105.416 181.041L221.698 181.179L226.744 154.345L110.462 154.206L105.416 181.041Z" fill="#D1D3D4"/>
<path d="M225.508 163.257L108.179 163.119C107.991 163.119 107.812 163.044 107.679 162.912C107.547 162.779 107.472 162.6 107.472 162.412C107.472 162.32 107.491 162.229 107.526 162.144C107.562 162.059 107.614 161.982 107.68 161.917C107.746 161.852 107.824 161.802 107.909 161.767C107.995 161.733 108.086 161.717 108.179 161.718L225.508 161.857C225.677 161.88 225.833 161.963 225.945 162.092C226.058 162.221 226.12 162.386 226.12 162.557C226.12 162.728 226.058 162.894 225.945 163.022C225.833 163.151 225.677 163.235 225.508 163.257Z" fill="#D1D3D4"/>
<path d="M190.41 182.545H190.284C190.102 182.508 189.942 182.401 189.838 182.248C189.734 182.095 189.695 181.907 189.728 181.725L195.078 153.503C195.093 153.412 195.126 153.325 195.175 153.248C195.224 153.17 195.288 153.103 195.363 153.05C195.438 152.997 195.523 152.96 195.612 152.94C195.702 152.92 195.795 152.918 195.885 152.935C196.069 152.969 196.232 153.074 196.338 153.228C196.445 153.381 196.486 153.571 196.453 153.755L191.104 181.977C191.071 182.137 190.985 182.281 190.858 182.385C190.732 182.488 190.573 182.545 190.41 182.545Z" fill="#D1D3D4"/>
<path d="M131.064 182.474H130.938C130.756 182.437 130.596 182.331 130.493 182.177C130.389 182.024 130.349 181.836 130.383 181.654L135.719 153.432C135.774 153.269 135.885 153.132 136.033 153.046C136.181 152.96 136.355 152.93 136.523 152.963C136.691 152.995 136.841 153.087 136.946 153.222C137.052 153.357 137.104 153.526 137.094 153.697L131.758 181.919C131.723 182.077 131.635 182.218 131.509 182.319C131.383 182.42 131.226 182.475 131.064 182.474Z" fill="#D1D3D4"/>
</g>
<defs>
<clipPath id="clip0_4274_29461">
<rect width="406" height="306" fill="white" transform="translate(0 -4)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 41 KiB

View File

@@ -1,44 +0,0 @@
<svg width="246" height="204" viewBox="0 0 246 204" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15.9664 80.7056C1.9299 101.534 -5.31519 125.861 4.55784 148.18C22.5467 188.845 120.573 177.416 163.26 161.447C283.487 116.471 225.675 -1.54332 95.6636 27.6382C59.2626 35.8086 30.003 59.877 15.9664 80.7056Z" fill="#E1DDDB" fill-opacity="0.3"/>
<path d="M95.6307 23.4976C101.102 13.3476 109.932 6.31365 123.036 10.536C124.191 11.0005 125.403 11.311 126.639 11.4597C127.895 11.386 129.146 11.2369 130.384 11.0131C136.292 10.5259 140.656 16.2505 143.529 21.427C148.867 31.0796 158.622 44.1833 156.724 55.7847C155.252 64.7776 146.797 71.0097 138.261 74.1562C128.636 77.7063 118.244 78.655 108.136 76.9069C103.061 76.034 97.813 74.3288 94.159 70.6443C89.084 65.5084 88.069 57.6016 88.4547 50.3443C88.9926 42.2345 91.1749 31.8104 95.6307 23.4976Z" fill="white"/>
<path d="M117.991 78.2569C114.66 78.2567 111.336 77.9715 108.054 77.4043C101.649 76.3081 97.0006 74.2172 93.8439 71.0098C89.4084 66.554 87.4393 59.652 87.9772 50.3952C88.5558 40.7933 91.2557 30.6534 95.2142 23.2845C99.8528 14.657 108.46 5.35964 123.137 10.0895L123.837 10.3331C124.73 10.6795 125.665 10.9015 126.618 10.9929C127.348 10.9968 128.077 10.9219 128.79 10.7696C129.278 10.6884 129.805 10.597 130.303 10.5564C135.165 10.1403 139.752 13.7334 143.894 21.2038C144.848 22.9293 145.924 24.7563 147.091 26.6949C152.278 35.3732 158.733 46.1728 157.139 55.866C155.871 63.712 148.877 70.7155 138.392 74.5928C131.864 77.0187 124.955 78.2596 117.991 78.2569ZM96.0972 23.721C92.1996 30.9782 89.5302 40.976 88.9922 50.4459C88.4543 59.3982 90.332 66.0972 94.5544 70.3602C97.5994 73.4053 102.076 75.4352 108.277 76.4502C118.3 78.1754 128.602 77.223 138.139 73.6894C148.289 69.9238 155.079 63.1842 156.307 55.6732C157.84 46.325 151.486 35.6777 146.38 27.1212C145.223 25.1826 144.127 23.3556 143.163 21.6199C139.214 14.5149 134.941 11.0741 130.465 11.4699C129.988 11.4699 129.521 11.5917 129.034 11.6729C128.218 11.8545 127.384 11.9464 126.547 11.947C125.503 11.8549 124.477 11.6122 123.502 11.2263L122.812 10.9929C108.744 6.45585 100.482 15.4284 95.9957 23.7413L96.0972 23.721Z" fill="#454545"/>
<path d="M182.845 167.75C182.845 167.75 178.596 94.152 167.857 84.3978C152.409 70.4314 97.5788 67.5184 86.4443 70.3909C66.7533 75.4659 53.0508 106.22 53.0508 106.22L85.7135 115.985C85.7845 141.248 88.3119 167.75 88.3119 167.75H182.845Z" fill="#03C7E8"/>
<path d="M84.2307 146.058C84.1627 146.053 84.0963 146.035 84.0354 146.004C83.9746 145.973 83.9204 145.93 83.8761 145.878C83.8317 145.827 83.7981 145.766 83.7771 145.702C83.7561 145.637 83.7481 145.568 83.7537 145.5L88.1689 87.6451C88.1741 87.5757 88.1928 87.5081 88.2242 87.446C88.2556 87.3839 88.299 87.3286 88.3518 87.2833C88.4046 87.238 88.4659 87.2037 88.532 87.1821C88.5982 87.1606 88.668 87.1524 88.7373 87.1579C88.8063 87.163 88.8736 87.1819 88.9352 87.2134C88.9967 87.2449 89.0514 87.2884 89.0959 87.3414C89.1403 87.3944 89.1737 87.4557 89.1941 87.5219C89.2144 87.588 89.2213 87.6575 89.2144 87.7263L84.7991 145.581C84.794 145.65 84.7752 145.718 84.7437 145.779C84.7122 145.841 84.6686 145.895 84.6156 145.94C84.5627 145.984 84.5013 146.018 84.4352 146.038C84.3691 146.058 84.2995 146.065 84.2307 146.058Z" fill="#454545"/>
<path d="M114.163 90.7217C117.828 95.279 123.298 98.527 129.196 98.6184C129.959 98.6607 130.724 98.5574 131.449 98.3139C132.442 97.8894 133.288 97.1838 133.885 96.2839C136.544 92.6299 136.656 87.7071 135.986 83.2411C135.509 80.1048 134.595 76.8162 132.22 74.7151C129.429 72.2487 125.379 72.0152 121.654 71.9137C117.929 71.8122 109.291 70.7262 107.911 75.314C106.622 79.5059 111.707 87.6665 114.163 90.7217Z" fill="#454545"/>
<path d="M129.42 99.0646H129.187C123.553 98.9732 117.809 95.9587 113.82 91.0055C111.17 87.7169 106.146 79.536 107.476 75.1816C108.805 70.8273 115.9 71.1216 120.123 71.3855L121.676 71.4566C125.786 71.5784 129.704 71.8829 132.516 74.3696C135.327 76.8564 136.028 80.5307 136.424 83.1697C137.286 88.874 136.556 93.3197 134.252 96.5372C133.611 97.5159 132.692 98.2797 131.613 98.7296C130.908 98.9745 130.165 99.0881 129.42 99.0646ZM116.662 72.1671C113.089 72.1671 109.181 72.6847 108.339 75.4455C107.11 79.4142 112.064 87.4022 114.51 90.4472C118.336 95.1974 123.827 98.08 129.197 98.1714C129.908 98.2085 130.62 98.1157 131.298 97.8973C132.211 97.5007 132.988 96.8442 133.531 96.0094C135.683 93.0456 136.363 88.7623 135.561 83.3016C135.185 80.7946 134.404 77.2116 131.947 75.0395C129.491 72.8674 125.604 72.4614 121.666 72.3498C121.209 72.3498 120.65 72.3498 120.102 72.2787C119.067 72.228 117.88 72.1671 116.662 72.1671Z" fill="#454545"/>
<path d="M137.874 43.808C138.249 42.5429 138.885 41.3706 139.742 40.3671C140.181 39.8733 140.761 39.5266 141.404 39.374C142.047 39.2215 142.721 39.2706 143.335 39.5145C143.762 39.7688 144.12 40.1237 144.379 40.5484C144.637 40.973 144.787 41.4544 144.817 41.9505C144.867 42.9487 144.701 43.9458 144.33 44.8737C143.765 46.6382 142.787 48.2423 141.477 49.5529C140.827 50.204 140.05 50.7154 139.195 51.0558C138.34 51.3961 137.424 51.5582 136.504 51.5321" fill="white"/>
<path d="M136.89 51.9282H136.474C136.367 51.9179 136.268 51.8667 136.198 51.7854C136.128 51.7041 136.093 51.5989 136.098 51.4917C136.101 51.4379 136.114 51.3851 136.138 51.3366C136.161 51.2881 136.195 51.245 136.235 51.2098C136.276 51.1747 136.324 51.1482 136.375 51.1321C136.427 51.116 136.481 51.1106 136.535 51.1162C137.403 51.1458 138.268 50.9971 139.076 50.6794C139.884 50.3617 140.619 49.8816 141.234 49.2689C142.496 48.0042 143.436 46.4553 143.975 44.7521C144.329 43.8802 144.488 42.9414 144.442 42.0015C144.417 41.5693 144.287 41.1497 144.062 40.7798C143.837 40.4098 143.525 40.1009 143.152 39.8801C142.615 39.6774 142.028 39.6427 141.47 39.7804C140.911 39.9181 140.408 40.2218 140.026 40.6515C139.218 41.6241 138.614 42.7494 138.25 43.9604C138.232 44.0102 138.204 44.0559 138.168 44.0947C138.132 44.1336 138.089 44.1648 138.041 44.1866C137.992 44.2083 137.94 44.2202 137.887 44.2214C137.834 44.2227 137.782 44.2133 137.732 44.1939C137.632 44.1572 137.55 44.0822 137.504 43.9852C137.458 43.8883 137.453 43.7772 137.489 43.6762C137.886 42.3539 138.557 41.1298 139.458 40.0831C139.958 39.5304 140.615 39.1452 141.342 38.9799C142.069 38.8146 142.828 38.8773 143.518 39.1595C144.012 39.44 144.429 39.8394 144.73 40.3214C145.031 40.8034 145.208 41.353 145.243 41.9203C145.299 42.9602 145.126 43.9997 144.736 44.9653C144.16 46.8097 143.143 48.4856 141.772 49.8474C140.468 51.1359 138.723 51.8799 136.89 51.9282Z" fill="#454545"/>
<path d="M130.902 61.4082L132.475 77.0595C132.475 77.0595 131.714 85.961 121.929 84.1645C119.055 83.6096 116.34 82.4241 113.979 80.6936C111.619 78.963 109.671 76.7304 108.277 74.1566L108.643 46.0715C108.582 44.9246 110.967 40.9965 109.84 31.0698C113.195 30.7268 116.441 29.6886 119.371 28.0214C122.302 26.3542 124.853 24.0946 126.862 21.3867C126.862 21.3867 131.399 26.9895 133.307 28.7252C134.839 30.0073 136.203 31.477 137.367 33.0998C140.412 37.6673 140.28 45.1174 139.549 50.3548C138.839 54.6584 136.342 61.5198 130.902 61.4082Z" fill="white"/>
<path d="M124.375 84.8953C123.527 84.8897 122.681 84.8082 121.847 84.6517C118.893 84.0752 116.102 82.8527 113.676 81.0717C111.249 79.2906 109.246 76.9951 107.81 74.3495C107.795 74.2826 107.795 74.2133 107.81 74.1465C107.81 74.1465 108.175 46.2441 108.175 46.0716C108.218 45.5697 108.323 45.0751 108.49 44.5998C109.058 42.651 110.226 38.591 109.383 31.1206C109.377 31.0608 109.382 31.0004 109.4 30.9428C109.417 30.8852 109.446 30.8318 109.485 30.7857C109.519 30.736 109.565 30.6948 109.617 30.6649C109.67 30.6351 109.729 30.6174 109.789 30.6131C113.072 30.2679 116.248 29.2475 119.118 27.6161C121.988 25.9847 124.49 23.7774 126.466 21.133C126.507 21.0737 126.562 21.0246 126.625 20.9894C126.688 20.9542 126.759 20.9339 126.831 20.93C126.904 20.9272 126.977 20.9414 127.044 20.9713C127.111 21.0012 127.17 21.0461 127.217 21.1026C127.217 21.1026 131.754 26.6952 133.621 28.3801C135.158 29.6918 136.522 31.1924 137.681 32.8461C141.193 38.1647 140.422 46.8836 139.935 50.3853C139.346 54.5773 136.89 61.5503 131.439 61.865L132.952 77.0189C132.709 79.4527 131.521 81.6941 129.643 83.2612C128.121 84.3844 126.265 84.9603 124.375 84.8953ZM108.693 74.045C110.072 76.5357 111.98 78.6941 114.282 80.3687C116.585 82.0433 119.226 83.1935 122.02 83.7382C124.943 84.266 127.308 83.86 129.054 82.5304C130.698 81.131 131.753 79.1626 132.008 77.0189L130.444 61.459C130.439 61.393 130.447 61.3266 130.468 61.2637C130.489 61.2009 130.522 61.143 130.566 61.0936C130.611 61.0479 130.665 61.0118 130.724 60.9874C130.784 60.963 130.847 60.9508 130.911 60.9515H131.033C136.108 60.9515 138.433 54.2423 138.991 50.2534C139.478 46.8531 140.229 38.4185 136.89 33.3638C135.757 31.7708 134.431 30.3252 132.941 29.0602C130.797 26.8597 128.764 24.5527 126.851 22.148C122.766 27.2984 116.828 30.6462 110.307 31.4759C111.068 38.8346 109.901 42.8845 109.292 44.8536C109.159 45.2422 109.067 45.6436 109.018 46.0513C109.018 46.0513 109.058 46.0817 108.693 74.045Z" fill="#454545"/>
<path d="M131.438 66.5538C128.868 67.0035 126.223 66.7221 123.805 65.7418C118.233 63.7828 117.908 59.9766 117.908 59.9766C117.908 59.9766 122.597 62.9201 130.9 61.3773L131.438 66.5538Z" fill="#454545"/>
<path d="M129.176 67.2139C127.292 67.1821 125.427 66.8391 123.655 66.1989C117.879 64.1689 117.483 60.2206 117.463 60.1089C117.455 60.025 117.471 59.9405 117.508 59.8652C117.546 59.7899 117.605 59.7268 117.677 59.6833C117.749 59.6397 117.832 59.6175 117.916 59.6192C118.001 59.6209 118.083 59.6464 118.153 59.6928C118.153 59.6928 122.812 62.5246 130.82 61.0326C130.945 61.0116 131.073 61.0408 131.176 61.1138C131.226 61.15 131.268 61.1971 131.298 61.2516C131.327 61.306 131.345 61.3664 131.348 61.4284L131.876 66.5034C131.898 66.5877 131.895 66.6764 131.868 66.7591C131.841 66.8418 131.791 66.9151 131.724 66.9703C131.68 67.0105 131.629 67.0411 131.573 67.0603C131.517 67.0795 131.458 67.0869 131.399 67.082C130.664 67.1971 129.92 67.2413 129.176 67.2139ZM118.59 60.86C119.036 62.0273 120.315 64.0674 123.959 65.3463C126.186 66.196 128.589 66.4786 130.952 66.1685L130.516 61.9359C124.406 62.9611 120.315 61.6822 118.59 60.86Z" fill="#454545"/>
<path d="M124.061 36.0742C124.061 36.0742 129.846 43.0879 128.78 44.519C127.715 45.9502 124.284 45.4122 124.284 45.4122" fill="white"/>
<path d="M125.714 45.9592C125.211 45.9586 124.71 45.9247 124.212 45.8577C124.106 45.8253 124.017 45.7561 123.958 45.6627C123.9 45.5693 123.878 45.4581 123.895 45.3494C123.912 45.2408 123.968 45.142 124.052 45.0713C124.137 45.0006 124.244 44.9627 124.354 44.9645C125.237 45.1066 127.683 45.2385 128.414 44.2438C128.87 43.6348 126.668 39.9504 123.694 36.3573C123.657 36.312 123.628 36.2598 123.611 36.2036C123.594 36.1475 123.588 36.0885 123.594 36.03C123.6 35.9716 123.617 35.9148 123.644 35.863C123.672 35.8112 123.709 35.7653 123.755 35.728C123.849 35.6544 123.968 35.6197 124.087 35.631C124.206 35.6423 124.316 35.6988 124.394 35.7889C126.201 37.9813 130.281 43.239 129.134 44.7818C128.414 45.7562 126.881 45.9592 125.714 45.9592Z" fill="#454545"/>
<path d="M131.302 37.6836C131.774 37.6204 132.06 36.8441 131.941 35.9495C131.821 35.055 131.341 34.381 130.869 34.4442C130.397 34.5074 130.111 35.2838 130.23 36.1783C130.35 37.0729 130.83 37.7468 131.302 37.6836Z" fill="#454545"/>
<path d="M117.067 37.7043C117.543 37.6987 117.921 36.9626 117.911 36.0602C117.9 35.1577 117.505 34.4306 117.029 34.4362C116.552 34.4418 116.175 35.1779 116.185 36.0803C116.196 36.9828 116.59 37.7099 117.067 37.7043Z" fill="#454545"/>
<path d="M111.798 41.8686C111.449 40.5288 110.853 39.2657 110.042 38.1436C109.575 37.5067 108.964 36.9903 108.258 36.6373C107.551 36.2842 106.771 36.1047 105.982 36.1136C105.494 36.1326 105.016 36.2622 104.586 36.4925C104.155 36.7228 103.782 37.0479 103.495 37.4432C103.153 38.0128 102.938 38.6501 102.866 39.3108C102.348 42.7923 103.515 47.3293 106.794 49.1259C107.347 49.4737 107.988 49.6582 108.641 49.6582C109.295 49.6582 109.935 49.4737 110.488 49.1259" fill="white"/>
<path d="M108.865 50.0604C108.072 50.0422 107.296 49.8333 106.601 49.4514C103.171 47.5635 101.932 42.8844 102.47 39.2202C102.547 38.5008 102.783 37.8074 103.161 37.1902C103.481 36.7428 103.898 36.3741 104.382 36.1121C104.865 35.8501 105.402 35.7016 105.952 35.6779C106.81 35.6717 107.657 35.8702 108.423 36.2567C109.189 36.6433 109.852 37.2069 110.357 37.9007C111.203 39.0603 111.823 40.3687 112.184 41.7577C112.212 41.86 112.2 41.9693 112.148 42.0623C112.097 42.1552 112.012 42.2244 111.91 42.2551C111.86 42.2722 111.807 42.2789 111.754 42.2747C111.702 42.2706 111.651 42.2557 111.604 42.2309C111.557 42.2062 111.516 42.1721 111.483 42.1309C111.45 42.0897 111.426 42.0421 111.413 41.9912C111.077 40.6998 110.506 39.4817 109.728 38.3981C109.306 37.8037 108.747 37.3198 108.099 36.9876C107.45 36.6554 106.731 36.4847 106.003 36.4899C105.578 36.5057 105.162 36.617 104.786 36.8156C104.41 37.0141 104.083 37.2948 103.83 37.6368C103.52 38.1565 103.329 38.7391 103.272 39.342C102.765 42.722 103.881 47.0357 106.987 48.7409C107.466 49.0597 108.025 49.2365 108.599 49.2509C109.174 49.2653 109.741 49.1166 110.235 48.8221C110.321 48.7589 110.428 48.7311 110.534 48.7443C110.639 48.7575 110.736 48.8108 110.804 48.8932C110.869 48.9781 110.899 49.0855 110.885 49.192C110.872 49.2986 110.817 49.3955 110.732 49.4616C110.193 49.8617 109.536 50.0722 108.865 50.0604Z" fill="#454545"/>
<path d="M166.029 161.964C165.906 161.964 165.786 161.92 165.691 161.84C165.597 161.76 165.533 161.65 165.512 161.528C162.03 139.797 155.362 98.3744 155.057 97.1158C155.023 96.9813 155.043 96.8387 155.114 96.719C155.184 96.5994 155.299 96.5122 155.433 96.4764C155.568 96.4454 155.711 96.4683 155.83 96.5402C155.95 96.6121 156.036 96.7275 156.072 96.8621C156.468 98.4759 166.131 158.777 166.547 161.355C166.567 161.492 166.533 161.632 166.451 161.744C166.37 161.856 166.247 161.932 166.11 161.954L166.029 161.964Z" fill="#454545"/>
<path d="M103.112 184.842L102.977 184.655H102.747H55.0401C50.0527 184.655 45.9305 180.138 45.9305 174.453V116.416C45.9305 110.737 50.0532 106.208 55.0401 106.208H181.016C186.003 106.208 190.134 110.738 190.165 116.419V174.453C190.165 180.138 186.037 184.655 181.049 184.655H131.929H131.726L131.592 184.807L115.869 202.609L103.112 184.842Z" fill="white" stroke="#454545" stroke-width="0.9"/>
<path d="M126.486 55.034C118.985 55.034 116.59 45.1377 116.488 44.7114C116.464 44.5944 116.486 44.4726 116.551 44.372C116.615 44.2715 116.717 44.2002 116.833 44.1735C116.89 44.1599 116.95 44.1579 117.008 44.1674C117.066 44.1769 117.122 44.1979 117.172 44.229C117.222 44.2601 117.265 44.3009 117.299 44.3488C117.334 44.3968 117.358 44.451 117.371 44.5084C117.371 44.5998 119.695 54.1408 126.506 54.1408C127.212 54.168 127.916 54.0394 128.567 53.764C129.217 53.4885 129.8 53.0731 130.272 52.5472C132.616 49.8778 131.987 44.7013 131.977 44.6505C131.968 44.5918 131.972 44.532 131.986 44.4745C132.001 44.417 132.027 44.3631 132.063 44.3158C132.099 44.2686 132.144 44.229 132.196 44.1993C132.247 44.1696 132.304 44.1505 132.363 44.143C132.48 44.131 132.598 44.1642 132.692 44.2358C132.787 44.3074 132.85 44.4121 132.87 44.5287C132.87 44.7622 133.57 50.162 130.952 53.1359C130.395 53.7634 129.705 54.2592 128.932 54.5875C128.16 54.9157 127.324 55.0682 126.486 55.034Z" fill="#454545"/>
<path d="M70.1771 103.176C73.7637 101.377 78.12 99.9134 81.6644 101.817C83.3455 102.866 84.7632 104.287 85.8078 105.971C88.0669 109.136 90.1082 112.451 91.9175 115.893C92.3914 116.848 92.7954 118.019 92.0581 118.706C91.16 119.551 89.7377 118.638 88.9482 117.701C87.5236 116.028 86.3296 114.173 85.3981 112.183C87.2805 114.936 88.7735 117.936 89.8348 121.097C90.0177 121.471 90.0817 121.891 90.0181 122.302C89.8722 122.667 89.588 122.96 89.2274 123.116C88.8667 123.273 88.4587 123.281 88.0922 123.139C87.3497 122.858 86.7046 122.367 86.2347 121.728C84.1638 119.239 82.3741 116.53 80.8983 113.649C82.3828 116.657 83.8796 119.745 84.2431 123.078C84.3088 123.353 84.3162 123.639 84.2649 123.917C84.2135 124.196 84.1045 124.46 83.9449 124.694C83.7065 124.904 83.4062 125.031 83.0894 125.055C82.7726 125.079 82.4565 124.999 82.1891 124.828C81.6793 124.464 81.2568 123.992 80.952 123.445L75.6783 115.778C77.0141 118.725 77.9793 121.827 78.5518 125.012C78.6234 125.274 78.623 125.551 78.5505 125.813C78.2358 126.713 76.8342 126.341 76.1662 125.662C75.1107 124.468 74.2905 123.084 73.7489 121.585C72.0643 117.734 70.4974 113.712 68.6538 109.896C67.1293 106.761 66.4761 105.033 70.1771 103.176Z" fill="white"/>
<path d="M77.958 126.765L78.0684 126.749C78.2758 126.704 78.4681 126.606 78.6266 126.465C78.785 126.324 78.9042 126.144 78.9726 125.944C79.0614 125.607 79.0675 125.254 78.9905 124.914C78.6521 123.052 78.1839 121.216 77.5894 119.419L80.5096 123.697C80.8645 124.309 81.3549 124.832 81.9432 125.225C82.3005 125.453 82.7244 125.554 83.1464 125.512C83.5684 125.471 83.964 125.288 84.2695 124.994C84.4767 124.712 84.621 124.389 84.6929 124.047C84.7648 123.705 84.7626 123.352 84.6863 123.01C84.5274 121.793 84.252 120.594 83.864 119.429C84.4984 120.359 85.1577 121.183 85.8586 122.011C86.357 122.728 87.0556 123.282 87.867 123.604C88.1454 123.691 88.4383 123.721 88.7284 123.693C89.0186 123.665 89.3002 123.578 89.5565 123.44C89.7497 123.337 89.9205 123.198 90.0587 123.029C90.197 122.859 90.2999 122.664 90.3614 122.455C90.4574 121.958 90.4043 121.445 90.2087 120.979C89.9753 120.255 89.7117 119.535 89.4297 118.828C89.9032 119.214 90.4782 119.455 91.0854 119.521C91.3167 119.539 91.5492 119.507 91.7672 119.428C91.9853 119.348 92.184 119.224 92.3501 119.062C92.871 118.572 93.2855 117.585 92.2992 115.692C90.4856 112.233 88.4385 108.901 86.1719 105.721C85.0837 103.975 83.6092 102.503 81.8619 101.418C78.8572 99.8217 74.971 100.259 69.9541 102.778C65.9002 104.812 66.657 106.884 68.2137 110.097C69.4397 112.62 70.5521 115.275 71.623 117.791C72.1819 119.102 72.7308 120.415 73.2481 121.722C73.8155 123.272 74.6668 124.702 75.7585 125.94C76.0393 126.234 76.3829 126.461 76.7638 126.604C77.1447 126.747 77.5529 126.802 77.958 126.765ZM75.6082 115.306C75.5536 115.304 75.4993 115.313 75.4476 115.33C75.3469 115.387 75.271 115.48 75.2346 115.59C75.1982 115.7 75.2039 115.819 75.2507 115.925C76.5744 118.833 77.527 121.896 78.0858 125.042C78.1393 125.224 78.1475 125.416 78.1096 125.603C78.0928 125.657 78.061 125.706 78.0176 125.744C77.9743 125.781 77.9213 125.806 77.8647 125.814C77.6113 125.827 77.3581 125.786 77.1214 125.695C76.8847 125.604 76.6697 125.464 76.4903 125.284C75.481 124.129 74.6936 122.798 74.1677 121.357C73.6003 120.057 73.0514 118.744 72.5442 117.435C71.4641 114.858 70.3387 112.186 69.0996 109.643C67.6013 106.545 67.1227 105.222 70.4321 103.558C75.1432 101.188 78.7608 100.74 81.5158 102.179C83.1359 103.197 84.5009 104.573 85.5059 106.202C87.7534 109.348 89.7816 112.644 91.5763 116.068C91.8963 116.687 92.3545 117.808 91.808 118.333C91.7304 118.406 91.6381 118.462 91.5372 118.497C91.4363 118.532 91.3291 118.545 91.2228 118.535C90.4804 118.382 89.8188 117.964 89.3603 117.361C88.9613 116.898 88.5854 116.452 88.2259 115.911C87.5179 114.519 86.7231 113.172 85.8463 111.879C85.7786 111.784 85.6785 111.717 85.5648 111.691C85.451 111.665 85.3317 111.681 85.2292 111.737C85.1284 111.796 85.0535 111.891 85.0192 112.002C84.9848 112.114 84.9936 112.234 85.0438 112.34C85.7112 113.761 86.5088 115.118 87.4264 116.392C88.209 117.951 88.8768 119.565 89.4243 121.221C89.5572 121.517 89.6069 121.843 89.5678 122.165C89.5075 122.344 89.3789 122.493 89.2099 122.579C89.0614 122.67 88.8961 122.731 88.7237 122.757C88.5513 122.783 88.3754 122.774 88.2064 122.731C87.5704 122.454 87.0251 122.003 86.6327 121.431C84.5887 118.966 82.8207 116.284 81.3603 113.434C81.2978 113.338 81.2021 113.269 81.0916 113.239C80.981 113.209 80.8633 113.22 80.7607 113.271C80.6582 113.322 80.578 113.41 80.5355 113.516C80.493 113.622 80.491 113.741 80.53 113.848C81.9552 116.804 83.4258 119.855 83.8218 123.132C83.8773 123.341 83.89 123.56 83.8592 123.774C83.8284 123.989 83.7546 124.195 83.6423 124.381C83.4755 124.509 83.2729 124.582 83.0626 124.59C82.8523 124.598 82.6447 124.541 82.4685 124.426C82.0062 124.101 81.6199 123.68 81.3361 123.192L76.0639 115.534C76.0203 115.454 75.9533 115.389 75.8718 115.349C75.7904 115.308 75.6984 115.293 75.6082 115.306Z" fill="#454545"/>
<path d="M207.372 99.7322C207.43 99.7275 207.486 99.7122 207.538 99.6872C215.214 94.2396 220.02 88.2106 221.846 81.7353C229.021 77.1588 232.984 70.6532 232.606 63.8795C231.975 51.8119 217.685 42.7708 200.833 43.6732C183.98 44.6076 170.809 55.1665 171.449 67.2315C172.089 79.2964 186.367 88.3304 203.184 87.4163C205.507 87.2898 207.816 86.9722 210.087 86.4668C209.922 90.8412 208.779 95.1232 206.742 98.998C206.69 99.085 206.665 99.1856 206.671 99.2869C206.677 99.3882 206.712 99.4854 206.774 99.5662C206.835 99.6469 206.919 99.7074 207.016 99.7398C207.112 99.7722 207.215 99.7751 207.313 99.7481L207.372 99.7322ZM200.922 44.7007C217.2 43.8164 230.955 52.4343 231.59 63.933C231.952 70.3746 228.141 76.5868 221.138 80.9804C221.033 81.051 220.954 81.1535 220.912 81.2727C219.332 87.1032 215.203 92.6024 208.622 97.6282C210.263 93.9118 211.129 89.9003 211.169 85.8383C211.169 85.7615 211.151 85.6857 211.117 85.6166C211.084 85.5475 211.035 85.4869 210.975 85.4395C210.914 85.392 210.844 85.3589 210.769 85.3426C210.694 85.3263 210.616 85.3272 210.541 85.3453C208.13 85.9195 205.672 86.2772 203.197 86.4139C186.902 87.3133 173.124 78.6909 172.49 67.1922C171.855 55.6935 184.6 45.6178 200.892 44.7086L200.922 44.7007Z" fill="#D3CECB"/>
<path d="M75.9248 170.151C75.6912 170.236 75.5706 170.494 75.6553 170.728C75.7401 170.961 75.9982 171.082 76.2319 170.997L75.9248 170.151ZM105.694 146.36L105.325 146.103L105.694 146.36ZM108.515 125.736L108.549 126.184L108.515 125.736ZM96.4601 134.933L96.8292 135.19L96.4601 134.933ZM151.042 128.024L151.44 128.233L151.042 128.024ZM149.712 113.888L149.745 114.337L149.712 113.888ZM133.632 131.3L133.202 131.17L133.202 131.17L133.632 131.3ZM133.617 131.352L134.047 131.482L134.047 131.482L133.617 131.352ZM157.054 147.878C157.291 147.802 157.421 147.548 157.345 147.312C157.268 147.075 157.015 146.945 156.778 147.021L157.054 147.878ZM133.202 131.17L133.186 131.221L134.047 131.482L134.063 131.431L133.202 131.17ZM136.528 147.943C142.241 142.272 147.672 135.394 151.44 128.233L150.643 127.814C146.927 134.878 141.557 141.683 135.893 147.305L136.528 147.943ZM151.44 128.233C152.08 127.017 153.347 123.307 153.658 119.949C153.814 118.276 153.74 116.614 153.172 115.382C152.884 114.756 152.461 114.228 151.866 113.877C151.271 113.526 150.542 113.375 149.678 113.44L149.745 114.337C150.465 114.283 151.003 114.413 151.409 114.652C151.815 114.892 152.126 115.263 152.355 115.759C152.821 116.77 152.913 118.236 152.762 119.866C152.461 123.114 151.225 126.708 150.643 127.814L151.44 128.233ZM149.678 113.44C145.534 113.75 141.938 116.475 139.139 119.927C136.333 123.388 134.271 127.646 133.202 131.17L134.063 131.431C135.102 128.005 137.114 123.854 139.838 120.494C142.568 117.126 145.959 114.62 149.745 114.337L149.678 113.44ZM133.186 131.221C132.448 133.654 131.751 137 131.92 140.149C132.09 143.295 133.133 146.344 135.983 148.012L136.438 147.236C133.964 145.788 132.981 143.108 132.819 140.101C132.657 137.097 133.325 133.865 134.047 131.482L133.186 131.221ZM96.091 134.675C93.9783 137.703 91.552 142.707 90.6804 147.575C89.8122 152.425 90.4654 157.33 94.7866 159.86L95.2412 159.083C91.4248 156.849 90.7184 152.47 91.5663 147.734C92.411 143.016 94.7766 138.132 96.8292 135.19L96.091 134.675ZM108.481 125.287C105.71 125.494 103.304 126.859 101.256 128.647C99.2081 130.434 97.4895 132.671 96.091 134.675L96.8292 135.19C98.2148 133.204 99.8836 131.039 101.848 129.325C103.811 127.611 106.04 126.372 108.549 126.184L108.481 125.287ZM106.063 146.617C107.294 144.848 110.281 139.483 111.829 134.629C112.599 132.216 113.042 129.845 112.667 128.091C112.477 127.198 112.067 126.431 111.349 125.919C110.635 125.41 109.678 125.197 108.481 125.287L108.549 126.184C109.611 126.105 110.337 126.302 110.827 126.652C111.313 126.999 111.63 127.541 111.787 128.279C112.109 129.786 111.735 131.961 110.972 134.355C109.454 139.114 106.511 144.399 105.325 146.103L106.063 146.617ZM95.3309 159.791C99.3824 155.769 102.824 151.27 106.063 146.617L105.325 146.103C102.099 150.737 98.6929 155.185 94.6969 159.152L95.3309 159.791ZM76.2319 170.997C83.5488 168.342 89.6767 165.404 95.3309 159.791L94.6969 159.152C89.1766 164.632 83.1888 167.515 75.9248 170.151L76.2319 170.997ZM94.7866 159.86C101.288 163.666 109.486 162.902 117.124 160.117C124.773 157.327 131.961 152.476 136.528 147.943L135.893 147.305C131.416 151.749 124.338 156.528 116.816 159.271C109.282 162.019 101.403 162.69 95.2412 159.083L94.7866 159.86ZM135.983 148.012C139.071 149.82 142.811 150.274 146.52 150.039C150.233 149.803 153.958 148.876 157.054 147.878L156.778 147.021C153.72 148.007 150.074 148.912 146.463 149.14C142.848 149.369 139.312 148.918 136.438 147.236L135.983 148.012Z" fill="#D7D3D1"/>
<path d="M34.1208 1.67272L34.1208 1.67271C39.9029 0.930545 45.3366 1.92561 49.4422 4.11327C53.5488 6.30152 56.2929 9.65956 56.8045 13.6458C57.3161 17.632 55.5083 21.5751 52.0884 24.7305C48.6694 27.8851 43.6661 30.2212 37.8913 30.9639L37.8912 30.9639C36.1449 31.1889 34.3819 31.2561 32.6236 31.1648L32.0474 31.1349L32.1587 31.7011C32.6982 34.4435 33.7686 37.0476 35.3035 39.3693C29.2428 36.2215 26.3184 32.6297 24.9143 29.7128L24.8379 29.5541L24.6742 29.4894C19.3949 27.4043 15.8095 23.6168 15.215 18.9913L15.2149 18.991C14.6999 15.0042 16.5008 11.0607 19.9169 7.90532C23.3321 4.7507 28.3352 2.4146 34.1208 1.67272Z" stroke="#D3CECB" stroke-width="0.9"/>
<path d="M61.0231 135.858L48.305 133.863L49.2502 146.414L57.0239 149.239L65.7092 152.397L63.3431 144.054L61.0231 135.858Z" fill="white"/>
<path d="M66.0579 152.457C66.0648 152.408 66.0613 152.357 66.0477 152.309L61.3616 135.77C61.3421 135.706 61.305 135.649 61.2544 135.604C61.2038 135.56 61.1417 135.529 61.075 135.517L48.3569 133.522C48.3039 133.516 48.2503 133.521 48.1995 133.537C48.1487 133.552 48.1018 133.578 48.0616 133.612C48.0239 133.647 47.9944 133.69 47.9751 133.737C47.9558 133.784 47.9471 133.835 47.9496 133.887L48.9053 146.418C48.9097 146.484 48.9336 146.547 48.9742 146.6C49.0148 146.653 49.0702 146.693 49.1337 146.716L65.5927 152.7C65.6534 152.724 65.7197 152.73 65.7838 152.719C65.848 152.708 65.9075 152.679 65.9554 152.636C66.0052 152.587 66.0406 152.525 66.0579 152.457ZM60.7765 136.167L65.2501 151.856L49.6494 146.181L48.7496 134.284L60.7765 136.167Z" fill="#454545"/>
<path d="M55.3166 137.493L51.3945 141.013L15.4608 102.75L19.3828 99.2312L55.3166 137.493Z" fill="white"/>
<path d="M55.6719 137.552L55.6801 137.504C55.6838 137.459 55.6774 137.413 55.6612 137.37C55.645 137.327 55.6195 137.289 55.5863 137.256L19.6493 98.9922C19.6177 98.9586 19.5797 98.9313 19.5375 98.912C19.4953 98.8927 19.4496 98.8817 19.4032 98.8797C19.3104 98.8779 19.2205 98.9099 19.1509 98.9694L15.2269 102.5C15.1925 102.529 15.1645 102.565 15.1448 102.606C15.1251 102.646 15.1141 102.69 15.1124 102.735C15.1107 102.78 15.1183 102.825 15.1349 102.867C15.1515 102.909 15.1766 102.948 15.2087 102.98L51.1388 141.243C51.2014 141.311 51.2888 141.353 51.382 141.358C51.4752 141.364 51.5665 141.333 51.6361 141.273L55.56 137.743C55.6186 137.693 55.6581 137.626 55.6719 137.552ZM19.3681 99.7079L54.811 137.466L51.4142 140.519L15.9632 102.767L19.3681 99.7079Z" fill="#454545"/>
<path d="M15.465 102.744L13.3205 108.151L49.2506 146.414L51.4021 141.008L15.465 102.744Z" fill="#454545"/>
<path d="M51.7505 141.068C51.7591 141.015 51.7552 140.961 51.7392 140.91C51.7231 140.858 51.6954 140.811 51.658 140.772L15.728 102.509C15.6862 102.466 15.6344 102.434 15.5772 102.416C15.52 102.398 15.4592 102.395 15.4004 102.405C15.3415 102.416 15.2864 102.441 15.2401 102.478C15.1938 102.515 15.1578 102.563 15.1352 102.617L12.9907 108.024C12.966 108.084 12.9591 108.15 12.9709 108.214C12.9827 108.278 13.0127 108.337 13.0575 108.386L48.9945 146.65C49.0356 146.693 49.0869 146.724 49.1438 146.742C49.2006 146.761 49.261 146.764 49.3195 146.753C49.3779 146.742 49.4325 146.717 49.4781 146.68C49.5237 146.643 49.5589 146.595 49.5803 146.541L51.7318 141.135L51.7505 141.068ZM15.5906 103.389L50.994 141.085L49.1238 145.775L13.7226 108.108L15.5906 103.389Z" fill="#454545"/>
<path d="M25.0873 97.5939L19.3945 99.228L55.3245 137.491L61.0174 135.857L25.0873 97.5939Z" fill="#454545"/>
<path d="M61.367 135.946C61.3741 135.893 61.3695 135.839 61.3535 135.788C61.3376 135.737 61.3107 135.69 61.2745 135.65L25.3492 97.3593C25.3025 97.3132 25.2443 97.2799 25.1805 97.2627C25.1166 97.2455 25.0494 97.2452 24.9856 97.2616L19.2858 98.8944C19.2279 98.9097 19.1755 98.9401 19.1341 98.9824C19.0927 99.0246 19.0639 99.0771 19.0507 99.1344C19.0349 99.1901 19.034 99.2491 19.0482 99.3055C19.0623 99.362 19.091 99.414 19.1315 99.4567L55.0616 137.72C55.1107 137.764 55.1717 137.793 55.2371 137.805C55.3026 137.816 55.3699 137.809 55.431 137.783L61.1296 136.157C61.1865 136.14 61.2379 136.109 61.279 136.067C61.3201 136.025 61.3496 135.974 61.3647 135.918L61.367 135.946ZM25.01 97.9949L60.4135 135.691L55.4693 137.103L20.0345 99.381L25.01 97.9949Z" fill="#454545"/>
<path d="M63.3443 144.054L60.8645 144.117L57.3153 147.305L57.0251 149.239L65.7104 152.397L63.3443 144.054Z" fill="#03C7E8"/>
<path d="M66.0577 152.457C66.0646 152.408 66.0611 152.357 66.0475 152.308L63.6825 143.958C63.6602 143.885 63.6136 143.82 63.5499 143.775C63.4863 143.73 63.4092 143.706 63.3308 143.708L60.8499 143.779C60.7667 143.779 60.687 143.811 60.6267 143.866L57.0786 147.048C57.0218 147.104 56.9823 147.175 56.9644 147.252L56.6801 149.194C56.6674 149.271 56.6828 149.35 56.7237 149.418C56.7646 149.485 56.8284 149.536 56.9037 149.562L65.589 152.72C65.6497 152.744 65.716 152.751 65.7801 152.74C65.8443 152.728 65.9038 152.7 65.9517 152.657C66.0061 152.602 66.043 152.533 66.0577 152.457ZM63.0822 144.402L65.1872 151.845L57.4136 149.019L57.6438 147.475L61.0336 144.462L63.0822 144.402Z" fill="#454545"/>
<path d="M73.8776 59.2742L68.5595 58.375L64.808 62.2459L64.0142 56.9138L59.1738 54.5463L64.0001 52.1437L64.7589 46.8115L68.5384 50.6543L73.8425 49.727L71.3485 54.5112L73.8776 59.2742Z" fill="#D3CECB"/>
<path d="M31.6856 197.783L25.1642 196.68L24.9049 196.636L24.7218 196.825L20.1199 201.574L19.146 195.033L19.1073 194.773L18.8712 194.657L12.9346 191.753L18.8544 188.806L19.0896 188.689L19.1266 188.429L20.0574 181.888L24.6946 186.603L24.8786 186.79L25.1372 186.745L31.6422 185.607L28.5826 191.477L28.461 191.71L28.5844 191.942L31.6856 197.783Z" stroke="#D3CECB"/>
<path d="M245.799 173.629L238.276 172.357L232.97 177.832L231.847 170.29L225 166.941L231.827 163.542L232.9 156L238.246 161.436L245.749 160.124L242.221 166.891L245.799 173.629Z" fill="#D7D3D1"/>
<path d="M220.924 155.81L219.388 153.342L219.249 153.119L218.986 153.107L216.081 152.974L217.952 150.748L218.121 150.547L218.051 150.293L217.279 147.493L219.973 148.584L220.217 148.683L220.436 148.538L222.862 146.939L222.66 149.84L222.642 150.102L222.847 150.266L225.116 152.077L222.291 152.779L222.036 152.842L221.943 153.089L220.924 155.81Z" stroke="#D3CECB"/>
<path d="M46.3063 94.4625L41.8463 90.7153L41.665 90.5631L41.4369 90.6262L35.8235 92.1786L38.0027 86.777L38.0912 86.5576L37.9606 86.3603L34.7482 81.5065L40.5584 81.9101L40.7943 81.9264L40.9416 81.7415L44.5671 77.1896L45.9847 82.8389L46.0422 83.068L46.2635 83.1509L51.7106 85.1897L46.7684 88.2814L46.5677 88.407L46.5575 88.6435L46.3063 94.4625Z" stroke="#D3CECB" stroke-width="0.9"/>
</svg>

Before

Width:  |  Height:  |  Size: 31 KiB

View File

@@ -1,37 +1,38 @@
/* eslint-disable react/forbid-prop-types */
import React, { useMemo, useState } from 'react';
import { useCallback, useMemo, useState } from 'react';
import PropTypes from 'prop-types';
import {
Collapsible, Form, Icon, Spinner,
} from '@openedx/paragon';
import { Tune } from '@openedx/paragon/icons';
import { capitalize, toString } from 'lodash';
import { useSelector } from 'react-redux';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import {
Collapsible, Form, Icon, Spinner,
} from '@edx/paragon';
import { Tune } from '@edx/paragon/icons';
import { useIntl } from '@edx/frontend-platform/i18n';
import {
PostsStatusFilter, RequestStatus,
ThreadOrdering, ThreadType,
} from '../data/constants';
import { selectCourseCohorts } from '../discussions/cohorts/data/selectors';
import selectCourseCohorts from '../discussions/cohorts/data/selectors';
import messages from '../discussions/posts/post-filter-bar/messages';
import { ActionItem } from '../discussions/posts/post-filter-bar/PostFilterBar';
function FilterBar({
intl,
const FilterBar = ({
filters,
selectedFilters,
onFilterChange,
showCohortsFilter,
}) {
}) => {
const intl = useIntl();
const [isOpen, setOpen] = useState(false);
const cohorts = useSelector(selectCourseCohorts);
const { status } = useSelector(state => state.cohorts);
const selectedCohort = useMemo(() => cohorts.find(cohort => (
toString(cohort.id) === selectedFilters.cohort)),
[selectedFilters.cohort]);
const selectedCohort = useMemo(
() => cohorts.find(cohort => (
toString(cohort.id) === selectedFilters.cohort)),
[selectedFilters.cohort],
);
const allFilters = [
{
@@ -91,10 +92,15 @@ function FilterBar({
},
];
const handleFilterToggle = useCallback((event) => {
onFilterChange(event);
setOpen(false);
}, [onFilterChange]);
return (
<Collapsible.Advanced
open={isOpen}
onToggle={() => setOpen(!isOpen)}
onToggle={setOpen}
className="filter-bar collapsible-card-lg border-0"
>
<Collapsible.Trigger className="collapsible-trigger border-0">
@@ -124,7 +130,7 @@ function FilterBar({
name={value.name}
className="d-flex flex-column list-group list-group-flush"
value={selectedFilters[value.name]}
onChange={onFilterChange}
onChange={handleFilterToggle}
>
{value.filters.map(filterName => {
const element = allFilters.find(obj => obj.id === filterName);
@@ -157,7 +163,7 @@ function FilterBar({
name="cohort"
className="d-flex flex-column list-group list-group-flush w-100"
value={selectedFilters.cohort}
onChange={onFilterChange}
onChange={handleFilterToggle}
>
<ActionItem
id="all-groups"
@@ -169,7 +175,7 @@ function FilterBar({
<ActionItem
key={toString(cohort.id)}
id={toString(cohort.id)}
label={capitalize(cohort.name)}
label={cohort.name}
value={toString(cohort.id)}
selected={selectedFilters.cohort}
/>
@@ -183,12 +189,19 @@ function FilterBar({
</Collapsible.Body>
</Collapsible.Advanced>
);
}
};
FilterBar.propTypes = {
intl: intlShape.isRequired,
filters: PropTypes.array.isRequired,
selectedFilters: PropTypes.object.isRequired,
filters: PropTypes.arrayOf(PropTypes.shape({
name: PropTypes.string,
filters: PropTypes.arrayOf(PropTypes.string),
})).isRequired,
selectedFilters: PropTypes.shape({
postType: ThreadType,
status: PostsStatusFilter,
orderBy: ThreadOrdering,
cohort: PropTypes.string,
}).isRequired,
onFilterChange: PropTypes.func.isRequired,
showCohortsFilter: PropTypes.bool,
};
@@ -197,4 +210,4 @@ FilterBar.defaultProps = {
showCohortsFilter: false,
};
export default injectIntl(FilterBar);
export default FilterBar;

View File

@@ -1,35 +1,29 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Form, TransitionReplace } from '@openedx/paragon';
import { getIn, useFormikContext } from 'formik';
import { Form, TransitionReplace } from '@edx/paragon';
function FormikErrorFeedback({ name }) {
const {
touched,
errors,
} = useFormikContext();
const FormikErrorFeedback = ({ name }) => {
const { touched, errors } = useFormikContext();
const fieldTouched = getIn(touched, name);
const fieldError = getIn(errors, name);
return (
<TransitionReplace>
{fieldTouched && fieldError
? (
<Form.Control.Feedback type="invalid" hasIcon={false} key={`${name}-error-feedback`}>
{fieldError}
</Form.Control.Feedback>
)
: (
<React.Fragment key={`${name}-no-error-feedback`} />
)}
{fieldTouched && fieldError ? (
<Form.Control.Feedback type="invalid" hasIcon={false} key={`${name}-error-feedback`}>
{fieldError}
</Form.Control.Feedback>
) : (
<React.Fragment key={`${name}-no-error-feedback`} />
)}
</TransitionReplace>
);
}
};
FormikErrorFeedback.propTypes = {
name: PropTypes.string.isRequired,
};
export default FormikErrorFeedback;
export default React.memo(FormikErrorFeedback);

View File

@@ -9,42 +9,50 @@ import { useDebounce } from '../discussions/data/hooks';
const defaultSanitizeOptions = {
USE_PROFILES: { html: true },
ADD_ATTR: ['columnalign'],
ADD_ATTR: ['columnalign', 'target'],
};
function HTMLLoader({
htmlNode, componentId, cssClassName, testId,
}) {
const HTMLLoader = ({
htmlNode, componentId, cssClassName, testId, delay,
}) => {
const sanitizedMath = DOMPurify.sanitize(htmlNode, { ...defaultSanitizeOptions });
const previewRef = useRef();
const debouncedPostContent = useDebounce(htmlNode, 500);
const previewRef = useRef(null);
const debouncedPostContent = useDebounce(htmlNode, delay);
useEffect(() => {
let promise = Promise.resolve(); // Used to hold chain of typesetting calls
function typeset(code) {
promise = promise.then(() => window.MathJax?.typesetPromise(code()))
promise = promise.then(() => {
if (typeof window?.MathJax !== 'undefined' && typeof window?.MathJax.startup !== 'undefined') {
window.MathJax.startup.defaultPageReady().then((window.MathJax?.typesetPromise(code())));
}
return null;
})
.catch((err) => logError(`Typeset failed: ${err.message}`));
return promise;
}
if (debouncedPostContent) {
typeset(() => {
previewRef.current.innerHTML = sanitizedMath;
if (previewRef.current !== null && typeof window?.MathJax !== 'undefined') {
previewRef.current.innerHTML = sanitizedMath;
}
});
}
}, [debouncedPostContent]);
return (
<div ref={previewRef} className={cssClassName} id={componentId} data-testid={testId} />
);
}
};
HTMLLoader.propTypes = {
htmlNode: PropTypes.node,
componentId: PropTypes.string,
cssClassName: PropTypes.string,
testId: PropTypes.string,
delay: PropTypes.number,
};
HTMLLoader.defaultProps = {
@@ -52,6 +60,7 @@ HTMLLoader.defaultProps = {
componentId: null,
cssClassName: '',
testId: '',
delay: 0,
};
export default HTMLLoader;
export default React.memo(HTMLLoader);

View File

@@ -0,0 +1,21 @@
import { Helmet } from 'react-helmet';
import { getConfig } from '@edx/frontend-platform';
import { useIntl } from '@edx/frontend-platform/i18n';
import messages from './messages';
const Head = () => {
const intl = useIntl();
return (
<Helmet>
<title>
{intl.formatMessage(messages['discussions.page.title'], { siteName: getConfig().SITE_NAME })}
</title>
<link rel="shortcut icon" href={getConfig().FAVICON_URL} type="image/x-icon" />
</Helmet>
);
};
export default Head;

View File

@@ -0,0 +1,20 @@
import React from 'react';
import { render } from '@testing-library/react';
import { Helmet } from 'react-helmet';
import { getConfig } from '@edx/frontend-platform';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import Head from './Head';
describe('Head', () => {
const props = {};
it('should match render title tag and favicon with the site configuration values', () => {
render(<IntlProvider locale="en"><Head {...props} /></IntlProvider>);
const helmet = Helmet.peek();
expect(helmet.title).toEqual(`Discussions | ${getConfig().SITE_NAME}`);
expect(helmet.linkTags[0].rel).toEqual('shortcut icon');
expect(helmet.linkTags[0].href).toEqual(getConfig().FAVICON_URL);
});
});

View File

@@ -0,0 +1,11 @@
import { defineMessages } from '@edx/frontend-platform/i18n';
const messages = defineMessages({
'discussions.page.title': {
id: 'discussions.page.title',
defaultMessage: 'Discussions | {siteName}',
description: 'Title tag',
},
});
export default messages;

View File

@@ -1,64 +1,39 @@
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import React from 'react';
import classNames from 'classnames';
import { useDispatch, useSelector } from 'react-redux';
import { useSelector } from 'react-redux';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { useIntl } from '@edx/frontend-platform/i18n';
import { fetchTab } from './data/thunks';
import Tabs from './tabs/Tabs';
import messages from './messages';
import './navBar.scss';
function CourseTabsNavigation({
activeTab, className, intl, courseId, rootSlug,
}) {
const dispatch = useDispatch();
const CourseTabsNavigation = () => {
const intl = useIntl();
const tabs = useSelector(state => state.courseTabs.tabs);
useEffect(() => {
dispatch(fetchTab(courseId, rootSlug));
}, [courseId]);
return (
<div id="courseTabsNavigation" className={classNames('course-tabs-navigation', className)}>
<div className="container-xl">
{!!tabs.length
&& (
<Tabs
className="nav-underline-tabs"
aria-label={intl.formatMessage(messages.courseMaterial)}
>
{tabs.map(({ url, title, slug }) => (
<a
key={slug}
className={classNames('nav-item flex-shrink-0 nav-link', { active: slug === activeTab })}
href={url}
>
{title}
</a>
))}
</Tabs>
)}
</div>
<div id="courseTabsNavigation" className="course-tabs-navigation px-4 bg-white">
{!!tabs.length && (
<Tabs
className="nav-underline-tabs"
aria-label={intl.formatMessage(messages.courseMaterial)}
>
{tabs.map(({ url, title, slug }) => (
<a
key={slug}
className={classNames('nav-item flex-shrink-0 nav-link', { active: slug === 'discussion' })}
href={url}
>
{title}
</a>
))}
</Tabs>
)}
</div>
);
}
CourseTabsNavigation.propTypes = {
activeTab: PropTypes.string,
className: PropTypes.string,
rootSlug: PropTypes.string,
courseId: PropTypes.string.isRequired,
intl: intlShape.isRequired,
};
CourseTabsNavigation.defaultProps = {
activeTab: undefined,
className: null,
rootSlug: 'outline',
};
export default injectIntl(CourseTabsNavigation);
export default React.memo(CourseTabsNavigation);

View File

@@ -0,0 +1 @@
import './navigationBar.factory';

View File

@@ -0,0 +1,50 @@
import { Factory } from 'rosie';
import { getApiBaseUrl } from '../../../../data/constants';
Factory.define('navigationBar')
.attr('can_show_upgrade_sock', null, false)
.attr('can_view_certificate', null, false)
.attr('celebrations', null, {
first_section: false, streak_discount_enabled: false, streak_length_to_celebrate: null, weekly_goal: false,
})
.option('hasCourseAccess', null, true)
.attr('course_access', ['hasCourseAccess'], (hasCourseAccess) => ({
additional_context_user_message: null,
developer_message: null,
error_code: null,
has_access: hasCourseAccess,
user_fragment: null,
user_message: null,
}))
.option('course_id', null, 'course-v1:edX+DemoX+Demo_Course')
.sequence('is_enrolled', ['isEnrolled'], (idx, isEnrolled) => isEnrolled)
.attr('is_self_paced', null, false)
.attr('is_staff', null, true)
.attr('number', null, 'DemoX')
.attr('org', null, 'edX')
.attr('original_user_is_staff', null, true)
.attr('title', null, 'Demonstration Course')
.attr('username', null, 'edx')
.attr('tabs', ['course_id'], (idx, courseId) => [
{
tab_id: 'courseware',
title: 'Course',
url: `${getApiBaseUrl}/course/${courseId}/home`,
},
{
tab_id: 'progress',
title: 'Progress',
url: `${getApiBaseUrl}/course/${courseId}/progress`,
},
{
tab_id: 'discussion',
title: 'Discussion',
url: `${getApiBaseUrl}/course/${courseId}/discussion/forum/`,
},
{
tab_id: 'instructor',
title: 'Instructor',
url: `${getApiBaseUrl}/course/${courseId}/instructor`,
}]);

View File

@@ -1,18 +1,16 @@
/* eslint-disable import/prefer-default-export */
import { camelCaseObject } from '@edx/frontend-platform';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { getApiBaseUrl } from '../../../data/constants';
function normalizeCourseHomeCourseMetadata(metadata, rootSlug) {
export const getCourseMetadataApiUrl = (courseId) => `${getApiBaseUrl()}/api/course_home/course_metadata/${courseId}`;
function normalizeCourseHomeCourseMetadata(metadata) {
const data = camelCaseObject(metadata);
return {
...data,
tabs: data.tabs.map(tab => ({
// The API uses "courseware" as a slug for both courseware and the outline tab.
// If needed, we switch it to "outline" here for
// use within the MFE to differentiate between course home and courseware.
slug: tab.tabId === 'courseware' ? rootSlug : tab.tabId,
slug: tab.tabId === 'courseware' ? 'outline' : tab.tabId,
title: tab.title,
url: tab.url,
})),
@@ -20,10 +18,9 @@ function normalizeCourseHomeCourseMetadata(metadata, rootSlug) {
};
}
export async function getCourseHomeCourseMetadata(courseId, rootSlug) {
const url = `${getApiBaseUrl()}/api/course_home/course_metadata/${courseId}`;
// don't know the context of adding timezone in url. hence omitting it
// url = appendBrowserTimezoneToUrl(url);
export async function getCourseHomeCourseMetadata(courseId) {
const url = getCourseMetadataApiUrl(courseId);
const { data } = await getAuthenticatedHttpClient().get(url);
return normalizeCourseHomeCourseMetadata(data, rootSlug);
return normalizeCourseHomeCourseMetadata(data);
}

View File

@@ -0,0 +1,67 @@
import MockAdapter from 'axios-mock-adapter';
import { Factory } from 'rosie';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { initializeMockApp } from '@edx/frontend-platform/testing';
import { initializeStore } from '../../../store';
import executeThunk from '../../../test-utils';
import { getCourseMetadataApiUrl } from './api';
import fetchTab from './thunks';
import './__factories__';
const courseId = 'course-v1:edX+TestX+Test_Course';
let axiosMock = null;
let store;
describe('Navigation bar api tests', () => {
beforeEach(() => {
initializeMockApp({
authenticatedUser: {
userId: 3,
username: 'abc123',
administrator: true,
roles: [],
},
});
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
store = initializeStore();
});
afterEach(() => {
axiosMock.reset();
});
it('Successfully get navigation tabs', async () => {
axiosMock.onGet(`${getCourseMetadataApiUrl(courseId)}`).reply(200, (Factory.build('navigationBar', 1, { isEnrolled: true })));
await executeThunk(fetchTab(courseId, 'outline'), store.dispatch, store.getState);
expect(store.getState().courseTabs.tabs).toHaveLength(4);
expect(store.getState().courseTabs.courseStatus).toEqual('loaded');
});
it('Failed to get navigation tabs', async () => {
axiosMock.onGet(`${getCourseMetadataApiUrl(courseId)}`).reply(404);
await executeThunk(fetchTab(courseId, 'outline'), store.dispatch, store.getState);
expect(store.getState().courseTabs.courseStatus).toEqual('failed');
});
it('Denied to get navigation tabs', async () => {
axiosMock.onGet(`${getCourseMetadataApiUrl(courseId)}`).reply(403, {});
await executeThunk(fetchTab(courseId, 'outline'), store.dispatch, store.getState);
expect(store.getState().courseTabs.courseStatus).toEqual('denied');
});
it('Denied to get navigation bar when user has no access on course', async () => {
axiosMock.onGet(`${getCourseMetadataApiUrl(courseId)}`).reply(
200,
(Factory.build('navigationBar', 1, { hasCourseAccess: false, isEnrolled: true })),
);
await executeThunk(fetchTab(courseId, 'outline'), store.dispatch, store.getState);
expect(store.getState().courseTabs.courseStatus).toEqual('denied');
});
});

View File

@@ -1,3 +1,3 @@
/* eslint-disable import/prefer-default-export */
const selectCourseTabs = state => state.courseTabs;
export const selectCourseTabs = state => state.courseTabs;
export default selectCourseTabs;

View File

@@ -1,4 +1,3 @@
/* eslint-disable no-param-reassign */
import { createSlice } from '@reduxjs/toolkit';
export const LOADING = 'loading';
@@ -14,30 +13,44 @@ const slice = createSlice({
tabs: [],
courseTitle: null,
courseNumber: null,
isEnrolled: false,
org: null,
},
reducers: {
fetchTabDenied: (state, { payload }) => {
state.courseId = payload.courseId;
state.courseStatus = DENIED;
},
fetchTabFailure: (state, { payload }) => {
state.courseId = payload.courseId;
state.courseStatus = FAILED;
},
fetchTabRequest: (state, { payload }) => {
state.courseId = payload.courseId;
state.courseStatus = LOADING;
},
fetchTabSuccess: (state, { payload }) => {
state.courseId = payload.courseId;
state.targetUserId = payload.targetUserId;
state.tabs = payload.tabs;
state.courseStatus = LOADED;
state.courseTitle = payload.courseTitle;
state.courseNumber = payload.courseNumber;
state.org = payload.org;
},
fetchTabDenied: (state, { payload }) => (
{
...state,
courseId: payload.courseId,
courseStatus: DENIED,
}
),
fetchTabFailure: (state, { payload }) => (
{
...state,
courseId: payload.courseId,
courseStatus: FAILED,
}
),
fetchTabRequest: (state, { payload }) => (
{
...state,
courseId: payload.courseId,
courseStatus: LOADING,
}
),
fetchTabSuccess: (state, { payload }) => (
{
...state,
courseId: payload.courseId,
targetUserId: payload.targetUserId,
tabs: payload.tabs,
courseStatus: LOADED,
courseTitle: payload.courseTitle,
courseNumber: payload.courseNumber,
org: payload.org,
isEnrolled: payload.isEnrolled,
}
),
},
});

View File

@@ -1,6 +1,6 @@
/* eslint-disable import/prefer-default-export, no-unused-expressions */
import { logError } from '@edx/frontend-platform/logging';
import { getHttpErrorStatus } from '../../../discussions/utils';
import { getCourseHomeCourseMetadata } from './api';
import {
fetchTabDenied,
@@ -9,11 +9,11 @@ import {
fetchTabSuccess,
} from './slice';
export function fetchTab(courseId, rootSlug) {
export default function fetchTab(courseId) {
return async (dispatch) => {
dispatch(fetchTabRequest({ courseId }));
try {
const courseHomeCourseMetadata = await getCourseHomeCourseMetadata(courseId, rootSlug);
const courseHomeCourseMetadata = await getCourseHomeCourseMetadata(courseId);
if (!courseHomeCourseMetadata.courseAccess.hasAccess) {
dispatch(fetchTabDenied({ courseId }));
} else {
@@ -23,10 +23,15 @@ export function fetchTab(courseId, rootSlug) {
org: courseHomeCourseMetadata.org,
courseNumber: courseHomeCourseMetadata.number,
courseTitle: courseHomeCourseMetadata.title,
isEnrolled: courseHomeCourseMetadata.isEnrolled,
}));
}
} catch (e) {
dispatch(fetchTabFailure({ courseId }));
if (getHttpErrorStatus(e) === 403) {
dispatch(fetchTabDenied({ courseId }));
} else {
dispatch(fetchTabFailure({ courseId }));
}
logError(e);
}
};

View File

@@ -1,2 +0,0 @@
/* eslint-disable import/prefer-default-export */
export { default as CourseTabsNavigation } from './CourseTabsNavigation';

View File

@@ -1,23 +1,16 @@
@import "@edx/brand/paragon/fonts.scss";
@import "@edx/brand/paragon/variables.scss";
@import "@edx/paragon/scss/core/core.scss";
@import "@edx/brand/paragon/overrides.scss";
$fa-font-path: "~font-awesome/fonts";
@import "~font-awesome/scss/font-awesome";
.course-tabs-navigation {
border-bottom: solid 1px #eaeaea;
.nav a,
.nav button {
&:hover {
background-color: $light-400;
background-color: var(--pgn-color-light-400);
}
}
.nav a {
&:not(.active):hover {
background-color: $light-400;
background-color: var(--pgn-color-light-400);
border-bottom: none;
}
}
@@ -29,7 +22,7 @@ $fa-font-path: "~font-awesome/fonts";
.nav-link {
border-bottom: 4px solid transparent;
border-top: 4px solid transparent;
color: $gray-700;
color: var(--pgn-color-gray-700);
// temporary until we can remove .btn class from dropdowns
border-left: 0;
@@ -39,9 +32,9 @@ $fa-font-path: "~font-awesome/fonts";
&:hover,
&:focus,
&.active {
font-weight: $font-weight-normal;
color: $primary-500;
border-bottom-color: $primary-500;
font-weight: var(--pgn-typography-font-weight-normal);
color: var(--pgn-color-primary-500);
border-bottom-color: var(--pgn-color-primary-500);
}
}
}

View File

@@ -1,14 +1,14 @@
import React, { useMemo } from 'react';
import PropTypes from 'prop-types';
import { Dropdown } from '@openedx/paragon';
import classNames from 'classnames';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import { Dropdown } from '@edx/paragon';
import useIndexOfLastVisibleChild from './useIndexOfLastVisibleChild';
export default function Tabs({ children, className, ...attrs }) {
const Tabs = ({ children, className, ...attrs }) => {
const [
indexOfLastVisibleChild,
containerElementRef,
@@ -31,25 +31,28 @@ export default function Tabs({ children, className, ...attrs }) {
// Insert the overflow menu at the cut off index (even if it will be hidden
// it so it can be part of measurements)
wrappedChildren.splice(indexOfOverflowStart, 0, (
<div
className="nav-item flex-shrink-0"
style={indexOfOverflowStart >= React.Children.count(children) ? invisibleStyle : null}
ref={overflowElementRef}
key="overflow"
>
<Dropdown className="h-100">
<Dropdown.Toggle variant="link" className="nav-link h-100" id="learn.course.tabs.navigation.overflow.menu">
<FormattedMessage
id="learn.course.tabs.navigation.overflow.menu"
description="The title of the overflow menu for course tabs"
defaultMessage="More..."
/>
</Dropdown.Toggle>
<Dropdown.Menu className="dropdown-menu-right">{overflowChildren}</Dropdown.Menu>
</Dropdown>
</div>
));
wrappedChildren.splice(
indexOfOverflowStart,
0, (
<div
className="nav-item flex-shrink-0"
style={indexOfOverflowStart >= React.Children.count(children) ? invisibleStyle : null}
ref={overflowElementRef}
key="overflow"
>
<Dropdown className="h-100">
<Dropdown.Toggle variant="link" className="nav-link h-100" id="learn.course.tabs.navigation.overflow.menu">
<FormattedMessage
id="learn.course.tabs.navigation.overflow.menu"
description="The title of the overflow menu for course tabs"
defaultMessage="More..."
/>
</Dropdown.Toggle>
<Dropdown.Menu className="dropdown-menu-right">{overflowChildren}</Dropdown.Menu>
</Dropdown>
</div>
),
);
return wrappedChildren;
}, [children, indexOfLastVisibleChild]);
@@ -62,7 +65,7 @@ export default function Tabs({ children, className, ...attrs }) {
{tabChildren}
</nav>
);
}
};
Tabs.propTypes = {
children: PropTypes.node,
@@ -73,3 +76,5 @@ Tabs.defaultProps = {
children: null,
className: undefined,
};
export default Tabs;

View File

@@ -1,6 +1,6 @@
import { useLayoutEffect, useRef, useState } from 'react';
import { useWindowSize } from '@edx/paragon';
import { useWindowSize } from '@openedx/paragon';
const invisibleStyle = {
position: 'absolute',

View File

@@ -0,0 +1,74 @@
import React, { useState } from 'react';
import {
Hyperlink, Icon, IconButton, IconButtonWithTooltip,
} from '@openedx/paragon';
import { Close, HelpOutline } from '@openedx/paragon/icons';
import { useIntl } from '@edx/frontend-platform/i18n';
import messages from '../discussions/posts/post-editor/messages';
const PostHelpPanel = () => {
const intl = useIntl();
const [showHelpPane, setShowHelpPane] = useState(false);
return (
<>
<div className="d-flex justify-content-end">
<IconButtonWithTooltip
onClick={() => setShowHelpPane(true)}
alt={intl.formatMessage(messages.showHelpIcon)}
tooltipContent={<div>{intl.formatMessage(messages.discussionHelpTooltip)}</div>}
src={HelpOutline}
iconAs={Icon}
size="inline"
className="float-right p-3 help-icon"
iconClassNames="help-icon-size"
data-testid="help-button"
invertColors
isActive
/>
</div>
{showHelpPane && (
<div
className="w-100 p-2 bg-light-200 rounded box-shadow-down-1 post-preview overflow-auto my-3"
style={{ minHeight: '200px', wordBreak: 'break-word' }}
>
<IconButton
onClick={() => setShowHelpPane(false)}
alt={intl.formatMessage(messages.actionsAlt)}
src={Close}
iconAs={Icon}
size="inline"
className="float-right p-3"
iconClassNames="icon-size"
data-testid="hide-help-button"
/>
<div className="pt-2 px-3">
<h4 className="font-weight-bold">{intl.formatMessage(messages.discussionHelpHeader)}</h4>
<p className="pt-2">{intl.formatMessage(messages.discussionHelpDescription)}</p>
<Hyperlink
target="_blank"
className="w-100"
destination="https://support.edx.org/hc/en-us/sections/115004169687-Participating-in-Course-Discussions"
showLaunchIcon={false}
>
{intl.formatMessage(messages.discussionHelpCourseParticipation)}
</Hyperlink>
<Hyperlink
target="_blank"
className="w-100"
destination="https://support.edx.org/hc/en-us/articles/360000035267-Entering-math-expressions-in-course-discussions"
showLaunchIcon={false}
>
{intl.formatMessage(messages.discussionHelpMathExpressions)}
</Hyperlink>
</div>
</div>
)}
</>
);
};
export default React.memo(PostHelpPanel);

View File

@@ -1,23 +1,25 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Button, Icon, IconButton } from '@edx/paragon';
import { Close } from '@edx/paragon/icons';
import { Button, Icon, IconButton } from '@openedx/paragon';
import { Close } from '@openedx/paragon/icons';
import { useIntl } from '@edx/frontend-platform/i18n';
import messages from '../discussions/posts/post-editor/messages';
import HTMLLoader from './HTMLLoader';
function PostPreviewPane({
htmlNode, intl, isPost, editExisting,
}) {
const PostPreviewPanel = ({
htmlNode, isPost, editExisting,
}) => {
const intl = useIntl();
const [showPreviewPane, setShowPreviewPane] = useState(false);
return (
<>
{showPreviewPane && (
<div
className={`w-100 p-2 bg-light-200 rounded box-shadow-down-1 post-preview ${isPost ? 'mt-2 mb-5' : 'my-3'}`}
className={`w-100 p-2 bg-light-200 rounded box-shadow-down-1 post-preview overflow-auto ${isPost ? 'mt-2 mb-5' : 'my-3'}`}
style={{ minHeight: '200px', wordBreak: 'break-word' }}
>
<IconButton
@@ -28,8 +30,17 @@ function PostPreviewPane({
size="inline"
className="float-right p-3"
iconClassNames="icon-size"
data-testid="hide-preview-button"
/>
<HTMLLoader htmlNode={htmlNode} cssClassName="text-primary" componentId="post-preview" testId="post-preview" />
{htmlNode && (
<HTMLLoader
htmlNode={htmlNode}
cssClassName="text-primary"
componentId="post-preview"
testId="post-preview"
delay={500}
/>
)}
</div>
)}
<div className="d-flex justify-content-end">
@@ -40,6 +51,7 @@ function PostPreviewPane({
onClick={() => setShowPreviewPane(true)}
className={`text-primary-500 font-style p-0 ${editExisting && 'mb-4.5'}`}
style={{ lineHeight: '26px' }}
data-testid="show-preview-button"
>
{intl.formatMessage(messages.showPreviewButton)}
</Button>
@@ -47,18 +59,18 @@ function PostPreviewPane({
</div>
</>
);
}
};
PostPreviewPane.propTypes = {
intl: intlShape.isRequired,
htmlNode: PropTypes.node.isRequired,
PostPreviewPanel.propTypes = {
htmlNode: PropTypes.node,
isPost: PropTypes.bool,
editExisting: PropTypes.bool,
};
PostPreviewPane.defaultProps = {
PostPreviewPanel.defaultProps = {
htmlNode: '',
isPost: false,
editExisting: false,
};
export default injectIntl(PostPreviewPane);
export default React.memo(PostPreviewPanel);

View File

@@ -1,19 +1,22 @@
import React, { useContext, useEffect } from 'react';
import React, {
useCallback, useContext, useEffect, useRef, useState,
} from 'react';
import { Icon, SearchField } from '@openedx/paragon';
import { Search as SearchIcon } from '@openedx/paragon/icons';
import camelCase from 'lodash/camelCase';
import { useDispatch, useSelector } from 'react-redux';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Icon, SearchField } from '@edx/paragon';
import { Search as SearchIcon } from '@edx/paragon/icons';
import { useIntl } from '@edx/frontend-platform/i18n';
import { DiscussionContext } from '../discussions/common/context';
import DiscussionContext from '../discussions/common/context';
import { setUsernameSearch } from '../discussions/learners/data';
import { setSearchQuery } from '../discussions/posts/data';
import postsMessages from '../discussions/posts/post-actions-bar/messages';
import { setFilter as setTopicFilter } from '../discussions/topics/data/slices';
function Search({ intl }) {
const Search = () => {
const intl = useIntl();
const dispatch = useDispatch();
const { page } = useContext(DiscussionContext);
const postSearch = useSelector(({ threads }) => threads.filters.search);
@@ -21,8 +24,10 @@ function Search({ intl }) {
const learnerSearch = useSelector(({ learners }) => learners.usernameSearch);
const isPostSearch = ['posts', 'my-posts'].includes(page);
const isTopicSearch = 'topics'.includes(page);
let searchValue = '';
const [searchValue, setSearchValue] = useState('');
const previousSearchValueRef = useRef('');
let currentValue = '';
if (isPostSearch) {
currentValue = postSearch;
} else if (isTopicSearch) {
@@ -31,20 +36,22 @@ function Search({ intl }) {
currentValue = learnerSearch;
}
const onClear = () => {
const onClear = useCallback(() => {
dispatch(setSearchQuery(''));
dispatch(setTopicFilter(''));
dispatch(setUsernameSearch(''));
};
previousSearchValueRef.current = '';
}, [previousSearchValueRef]);
const onChange = (query) => {
searchValue = query;
};
const onChange = useCallback((query) => {
setSearchValue(query);
}, []);
const onSubmit = (query) => {
if (query === '') {
const onSubmit = useCallback((query) => {
if (query === '' || query === previousSearchValueRef.current) {
return;
}
if (isPostSearch) {
dispatch(setSearchQuery(query));
} else if (page === 'topics') {
@@ -52,35 +59,37 @@ function Search({ intl }) {
} else if (page === 'learners') {
dispatch(setUsernameSearch(query));
}
};
previousSearchValueRef.current = query;
}, [page, searchValue, previousSearchValueRef]);
const handleIconClick = useCallback((e) => {
e.preventDefault();
onSubmit(searchValue);
}, [searchValue]);
useEffect(() => onClear(), [page]);
return (
<>
<SearchField.Advanced
onClear={onClear}
onChange={onChange}
onSubmit={onSubmit}
value={currentValue}
>
<SearchField.Label />
<SearchField.Input
style={{ paddingRight: '1rem' }}
placeholder={intl.formatMessage(postsMessages.search, { page: camelCase(page) })}
/>
<span className="mt-auto mb-auto mr-2.5 pointer-cursor-hover">
<Icon
src={SearchIcon}
onClick={() => onSubmit(searchValue)}
/>
</span>
</SearchField.Advanced>
</>
);
}
Search.propTypes = {
intl: intlShape.isRequired,
return (
<SearchField.Advanced
onClear={onClear}
onChange={onChange}
onSubmit={onSubmit}
value={currentValue}
>
<SearchField.Label />
<SearchField.Input
style={{ paddingRight: '1rem' }}
placeholder={intl.formatMessage(postsMessages.search, { page: camelCase(page) })}
/>
<span className="py-auto px-2.5 pointer-cursor-hover">
<Icon
src={SearchIcon}
onClick={handleIconClick}
data-testid="search-icon"
/>
</span>
</SearchField.Advanced>
);
};
export default injectIntl(Search);
export default React.memo(Search);

View File

@@ -1,32 +1,37 @@
import React from 'react';
import PropTypes from 'prop-types';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Button, Icon } from '@edx/paragon';
import { Search } from '@edx/paragon/icons';
import { Button, Icon } from '@openedx/paragon';
import { Search } from '@openedx/paragon/icons';
import { useIntl } from '@edx/frontend-platform/i18n';
import { RequestStatus } from '../data/constants';
import messages from '../discussions/posts/post-actions-bar/messages';
function SearchInfo({
intl,
const SearchInfo = ({
count,
text,
loadingStatus,
onClear,
textSearchRewrite,
}) {
}) => {
const intl = useIntl();
return (
<div className="d-flex flex-row border-bottom border-light-400">
<Icon src={Search} className="justify-content-start ml-3.5 mr-2 mb-2 mt-2.5" />
<Button variant="" size="inline" className="text-justify p-2">
{loadingStatus === RequestStatus.SUCCESSFUL && (
textSearchRewrite ? intl.formatMessage(messages.searchRewriteInfo, {
searchString: text,
count,
textSearchRewrite,
})
: intl.formatMessage(messages.searchInfo, { count, text })
textSearchRewrite ? (
intl.formatMessage(messages.searchRewriteInfo, {
searchString: text,
count,
textSearchRewrite,
})
) : (
intl.formatMessage(messages.searchInfo, { count, text })
)
)}
{loadingStatus !== RequestStatus.SUCCESSFUL && intl.formatMessage(messages.searchInfoSearching)}
</Button>
@@ -35,10 +40,9 @@ function SearchInfo({
</Button>
</div>
);
}
};
SearchInfo.propTypes = {
intl: intlShape.isRequired,
count: PropTypes.number.isRequired,
text: PropTypes.string.isRequired,
loadingStatus: PropTypes.string.isRequired,
@@ -51,4 +55,4 @@ SearchInfo.defaultProps = {
textSearchRewrite: null,
};
export default injectIntl(SearchInfo);
export default React.memo(SearchInfo);

View File

@@ -0,0 +1,11 @@
import React from 'react';
import { Spinner as ParagonSpinner } from '@openedx/paragon';
const Spinner = () => (
<div className="spinner-container" data-testid="spinner">
<ParagonSpinner animation="border" variant="primary" size="lg" />
</div>
);
export default React.memo(Spinner);

View File

@@ -1,13 +1,13 @@
import React, { useState } from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import { ActionRow, AlertModal, Button } from '@openedx/paragon';
import { Editor } from '@tinymce/tinymce-react';
import { useParams } from 'react-router';
import { useLocation, useParams } from 'react-router-dom';
// TinyMCE so the global var exists
// eslint-disable-next-line no-unused-vars,import/no-extraneous-dependencies
/* eslint-disable-next-line @typescript-eslint/no-unused-vars, import/no-extraneous-dependencies */
import tinymce from 'tinymce/tinymce';
import { useIntl } from '@edx/frontend-platform/i18n';
import { ActionRow, AlertModal, Button } from '@edx/paragon';
import { MAX_UPLOAD_FILE_SIZE } from '../data/constants';
import messages from '../discussions/messages';
@@ -42,30 +42,32 @@ import contentCss from '!!raw-loader!tinymce/skins/content/default/content.min.c
import contentUiCss from '!!raw-loader!tinymce/skins/ui/oxide/content.min.css';
/* istanbul ignore next */
const setup = (editor) => {
editor.ui.registry.addButton('openedx_code', {
icon: 'sourcecode',
onAction: () => {
editor.execCommand('CodeSample');
},
});
editor.ui.registry.addButton('openedx_html', {
text: 'HTML',
onAction: () => {
editor.execCommand('mceCodeEditor');
},
});
};
/* istanbul ignore next */
export default function TinyMCEEditor(props) {
const TinyMCEEditor = (props) => {
// note that skin and content_css is disabled to avoid the normal
// loading process and is instead loaded as a string via content_style
const locationObj = useLocation();
const { courseId, postId } = useParams();
const [showImageWarning, setShowImageWarning] = useState(false);
const intl = useIntl();
const uploadHandler = async (blobInfo, success, failure) => {
const enableInContextSidebar = Boolean(new URLSearchParams(locationObj.search).get('inContextSidebar') !== null);
/* istanbul ignore next */
const setup = useCallback((editor) => {
editor.ui.registry.addButton('openedx_code', {
icon: 'sourcecode',
onAction: () => {
editor.execCommand('CodeSample');
},
});
editor.ui.registry.addButton('openedx_html', {
text: 'HTML',
onAction: () => {
editor.execCommand('mceCodeEditor');
},
});
}, []);
const uploadHandler = useCallback(async (blobInfo, success, failure) => {
try {
const blob = blobInfo.blob();
const imageSize = blobInfo.blob().size / 1024;
@@ -76,7 +78,7 @@ export default function TinyMCEEditor(props) {
const filename = blobInfo.filename();
const { location } = await uploadFile(blob, filename, courseId, postId || 'root');
const img = new Image();
img.onload = function () {
img.onload = () => {
if (img.height > 999 || img.width > 999) { setShowImageWarning(true); }
};
img.src = location;
@@ -84,7 +86,11 @@ export default function TinyMCEEditor(props) {
} catch (e) {
failure(e.toString(), { remove: true });
}
};
}, [courseId, postId]);
const handleClose = useCallback(() => {
setShowImageWarning(false);
}, []);
let contentStyle;
// In the test environment this causes an error so set styles to empty since they aren't needed for testing.
@@ -94,6 +100,29 @@ export default function TinyMCEEditor(props) {
contentStyle = '';
}
// eslint-disable-next-line consistent-return
useEffect(() => {
if (enableInContextSidebar) {
const checkToxDialogVisibility = () => {
const toxDialog = document.querySelector('.tox-dialog');
if (toxDialog) {
toxDialog.style.alignSelf = 'start';
toxDialog.style.marginTop = '50px';
}
};
const observer = new MutationObserver(checkToxDialogVisibility);
// Observe changes to the entire document
observer.observe(document, { childList: true, subtree: true });
// Clean up the observer when the component unmounts
return () => {
observer.disconnect();
};
}
}, [enableInContextSidebar]);
return (
<>
<Editor
@@ -131,21 +160,22 @@ export default function TinyMCEEditor(props) {
<AlertModal
title={intl.formatMessage(messages.imageWarningModalTitle)}
isOpen={showImageWarning}
onClose={() => setShowImageWarning(false)}
onClose={handleClose}
isBlocking
footerNode={(
<ActionRow>
<Button variant="danger" onClick={() => setShowImageWarning(false)}>
<Button variant="danger" onClick={handleClose}>
{intl.formatMessage(messages.imageWarningDismissButton)}
</Button>
</ActionRow>
)}
)}
>
<p>
{intl.formatMessage(messages.imageWarningMessage)}
</p>
</AlertModal>
</>
);
}
};
export default React.memo(TinyMCEEditor);

View File

@@ -2,11 +2,11 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Icon, OverlayTrigger, Tooltip } from '@openedx/paragon';
import { HelpOutline, PostOutline, Report } from '@openedx/paragon/icons';
import { useSelector } from 'react-redux';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Icon, OverlayTrigger, Tooltip } from '@edx/paragon';
import { HelpOutline, PostOutline, Report } from '@edx/paragon/icons';
import { useIntl } from '@edx/frontend-platform/i18n';
import {
selectUserHasModerationPrivileges,
@@ -14,20 +14,23 @@ import {
} from '../discussions/data/selectors';
import messages from '../discussions/in-context-topics/messages';
function TopicStats({
const TopicStats = ({
threadCounts,
activeFlags,
inactiveFlags,
intl,
}) {
}) => {
const intl = useIntl();
const userHasModerationPrivileges = useSelector(selectUserHasModerationPrivileges);
const userIsGroupTa = useSelector(selectUserIsGroupTa);
const canSeeReportedStats = (activeFlags || inactiveFlags) && (userHasModerationPrivileges || userIsGroupTa);
return (
<div className="d-flex align-items-center mt-2.5" style={{ marginBottom: '2px' }}>
<OverlayTrigger
id="discussion-topic-stats"
placement="right"
overlay={(
<Tooltip>
<Tooltip id="discussion-topic-stats">
<div className="d-flex flex-column align-items-start">
{intl.formatMessage(messages.discussions, {
count: threadCounts?.discussion || 0,
@@ -42,8 +45,10 @@ function TopicStats({
</div>
</OverlayTrigger>
<OverlayTrigger
id="question-topic-stats"
placement="right"
overlay={(
<Tooltip>
<Tooltip id="question-topic-stats">
<div className="d-flex flex-column align-items-start">
{intl.formatMessage(messages.questions, {
count: threadCounts?.question || 0,
@@ -59,8 +64,10 @@ function TopicStats({
</OverlayTrigger>
{Boolean(canSeeReportedStats) && (
<OverlayTrigger
id="reported-topic-stats"
placement="right"
overlay={(
<Tooltip>
<Tooltip id="reported-topic-stats">
<div className="d-flex flex-column align-items-start">
{Boolean(activeFlags) && (
<span>
@@ -84,7 +91,7 @@ function TopicStats({
)}
</div>
);
}
};
TopicStats.propTypes = {
threadCounts: PropTypes.shape({
@@ -93,7 +100,6 @@ TopicStats.propTypes = {
}),
activeFlags: PropTypes.number,
inactiveFlags: PropTypes.number,
intl: intlShape.isRequired,
};
TopicStats.defaultProps = {
@@ -105,4 +111,4 @@ TopicStats.defaultProps = {
inactiveFlags: null,
};
export default injectIntl(TopicStats);
export default React.memo(TopicStats);

View File

@@ -1,20 +0,0 @@
import React from 'react';
export default function InsertLink() {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="none"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
fillRule="evenodd"
d="M3.9 12c0-1.71 1.39-3.1 3.1-3.1h4V7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-1.9H7c-1.71 0-3.1-1.39-3.1-3.1zM8 13h8v-2H8v2zm9-6h-4v1.9h4c1.71 0 3.1 1.39 3.1 3.1s-1.39 3.1-3.1 3.1h-4V17h4c2.76 0 5-2.24 5-5s-2.24-5-5-5z"
clipRule="evenodd"
/>
</svg>
);
}

View File

@@ -1,26 +0,0 @@
import React from 'react';
export default function Issue() {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="28"
height="28"
fill="none"
viewBox="0 0 28 28"
>
<path
fill="#F2F0EF"
d="M0 14C0 6.268 6.268 0 14 0s14 6.268 14 14-6.268 14-14 14S0 21.732 0 14z"
/>
<path
fill="#2D494E"
d="M14 2.333C7.56 2.333 2.333 7.56 2.333 14c0 6.44 5.227 11.667 11.667 11.667 6.44 0 11.667-5.227 11.667-11.667C25.667 7.56 20.44 2.334 14 2.334z"
/>
<path
fill="#fff"
d="M12.833 22.167h2.334v-2.334h-2.334v2.334zM16.532 14.198l1.05-1.073a3.713 3.713 0 001.085-2.625A4.665 4.665 0 0014 5.833 4.665 4.665 0 009.333 10.5h2.334A2.34 2.34 0 0114 8.167a2.34 2.34 0 012.333 2.333c0 .642-.256 1.225-.688 1.645l-1.447 1.47a4.696 4.696 0 00-1.365 3.302v.583h2.334c0-1.75.525-2.45 1.365-3.302z"
/>
</svg>
);
}

View File

@@ -1,18 +0,0 @@
import React from 'react';
export default function People() {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
fill="none"
viewBox="0 0 16 16"
>
<path
fill="#707070"
d="M11.072 7.332a1.992 1.992 0 001.993-2 1.997 1.997 0 10-3.993 0c0 1.107.893 2 2 2zm-5.334 0a1.992 1.992 0 001.994-2 1.997 1.997 0 10-3.993 0c0 1.107.893 2 2 2zm0 1.333c-1.553 0-4.666.78-4.666 2.334v1.666h9.333V11c0-1.554-3.113-2.334-4.667-2.334zm5.334 0c-.194 0-.414.014-.647.034.773.56 1.313 1.313 1.313 2.3v1.666h4V11c0-1.554-3.113-2.334-4.666-2.334z"
/>
</svg>
);
}

View File

@@ -1,20 +0,0 @@
import React from 'react';
export default function PushPin() {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="none"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
fillRule="evenodd"
d="M16 9V4H18V2H6V4H8V9C8 10.66 6.66 12 5 12V14H10.97V21L11.97 22L12.97 21V14H19V12C17.34 12 16 10.66 16 9Z"
clipRule="evenodd"
/>
</svg>
);
}

View File

@@ -1,26 +0,0 @@
import React from 'react';
export default function Question() {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="28"
height="28"
fill="none"
viewBox="0 0 28 28"
>
<path
fill="#fff"
d="M0 14.001c0-7.732 6.268-14 14-14s14 6.268 14 14-6.268 14-14 14-14-6.268-14-14z"
/>
<path
fill="#2D494E"
d="M14 2.334c-6.44 0-11.667 5.227-11.667 11.667 0 6.44 5.227 11.667 11.667 11.667 6.44 0 11.666-5.227 11.666-11.667 0-6.44-5.226-11.667-11.666-11.667z"
/>
<path
fill="#fff"
d="M12.833 22.168h2.333v-2.334h-2.333v2.334zM16.531 14.2l1.05-1.074a3.712 3.712 0 001.085-2.625A4.665 4.665 0 0014 5.834a4.665 4.665 0 00-4.667 4.667h2.333A2.34 2.34 0 0114 8.168a2.34 2.34 0 012.333 2.333c0 .642-.257 1.225-.688 1.645l-1.447 1.47a4.696 4.696 0 00-1.365 3.302v.583h2.333c0-1.75.525-2.45 1.365-3.302z"
/>
</svg>
);
}

View File

@@ -1,18 +0,0 @@
import React from 'react';
export default function QuestionAnswer() {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="21"
height="20"
fill="none"
viewBox="0 0 21 20"
>
<path
fill="currentColor"
d="M18.737 5h-2.5v7.5H5.404V15h10l3.333 3.333V5zm-4.166 5.833V1.667H2.07v12.5l3.333-3.334h9.166z"
/>
</svg>
);
}

View File

@@ -1,18 +0,0 @@
import React from 'react';
export default function QuestionAnswerOutline() {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
fill="none"
viewBox="0 0 20 20"
>
<path
fill="currentColor"
d="M12.6 3.267v6.067H4.08l-.512.512-.502.502v-7.08H12.6zm.867-1.733H2.198a.87.87 0 00-.867.867v12.134L4.8 11.068h8.668a.87.87 0 00.866-.867v-7.8a.87.87 0 00-.867-.867zM17.8 5h-1.733v7.8H4.799v1.734c0 .476.39.867.867.867H15.2l3.467 3.466v-13A.87.87 0 0017.8 5z"
/>
</svg>
);
}

View File

@@ -1,18 +0,0 @@
import React from 'react';
export default function StarFilled() {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="21"
height="20"
fill="none"
viewBox="0 0 21 20"
>
<path
fill="currentColor"
d="M10.404 14.392l5.15 3.108-1.367-5.858 4.55-3.942-5.991-.508-2.342-5.525-2.342 5.525L2.07 7.7l4.55 3.942L5.254 17.5l5.15-3.108z"
/>
</svg>
);
}

View File

@@ -1,18 +0,0 @@
import React from 'react';
export default function StarOutline() {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
fill="none"
viewBox="0 0 20 20"
>
<path
fill="currentColor"
d="M18.737 7.7l-5.991-.517-2.342-5.516-2.342 5.525L2.07 7.7l4.55 3.942L5.254 17.5l5.15-3.108 5.15 3.108-1.359-5.858L18.737 7.7zm-8.333 5.133L7.27 14.725l.834-3.567-2.767-2.4 3.65-.316 1.417-3.359 1.425 3.367 3.65.317-2.767 2.4.834 3.566-3.142-1.9z"
/>
</svg>
);
}

View File

@@ -1,18 +0,0 @@
import React from 'react';
export default function ThumbUpFilled() {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="21"
height="20"
fill="none"
viewBox="0 0 21 20"
>
<path
fill="currentColor"
d="M12.212.833L6.237 6.817V17.5h10.258l3.075-7.167V6.667h-6.925l.934-4.484-1.367-1.35zM1.237 7.5H4.57v10H1.237v-10z"
/>
</svg>
);
}

View File

@@ -1,21 +0,0 @@
import React from 'react';
export default function ThumbUpOutline() {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
fill="none"
viewBox="0 0 20 20"
>
<path
fill="currentColor"
fillRule="evenodd"
d="M19.57 6.667v3.666L16.495 17.5H6.238V6.817L12.212.833l1.367 1.35-.934 4.484h6.925zm-11.666.841v8.325h7.492l2.508-5.841V8.333h-7.309l.925-4.45-3.616 3.625z"
clipRule="evenodd"
/>
<path fill="currentColor" d="M4.57 17.5H1.237v-10H4.57v10z" />
</svg>
);
}

View File

@@ -1,11 +0,0 @@
export { default as InsertLink } from './InsertLink';
export { default as Issue } from './Issue';
export { default as People } from './People';
export { default as PushPin } from './PushPin';
export { default as Question } from './Question';
export { default as QuestionAnswer } from './QuestionAnswer';
export { default as QuestionAnswerOutline } from './QuestionAnswerOutline';
export { default as StarFilled } from './StarFilled';
export { default as StarOutline } from './StarOutline';
export { default as ThumbUpFilled } from './ThumbUpFilled';
export { default as ThumbUpOutline } from './ThumbUpOutline';

View File

@@ -1,4 +1,5 @@
export { default as PostActionsBar } from '../discussions/posts/post-actions-bar/PostActionsBar';
export { default as Search } from './Search';
export { default as Spinner } from './Spinner';
export { default as TinyMCEEditor } from './TinyMCEEditor';
export { default as TopicStats } from './TopicStats';

View File

@@ -1,7 +1,5 @@
/* eslint-disable import/prefer-default-export */
// Course Blocks API response for the demo course.
export const getBlocksAPIResponse = (newProvider = false) => {
const getBlocksAPIResponse = (newProvider = false) => {
const response = {
root: 'block-v1:edX+DemoX+Demo_Course+type@course+block@course',
blocks: {
@@ -936,3 +934,5 @@ export const getBlocksAPIResponse = (newProvider = false) => {
}
return response;
};
export default getBlocksAPIResponse;

View File

@@ -1 +0,0 @@
export * from './blocks';

View File

@@ -1,4 +1,3 @@
/* eslint-disable import/prefer-default-export */
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { getApiBaseUrl } from './constants';

View File

@@ -1,6 +1,9 @@
import { getConfig } from '@edx/frontend-platform';
export const getApiBaseUrl = () => getConfig().LMS_BASE_URL;
export const getFullUrl = (path) => (
new URL(`${getConfig().PUBLIC_PATH.replace(/\/$/, '')}/${path}`, window.location.origin).href
);
/**
* Enum for thread types.
@@ -77,6 +80,7 @@ export const RequestStatus = {
*/
export const AvatarOutlineAndLabelColors = {
Staff: 'staff-color',
Moderator: 'TA-color',
'Community TA': 'TA-color',
};
@@ -144,18 +148,17 @@ export const Routes = {
PATH: BASE_PATH,
},
LEARNERS: {
PATH: `${BASE_PATH}/learners`,
POSTS: `${BASE_PATH}/learners/:learnerUsername/posts(/:postId)?`,
PATH: `${BASE_PATH}/learners/:learnerUsername?`,
POSTS: `${BASE_PATH}/learners/:learnerUsername/posts/:postId?`,
POSTS_EDIT: `${BASE_PATH}/learners/:learnerUsername/posts/:postId/edit`,
},
POSTS: {
PATH: `${BASE_PATH}/topics/:topicId`,
MY_POSTS: `${BASE_PATH}/my-posts(/:postId)?`,
ALL_POSTS: `${BASE_PATH}/posts(/:postId)?`,
NEW_POST: [
`${BASE_PATH}/topics/:topicId/posts/:postId`,
`${BASE_PATH}/topics/:topicId`,
`${BASE_PATH}`,
],
MY_POSTS: `${BASE_PATH}/my-posts/:postId?`,
ALL_POSTS: `${BASE_PATH}/posts/:postId?`,
EDIT_MY_POSTS: `${BASE_PATH}/my-posts/:postId/edit`,
EDIT_ALL_POSTS: `${BASE_PATH}/posts/:postId/edit`,
NEW_POST: `${BASE_PATH}/*`,
EDIT_POST: [
`${BASE_PATH}/category/:category/posts/:postId/edit`,
`${BASE_PATH}/topics/:topicId/posts/:postId/edit`,
@@ -166,19 +169,19 @@ export const Routes = {
},
COMMENTS: {
PATH: [
`${BASE_PATH}/category/:category/posts/:postId`,
`${BASE_PATH}/topics/:topicId/posts/:postId`,
`${BASE_PATH}/category/:category/posts/:postId?`,
`${BASE_PATH}/topics/:topicId/posts/:postId?`,
`${BASE_PATH}/posts/:postId`,
`${BASE_PATH}/my-posts/:postId`,
`${BASE_PATH}/learners/:learnerUsername/posts/:postId`,
`${BASE_PATH}/learners/:learnerUsername/posts/:postId?`,
],
PAGE: `${BASE_PATH}/:page`,
PAGE: `${BASE_PATH}/:page/*`,
PAGES: {
category: `${BASE_PATH}/category/:category/posts/:postId`,
topics: `${BASE_PATH}/topics/:topicId/posts/:postId`,
category: `${BASE_PATH}/category/:category/posts/:postId?`,
topics: `${BASE_PATH}/topics/:topicId/posts/:postId?`,
posts: `${BASE_PATH}/posts/:postId`,
'my-posts': `${BASE_PATH}/my-posts/:postId`,
learners: `${BASE_PATH}/learners/:learnerUsername/posts/:postId`,
learners: `${BASE_PATH}/learners/:learnerUsername/posts/:postId?`,
},
},
TOPICS: {
@@ -189,9 +192,10 @@ export const Routes = {
],
ALL: `${BASE_PATH}/topics`,
CATEGORY: `${BASE_PATH}/category/:category`,
CATEGORY_POST: `${BASE_PATH}/category/:category/posts/:postId`,
CATEGORY_POST: `${BASE_PATH}/category/:category/posts/:postId?`,
CATEGORY_POST_EDIT: `${BASE_PATH}/category/:category/posts/:postId/edit`,
TOPIC: `${BASE_PATH}/topics/:topicId`,
TOPIC_POST: `${BASE_PATH}/topics/:topicId/posts/:postId`,
TOPIC_POST: `${BASE_PATH}/topics/:topicId/posts/:postId?`,
TOPIC_POST_EDIT: `${BASE_PATH}/topics/:topicId/posts/:postId/edit`,
},
};
@@ -205,11 +209,12 @@ export const PostsPages = {
};
export const ALL_ROUTES = []
.concat([Routes.TOPICS.CATEGORY_POST, Routes.TOPICS.CATEGORY])
.concat([Routes.TOPICS.CATEGORY_POST, `${Routes.TOPICS.CATEGORY}?`])
.concat(Routes.COMMENTS.PATH)
.concat(Routes.TOPICS.PATH)
.concat(Routes.POSTS.EDIT_POST)
.concat([Routes.POSTS.ALL_POSTS, Routes.POSTS.MY_POSTS])
.concat([Routes.LEARNERS.POSTS, Routes.LEARNERS.PATH])
.concat([Routes.DISCUSSIONS.PATH]);
.concat([`${Routes.DISCUSSIONS.PATH}/*`]);
export const MAX_UPLOAD_FILE_SIZE = 1024;

View File

@@ -1,4 +1,3 @@
/* eslint-disable import/prefer-default-export */
import { useState } from 'react';
import { useDispatch } from 'react-redux';
@@ -15,14 +14,16 @@ import { useDispatch } from 'react-redux';
*
* @return {(boolean|(function(*=): Promise<void>)|*)[]}
*/
export function useDispatchWithState() {
export default function useDispatchWithState() {
const dispatch = useDispatch();
const [isDispatching, setDispatching] = useState(false);
const dispatchWithState = async (thunk) => {
setDispatching(true);
await dispatch(thunk);
setDispatching(false);
};
return [
isDispatching,
dispatchWithState,

View File

@@ -5,11 +5,11 @@ import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { initializeMockApp } from '@edx/frontend-platform/testing';
import { initializeStore } from '../store';
import { executeThunk } from '../test-utils';
import { getBlocksAPIResponse } from './__factories__';
import executeThunk from '../test-utils';
import getBlocksAPIResponse from './__factories__/blocks';
import { getBlocksAPIURL } from './api';
import { RequestStatus } from './constants';
import { fetchCourseBlocks } from './thunks';
import fetchCourseBlocks from './thunks';
const blocksAPIURL = getBlocksAPIURL();
const courseId = 'course-v1:edX+DemoX+Demo_Course';

View File

@@ -1,5 +1,3 @@
/* eslint-disable import/prefer-default-export */
import { createSelector } from '@reduxjs/toolkit';
import { selectDiscussionProvider, selectGroupAtSubsection } from '../discussions/data/selectors';

View File

@@ -1,4 +1,3 @@
/* eslint-disable no-param-reassign,import/prefer-default-export */
import { createSlice } from '@reduxjs/toolkit';
import { RequestStatus } from './constants';
@@ -16,19 +15,33 @@ const blocksSlice = createSlice({
blocks: {},
},
reducers: {
fetchCourseBlocksRequest: (state) => {
state.status = RequestStatus.IN_PROGRESS;
},
fetchCourseBlocksSuccess: (state, { payload }) => {
state.status = RequestStatus.SUCCESSFUL;
Object.assign(state, payload);
},
fetchCourseBlocksFailed: (state) => {
state.status = RequestStatus.FAILED;
},
fetchCourseBlocksDenied: (state) => {
state.status = RequestStatus.DENIED;
},
fetchCourseBlocksRequest: (state) => (
{
...state,
status: RequestStatus.IN_PROGRESS,
}
),
fetchCourseBlocksSuccess: (state, { payload }) => (
{
...state,
status: RequestStatus.SUCCESSFUL,
topics: payload.topics,
chapters: payload.chapters,
blocks: payload.blocks,
}
),
fetchCourseBlocksFailed: (state) => (
{
...state,
status: RequestStatus.FAILED,
}
),
fetchCourseBlocksDenied: (state) => (
{
...state,
status: RequestStatus.DENIED,
}
),
},
});

View File

@@ -1,4 +1,3 @@
/* eslint-disable import/prefer-default-export, no-unused-expressions */
import { camelCaseObject } from '@edx/frontend-platform';
import { logError } from '@edx/frontend-platform/logging';
@@ -88,7 +87,7 @@ function normaliseCourseBlocks({
};
}
export function fetchCourseBlocks(courseId, username) {
export default function fetchCourseBlocks(courseId, username) {
return async (dispatch) => {
try {
dispatch(fetchCourseBlocksRequest({ courseId }));

View File

@@ -1,4 +1,3 @@
/* eslint-disable import/prefer-default-export */
import { ensureConfig, getConfig, snakeCaseObject } from '@edx/frontend-platform';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';

View File

@@ -5,9 +5,9 @@ import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { initializeMockApp } from '@edx/frontend-platform/testing';
import { initializeStore } from '../../../store';
import { executeThunk } from '../../../test-utils';
import executeThunk from '../../../test-utils';
import { getCohortsApiUrl } from './api';
import { fetchCourseCohorts } from './thunks';
import fetchCourseCohorts from './thunks';
import './__factories__';

View File

@@ -1,3 +1,3 @@
/* eslint-disable import/prefer-default-export */
const selectCourseCohorts = state => state.cohorts.cohorts;
export const selectCourseCohorts = state => state.cohorts.cohorts;
export default selectCourseCohorts;

View File

@@ -1,4 +1,3 @@
/* eslint-disable no-param-reassign,import/prefer-default-export */
import { createSlice } from '@reduxjs/toolkit';
import { RequestStatus } from '../../../data/constants';
@@ -10,17 +9,26 @@ const cohortsSlice = createSlice({
cohorts: [],
},
reducers: {
fetchCohortsRequest: (state) => {
state.status = RequestStatus.IN_PROGRESS;
state.cohorts = [];
},
fetchCohortsSuccess: (state, { payload }) => {
state.status = RequestStatus.SUCCESSFUL;
state.cohorts = payload;
},
fetchCohortsFailed: (state) => {
state.status = RequestStatus.FAILED;
},
fetchCohortsRequest: (state) => (
{
...state,
status: RequestStatus.IN_PROGRESS,
cohorts: [],
}
),
fetchCohortsSuccess: (state, { payload }) => (
{
...state,
status: RequestStatus.SUCCESSFUL,
cohorts: payload,
}
),
fetchCohortsFailed: (state) => (
{
...state,
status: RequestStatus.FAILED,
}
),
},
});

View File

@@ -1,4 +1,3 @@
/* eslint-disable import/prefer-default-export */
import { camelCaseObject } from '@edx/frontend-platform';
import { logError } from '@edx/frontend-platform/logging';
@@ -11,7 +10,7 @@ import {
fetchCohortsSuccess,
} from './slices';
export function fetchCourseCohorts(courseId) {
export default function fetchCourseCohorts(courseId) {
return async (dispatch) => {
try {
dispatch(fetchCohortsRequest());

View File

@@ -1,104 +1,134 @@
import React, { useContext, useState } from 'react';
import React, {
useCallback, useMemo, useRef, useState,
} from 'react';
import PropTypes from 'prop-types';
import { useSelector } from 'react-redux';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { logError } from '@edx/frontend-platform/logging';
import {
Button, Dropdown, Icon, IconButton, ModalPopup, useToggle,
} from '@edx/paragon';
import { MoreHoriz } from '@edx/paragon/icons';
} from '@openedx/paragon';
import { MoreHoriz } from '@openedx/paragon/icons';
import { useSelector } from 'react-redux';
import { useIntl } from '@edx/frontend-platform/i18n';
import { logError } from '@edx/frontend-platform/logging';
import { ContentActions } from '../../data/constants';
import { selectBlackoutDate } from '../data/selectors';
import { selectIsPostingEnabled } from '../data/selectors';
import messages from '../messages';
import { commentShape } from '../post-comments/comments/comment/proptypes';
import { postShape } from '../posts/post/proptypes';
import { inBlackoutDateRange, useActions } from '../utils';
import { DiscussionContext } from './context';
import { useActions } from '../utils';
function ActionsDropdown({
intl,
commentOrPost,
disabled,
const ActionsDropdown = ({
actionHandlers,
iconSize,
contentType,
disabled,
dropDownIconSize,
}) {
iconSize,
id,
}) => {
const buttonRef = useRef();
const intl = useIntl();
const [isOpen, open, close] = useToggle(false);
const [target, setTarget] = useState(null);
const actions = useActions(commentOrPost);
const { enableInContextSidebar } = useContext(DiscussionContext);
const handleActions = (action) => {
const isPostingEnabled = useSelector(selectIsPostingEnabled);
const actions = useActions(contentType, id);
// Check if we're in in-context sidebar mode
const isInContextSidebar = useMemo(() => (
typeof window !== 'undefined' && window.location.search.includes('inContextSidebar')
), []);
const handleActions = useCallback((action) => {
const actionFunction = actionHandlers[action];
if (actionFunction) {
actionFunction();
} else {
logError(`Unknown or unimplemented action ${action}`);
}
};
const blackoutDateRange = useSelector(selectBlackoutDate);
// Find and remove edit action if in blackout date range.
if (inBlackoutDateRange(blackoutDateRange)) {
actions.splice(actions.findIndex(action => action.id === 'edit'), 1);
}
}, [actionHandlers]);
// Find and remove edit action if in Posting is disabled.
useMemo(() => {
if (!isPostingEnabled) {
actions.splice(actions.findIndex(action => action.id === 'edit'), 1);
}
}, [actions, isPostingEnabled]);
const onClickButton = useCallback((event) => {
event.preventDefault();
setTarget(buttonRef.current);
open();
}, [open]);
const onCloseModal = useCallback(() => {
close();
setTarget(null);
}, [close]);
const dropdownContent = (
<div
className="bg-white shadow d-flex flex-column mt-1"
data-testid="actions-dropdown-modal-popup"
>
{actions.map(action => (
<React.Fragment key={action.id}>
{(action.action === ContentActions.DELETE) && <Dropdown.Divider />}
<Dropdown.Item
as={Button}
variant="tertiary"
size="inline"
onClick={() => {
close();
handleActions(action.action);
}}
className="d-flex justify-content-start actions-dropdown-item"
data-testId={action.id}
>
<Icon
src={action.icon}
className="icon-size-24"
/>
<span className="font-weight-normal ml-2">
{intl.formatMessage(action.label)}
</span>
</Dropdown.Item>
</React.Fragment>
))}
</div>
);
return (
<>
<IconButton
onClick={open}
onClick={onClickButton}
alt={intl.formatMessage(messages.actionsAlt)}
src={MoreHoriz}
iconAs={Icon}
disabled={disabled}
size={iconSize}
ref={setTarget}
iconClassNames={dropDownIconSize ? 'dropdown-icon-dimentions' : ''}
ref={buttonRef}
iconClassNames={dropDownIconSize ? 'dropdown-icon-dimensions' : ''}
/>
<div className="actions-dropdown">
<div className={`actions-dropdown ${isInContextSidebar ? 'in-context-sidebar' : ''}`}>
<ModalPopup
onClose={close}
onClose={onCloseModal}
positionRef={target}
isOpen={isOpen}
placement={enableInContextSidebar ? 'left' : 'auto-start'}
placement="bottom-end"
>
<div
className="bg-white p-1 shadow d-flex flex-column"
data-testid="actions-dropdown-modal-popup"
>
{actions.map(action => (
<React.Fragment key={action.id}>
{(action.action === ContentActions.DELETE)
&& <Dropdown.Divider />}
<Dropdown.Item
as={Button}
variant="tertiary"
size="inline"
onClick={() => {
close();
handleActions(action.action);
}}
className="d-flex justify-content-start py-1.5 mr-4"
>
<Icon src={action.icon} className="mr-1" /> {intl.formatMessage(action.label)}
</Dropdown.Item>
</React.Fragment>
))}
</div>
{dropdownContent}
</ModalPopup>
</div>
</>
);
}
};
ActionsDropdown.propTypes = {
intl: intlShape.isRequired,
commentOrPost: PropTypes.oneOfType([commentShape, postShape]).isRequired,
id: PropTypes.string.isRequired,
disabled: PropTypes.bool,
actionHandlers: PropTypes.objectOf(PropTypes.func).isRequired,
iconSize: PropTypes.string,
dropDownIconSize: PropTypes.bool,
contentType: PropTypes.oneOf(['POST', 'COMMENT']).isRequired,
};
ActionsDropdown.defaultProps = {
@@ -107,4 +137,4 @@ ActionsDropdown.defaultProps = {
dropDownIconSize: false,
};
export default injectIntl(ActionsDropdown);
export default ActionsDropdown;

View File

@@ -1,117 +1,178 @@
import {
fireEvent, render, screen, waitFor,
} from '@testing-library/react';
import MockAdapter from 'axios-mock-adapter';
import { act } from 'react-dom/test-utils';
import { IntlProvider } from 'react-intl';
import { Factory } from 'rosie';
import { camelCaseObject, initializeMockApp, snakeCaseObject } from '@edx/frontend-platform';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { logError } from '@edx/frontend-platform/logging';
import { AppProvider } from '@edx/frontend-platform/react';
import { ContentActions } from '../../data/constants';
import { initializeStore } from '../../store';
import executeThunk from '../../test-utils';
import { getCourseConfigApiUrl } from '../data/api';
import fetchCourseConfig from '../data/thunks';
import messages from '../messages';
import { getCommentsApiUrl } from '../post-comments/data/api';
import { addComment, fetchThreadComments } from '../post-comments/data/thunks';
import PostCommentsContext from '../post-comments/postCommentsContext';
import { getThreadsApiUrl } from '../posts/data/api';
import { fetchThread } from '../posts/data/thunks';
import { ACTIONS_LIST } from '../utils';
import ActionsDropdown from './ActionsDropdown';
import '../post-comments/data/__factories__';
import '../posts/data/__factories__';
jest.mock('@edx/frontend-platform/logging', () => ({
...jest.requireActual('@edx/frontend-platform/logging'),
logError: jest.fn(),
}));
let store;
let axiosMock;
const commentsApiUrl = getCommentsApiUrl();
const threadsApiUrl = getThreadsApiUrl();
const courseId = 'course-v1:edX+TestX+Test_Course';
const discussionThreadId = 'thread-1';
const questionThreadId = 'thread-2';
const commentContent = 'This is a comment for thread-1';
let discussionThread;
let questionThread;
let comment;
function buildTestContent(buildParams, testMeta) {
const buildTestContent = (buildParams, testMeta) => {
const buildParamsSnakeCase = snakeCaseObject(buildParams);
return [
{
testFor: 'comments',
...camelCaseObject(Factory.build('comment', { ...buildParamsSnakeCase }, null)),
...testMeta,
},
{
testFor: 'question threads',
...camelCaseObject(Factory.build('thread', { ...buildParamsSnakeCase, type: 'question' }, null)),
...testMeta,
},
{
discussionThread = Factory.build('thread', { ...buildParamsSnakeCase, id: discussionThreadId }, null);
questionThread = Factory.build('thread', { ...buildParamsSnakeCase, id: questionThreadId }, null);
comment = Factory.build('comment', { ...buildParamsSnakeCase, thread_id: discussionThreadId }, null);
return {
discussion: {
testFor: 'discussion threads',
...camelCaseObject(Factory.build('thread', { ...buildParamsSnakeCase, type: 'discussion' }, null)),
contentType: 'POST',
...camelCaseObject(discussionThread),
...testMeta,
},
];
}
question: {
testFor: 'question threads',
contentType: 'POST',
...camelCaseObject(questionThread),
...testMeta,
},
comment: {
testFor: 'comments',
contentType: 'COMMENT',
type: 'discussion',
...camelCaseObject(comment),
...testMeta,
},
};
};
const canPerformActionTestData = ACTIONS_LIST
.map(({ action, conditions, label: { defaultMessage } }) => {
const buildParams = {
const mockThreadAndComment = async (response) => {
axiosMock.onGet(`${threadsApiUrl}${discussionThreadId}/`).reply(200, response);
axiosMock.onGet(`${threadsApiUrl}${questionThreadId}/`).reply(200, response);
axiosMock.onGet(commentsApiUrl).reply(200, Factory.build('commentsResult'));
axiosMock.onPost(commentsApiUrl).reply(200, response);
await executeThunk(fetchThread(discussionThreadId), store.dispatch, store.getState);
await executeThunk(fetchThread(questionThreadId), store.dispatch, store.getState);
await executeThunk(fetchThreadComments(discussionThreadId), store.dispatch, store.getState);
await executeThunk(addComment(commentContent, discussionThreadId, null), store.dispatch, store.getState);
};
const canPerformActionTestData = ACTIONS_LIST.flatMap(({
id, action, conditions, label: { defaultMessage },
}) => {
const buildParams = { editable_fields: [action] };
if (conditions) {
Object.entries(conditions).forEach(([conditionKey, conditionValue]) => {
buildParams[conditionKey] = conditionValue;
});
}
const testContent = buildTestContent(buildParams, { label: defaultMessage, action });
switch (id) {
case 'answer':
case 'unanswer':
return [testContent.question];
case 'endorse':
case 'unendorse':
return [testContent.comment, testContent.discussion];
default:
return [testContent.discussion, testContent.question, testContent.comment];
}
});
const canNotPerformActionTestData = ACTIONS_LIST.flatMap(({ action, conditions, label: { defaultMessage } }) => {
const label = defaultMessage;
if (!conditions) {
const content = buildTestContent({ editable_fields: [] }, { reason: 'field is not editable', label: defaultMessage });
return [content.discussion, content.question, content.comment];
}
const reversedConditions = Object.fromEntries(Object.entries(conditions).map(([key, value]) => [key, !value]));
const content = {
// can edit field, but doesn't pass conditions
...buildTestContent({
editable_fields: [action],
};
if (conditions) {
Object.entries(conditions)
.forEach(([conditionKey, conditionValue]) => {
buildParams[conditionKey] = conditionValue;
});
}
return buildTestContent(buildParams, { label: defaultMessage, action });
})
.flat();
...reversedConditions,
}, { reason: 'field is editable but does not pass condition', label, action }),
const canNotPerformActionTestData = ACTIONS_LIST
.map(({ action, conditions, label: { defaultMessage } }) => {
const label = defaultMessage;
let content;
if (!conditions) {
content = buildTestContent({ editable_fields: [] }, { reason: 'field is not editable', label: defaultMessage });
} else {
const reversedConditions = Object.keys(conditions)
.reduce(
(results, key) => ({
...results,
[key]: !conditions[key],
}),
{},
);
// passes conditions, but can't edit field
...(action === ContentActions.DELETE ? {} : buildTestContent({
editable_fields: [],
...conditions,
}, { reason: 'passes conditions but field is not editable', label, action })),
content = [
// can edit field, but doesn't pass conditions
...buildTestContent({
editable_fields: [action],
...reversedConditions,
}, { reason: 'field is editable but does not pass condition', label, action }),
// passes conditions, but can't edit field
...(action === ContentActions.DELETE
? []
: buildTestContent({
editable_fields: [],
...conditions,
}, { reason: 'passes conditions but field is not editable', label, action })
),
// can't edit field, and doesn't pass conditions
...buildTestContent({
editable_fields: [],
...reversedConditions,
}, { reason: 'can not edit field and does not match conditions', label, action }),
];
}
return content;
})
.flat();
// can't edit field, and doesn't pass conditions
...buildTestContent({
editable_fields: [],
...reversedConditions,
}, { reason: 'can not edit field and does not match conditions', label, action }),
};
function renderComponent(
commentOrPost,
{ disabled = false, actionHandlers = {} } = {},
) {
return [content.discussion, content.question, content.comment];
});
const renderComponent = ({
id = '',
contentType = 'POST',
closed = false,
type = 'discussion',
postId = '',
disabled = false,
actionHandlers = {},
} = {}) => {
render(
<IntlProvider locale="en">
<AppProvider store={store}>
<ActionsDropdown
commentOrPost={commentOrPost}
disabled={disabled}
actionHandlers={actionHandlers}
/>
<PostCommentsContext.Provider value={{
isClosed: closed,
postType: type,
postId,
}}
>
<ActionsDropdown
id={id}
disabled={disabled}
actionHandlers={actionHandlers}
contentType={contentType}
/>
</PostCommentsContext.Provider>
</AppProvider>
</IntlProvider>,
);
}
};
const findOpenActionsDropdownButton = async () => (
screen.findByRole('button', { name: messages.actionsAlt.defaultMessage })
@@ -128,10 +189,18 @@ describe('ActionsDropdown', () => {
},
});
store = initializeStore();
Factory.resetAll();
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
axiosMock.onGet(`${getCourseConfigApiUrl()}${courseId}/`)
.reply(200, { isPostingEnabled: true });
await executeThunk(fetchCourseConfig(courseId), store.dispatch, store.getState);
});
it.each(buildTestContent())('can open drop down if enabled', async (commentOrPost) => {
renderComponent(commentOrPost, { disabled: false });
it.each(Object.values(buildTestContent()))('can open drop down if enabled', async (commentOrPost) => {
await mockThreadAndComment(commentOrPost);
renderComponent({ ...commentOrPost });
const openButton = await findOpenActionsDropdownButton();
await act(async () => {
@@ -141,8 +210,9 @@ describe('ActionsDropdown', () => {
await waitFor(() => expect(screen.queryByTestId('actions-dropdown-modal-popup')).toBeInTheDocument());
});
it.each(buildTestContent())('can not open drop down if disabled', async (commentOrPost) => {
renderComponent(commentOrPost, { disabled: true });
it.each(Object.values(buildTestContent()))('can not open drop down if disabled', async (commentOrPost) => {
await mockThreadAndComment(commentOrPost);
renderComponent({ ...commentOrPost, disabled: true });
const openButton = await findOpenActionsDropdownButton();
await act(async () => {
@@ -153,11 +223,9 @@ describe('ActionsDropdown', () => {
});
it('copy link action should be visible on posts', async () => {
const commentOrPost = {
testFor: 'thread',
...camelCaseObject(Factory.build('thread', { editable_fields: ['copy_link'] }, null)),
};
renderComponent(commentOrPost, { disabled: false });
const discussionObject = buildTestContent({ editable_fields: ['copy_link'] }).discussion;
await mockThreadAndComment(discussionObject);
renderComponent({ ...camelCaseObject(discussionObject) });
const openButton = await findOpenActionsDropdownButton();
await act(async () => {
@@ -168,11 +236,9 @@ describe('ActionsDropdown', () => {
});
it('copy link action should not be visible on a comment', async () => {
const commentOrPost = {
testFor: 'comments',
...camelCaseObject(Factory.build('comment', {}, null)),
};
renderComponent(commentOrPost, { disabled: false });
const commentObject = buildTestContent().comment;
await mockThreadAndComment(commentObject);
renderComponent({ ...camelCaseObject(commentObject) });
const openButton = await findOpenActionsDropdownButton();
await act(async () => {
@@ -182,16 +248,33 @@ describe('ActionsDropdown', () => {
await waitFor(() => expect(screen.queryByText('Copy link')).not.toBeInTheDocument());
});
it('should close the dropdown when pressing escape', async () => {
const discussionObject = buildTestContent({ editable_fields: ['copy_link'] }).discussion;
await mockThreadAndComment(discussionObject);
renderComponent({ ...camelCaseObject(discussionObject) });
const openButton = await findOpenActionsDropdownButton();
await act(async () => {
fireEvent.click(openButton);
});
await waitFor(() => expect(screen.getByRole('button', { name: 'Copy link' })).toBeInTheDocument());
await act(async () => {
fireEvent.keyDown(document.body, { key: 'Escape', code: 'Escape' });
});
await waitFor(() => expect(screen.queryByRole('button', { name: 'Copy link' })).toBeNull());
});
describe.each(canPerformActionTestData)('Actions', ({
testFor, action, label, reason, ...commentOrPost
testFor, action, label, ...commentOrPost
}) => {
describe(`for ${testFor}`, () => {
it(`can "${label}" when allowed`, async () => {
await mockThreadAndComment(commentOrPost);
const mockHandler = jest.fn();
renderComponent(
commentOrPost,
{ actionHandlers: { [action]: mockHandler } },
);
renderComponent({ ...commentOrPost, actionHandlers: { [action]: mockHandler } });
const openButton = await findOpenActionsDropdownButton();
await act(async () => {
@@ -214,7 +297,8 @@ describe('ActionsDropdown', () => {
}) => {
describe(`for ${testFor}`, () => {
it(`can't "${label}" when ${reason}`, async () => {
renderComponent(commentOrPost);
await mockThreadAndComment(commentOrPost);
renderComponent({ ...commentOrPost });
const openButton = await findOpenActionsDropdownButton();
await act(async () => {
@@ -225,4 +309,148 @@ describe('ActionsDropdown', () => {
});
});
});
it('applies in-context-sidebar class when inContextSidebar is in URL', async () => {
const originalLocation = window.location;
delete window.location;
window.location = { ...originalLocation, search: '?inContextSidebar=true' };
const discussionObject = buildTestContent().discussion;
await mockThreadAndComment(discussionObject);
renderComponent({ ...camelCaseObject(discussionObject) });
const openButton = await findOpenActionsDropdownButton();
await act(async () => {
fireEvent.click(openButton);
});
const dropdown = screen.getByTestId('actions-dropdown-modal-popup').closest('.actions-dropdown');
expect(dropdown).toHaveClass('in-context-sidebar');
window.location = originalLocation;
});
it('does not apply in-context-sidebar class when inContextSidebar is not in URL', async () => {
const originalLocation = window.location;
delete window.location;
window.location = { ...originalLocation, search: '' };
const discussionObject = buildTestContent().discussion;
await mockThreadAndComment(discussionObject);
renderComponent({ ...camelCaseObject(discussionObject) });
const openButton = await findOpenActionsDropdownButton();
await act(async () => {
fireEvent.click(openButton);
});
const dropdown = screen.getByTestId('actions-dropdown-modal-popup').closest('.actions-dropdown');
expect(dropdown).not.toHaveClass('in-context-sidebar');
window.location = originalLocation;
});
it('handles SSR environment when window is undefined', () => {
const testSSRLogic = () => {
if (typeof window !== 'undefined') {
return window.location.search.includes('inContextSidebar');
}
return false;
};
const originalWindow = global.window;
const originalProcess = global.process;
try {
delete global.window;
const result = testSSRLogic();
expect(result).toBe(false);
global.window = originalWindow;
const resultWithWindow = testSSRLogic();
expect(resultWithWindow).toBe(false);
} finally {
global.window = originalWindow;
global.process = originalProcess;
}
});
it('calls logError for unknown action', async () => {
const discussionObject = buildTestContent().discussion;
await mockThreadAndComment(discussionObject);
logError.mockClear();
renderComponent({
...discussionObject,
actionHandlers: {
[ContentActions.EDIT_CONTENT]: jest.fn(),
},
});
const openButton = await findOpenActionsDropdownButton();
await act(async () => {
fireEvent.click(openButton);
});
const copyLinkButton = await screen.findByText('Copy link');
await act(async () => {
fireEvent.click(copyLinkButton);
});
expect(logError).toHaveBeenCalledWith('Unknown or unimplemented action copy_link');
});
describe('posting restrictions', () => {
it('removes edit action when posting is disabled', async () => {
const discussionObject = buildTestContent({
editable_fields: ['raw_body'],
}).discussion;
await mockThreadAndComment(discussionObject);
axiosMock.onGet(`${getCourseConfigApiUrl()}${courseId}/`)
.reply(200, { isPostingEnabled: false });
await executeThunk(fetchCourseConfig(courseId), store.dispatch, store.getState);
renderComponent({ ...discussionObject });
const openButton = await findOpenActionsDropdownButton();
await act(async () => {
fireEvent.click(openButton);
});
await waitFor(() => {
expect(screen.queryByText('Edit')).not.toBeInTheDocument();
});
});
it('keeps edit action when posting is enabled', async () => {
const discussionObject = buildTestContent({
editable_fields: ['raw_body'],
}).discussion;
await mockThreadAndComment(discussionObject);
axiosMock.onGet(`${getCourseConfigApiUrl()}${courseId}/`)
.reply(200, { isPostingEnabled: true });
await executeThunk(fetchCourseConfig(courseId), store.dispatch, store.getState);
renderComponent({ ...discussionObject });
const openButton = await findOpenActionsDropdownButton();
await act(async () => {
fireEvent.click(openButton);
});
await waitFor(() => {
expect(screen.queryByText('Edit')).toBeInTheDocument();
});
});
});
});

View File

@@ -1,34 +1,41 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Alert } from '@openedx/paragon';
import { Report } from '@openedx/paragon/icons';
import { useSelector } from 'react-redux';
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Alert } from '@edx/paragon';
import { Report } from '@edx/paragon/icons';
import { useIntl } from '@edx/frontend-platform/i18n';
import { AvatarOutlineAndLabelColors } from '../../data/constants';
import {
selectModerationSettings, selectUserHasModerationPrivileges, selectUserIsGroupTa, selectUserIsStaff,
selectUserHasModerationPrivileges, selectUserIsGroupTa, selectUserIsStaff,
} from '../data/selectors';
import { commentShape } from '../post-comments/comments/comment/proptypes';
import messages from '../post-comments/messages';
import { postShape } from '../posts/post/proptypes';
import AuthorLabel from './AuthorLabel';
import AlertBar from './AlertBar';
function AlertBanner({
intl,
content,
}) {
const AlertBanner = ({
author,
abuseFlagged,
lastEdit,
closed,
closedBy,
closeReason,
editByLabel,
closedByLabel,
}) => {
const intl = useIntl();
const userHasModerationPrivileges = useSelector(selectUserHasModerationPrivileges);
const userIsGroupTa = useSelector(selectUserIsGroupTa);
const userIsGlobalStaff = useSelector(selectUserIsStaff);
const { reasonCodesEnabled } = useSelector(selectModerationSettings);
const userIsContentAuthor = getAuthenticatedUser().username === content.author;
const canSeeReportedBanner = content?.abuseFlagged;
const userIsContentAuthor = getAuthenticatedUser().username === author;
const canSeeReportedBanner = abuseFlagged;
const canSeeLastEditOrClosedAlert = (userHasModerationPrivileges || userIsGroupTa
|| userIsGlobalStaff || userIsContentAuthor
);
const editByLabelColor = AvatarOutlineAndLabelColors[editByLabel];
const closedByLabelColor = AvatarOutlineAndLabelColors[closedByLabel];
return (
<>
@@ -37,53 +44,54 @@ function AlertBanner({
{intl.formatMessage(messages.abuseFlaggedMessage)}
</Alert>
)}
{reasonCodesEnabled && canSeeLastEditOrClosedAlert && (
{ canSeeLastEditOrClosedAlert && (
<>
{content.lastEdit?.reason && (
<Alert variant="info" className="px-3 shadow-none mb-1 py-10px bg-light-200">
<div className="d-flex align-items-center flex-wrap text-gray-700 font-style">
{intl.formatMessage(messages.editedBy)}
<span className="ml-1 mr-3">
<AuthorLabel author={content.lastEdit.editorUsername} linkToProfile postOrComment />
</span>
<span
className="mx-1.5 font-size-8 font-style text-light-700"
style={{ lineHeight: '15px' }}
>
{intl.formatMessage(messages.fullStop)}
</span>
{intl.formatMessage(messages.reason)}:&nbsp;{content.lastEdit.reason}
</div>
</Alert>
{lastEdit?.reason && (
<AlertBar
message={intl.formatMessage(messages.editedBy)}
author={lastEdit.editorUsername}
authorLabel={editByLabel}
labelColor={editByLabelColor && `text-${editByLabelColor}`}
reason={lastEdit.reason}
/>
)}
{content.closed && (
<Alert variant="info" className="px-3 shadow-none mb-1 py-10px bg-light-200">
<div className="d-flex align-items-center flex-wrap text-gray-700 font-style">
{intl.formatMessage(messages.closedBy)}
<span className="ml-1 ">
<AuthorLabel author={content.closedBy} linkToProfile postOrComment />
</span>
<span
className="mx-1.5 font-size-8 font-style text-light-700"
style={{ lineHeight: '15px' }}
>
{intl.formatMessage(messages.fullStop)}
</span>
{content.closeReason && (`${intl.formatMessage(messages.reason)}: ${content.closeReason}`)}
</div>
</Alert>
{closed && (
<AlertBar
message={intl.formatMessage(messages.closedBy)}
author={closedBy}
authorLabel={closedByLabel}
labelColor={closedByLabelColor && `text-${closedByLabelColor}`}
reason={closeReason}
/>
)}
</>
)}
</>
);
}
AlertBanner.propTypes = {
intl: intlShape.isRequired,
content: PropTypes.oneOfType([commentShape.isRequired, postShape.isRequired]).isRequired,
};
export default injectIntl(AlertBanner);
AlertBanner.propTypes = {
author: PropTypes.string.isRequired,
abuseFlagged: PropTypes.bool,
closed: PropTypes.bool,
closedBy: PropTypes.string,
closedByLabel: PropTypes.string,
closeReason: PropTypes.string,
editByLabel: PropTypes.string,
lastEdit: PropTypes.shape({
editorUsername: PropTypes.string,
reason: PropTypes.string,
}),
};
AlertBanner.defaultProps = {
abuseFlagged: false,
closed: undefined,
closedBy: undefined,
closedByLabel: undefined,
closeReason: undefined,
editByLabel: undefined,
lastEdit: {},
};
export default React.memo(AlertBanner);

View File

@@ -9,7 +9,7 @@ import { ThreadType } from '../../data/constants';
import { initializeStore } from '../../store';
import messages from '../post-comments/messages';
import AlertBanner from './AlertBanner';
import { DiscussionContext } from './context';
import DiscussionContext from './context';
import '../post-comments/data/__factories__';
import '../posts/data/__factories__';
@@ -31,7 +31,14 @@ function renderComponent(
value={{ courseId: 'course-v1:edX+TestX+Test_Course' }}
>
<AlertBanner
content={content}
author={content.author}
abuseFlagged={content.abuseFlagged}
lastEdit={content.lastEdit}
closed={content.closed}
closedBy={content.closedBy}
closeReason={content.closeReason}
editByLabel={content.editByLabel}
closedByLabel={content.closedByLabel}
/>
</DiscussionContext.Provider>
</AppProvider>
@@ -83,7 +90,6 @@ describe.each([
store = initializeStore({
config: {
hasModerationPrivileges: true,
reasonCodesEnabled: true,
},
});
const content = buildTestContent(type, props);

View File

@@ -0,0 +1,61 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Alert } from '@openedx/paragon';
import { useIntl } from '@edx/frontend-platform/i18n';
import messages from '../post-comments/messages';
import AuthorLabel from './AuthorLabel';
const AlertBar = ({
message,
author,
authorLabel,
labelColor,
reason,
}) => {
const intl = useIntl();
return (
<Alert variant="info" className="px-3 shadow-none mb-1 py-10px bg-light-200">
<div className="d-flex align-items-center flex-wrap text-gray-700 font-style">
{message}
<span className="ml-1">
<AuthorLabel
author={author}
authorLabel={authorLabel}
labelColor={labelColor}
linkToProfile
postOrComment
/>
</span>
<span
className="mr-1.5 font-size-8 font-style text-light-700"
style={{ lineHeight: '15px' }}
>
{intl.formatMessage(messages.fullStop)}
</span>
{reason && (`${intl.formatMessage(messages.reason)}: ${reason}`)}
</div>
</Alert>
);
};
AlertBar.propTypes = {
message: PropTypes.string,
author: PropTypes.string,
authorLabel: PropTypes.string,
labelColor: PropTypes.string,
reason: PropTypes.string,
};
AlertBar.defaultProps = {
message: '',
author: '',
authorLabel: '',
labelColor: '',
reason: '',
};
export default React.memo(AlertBar);

View File

@@ -1,23 +1,20 @@
import React, { useContext } from 'react';
import React, { useContext, useMemo } from 'react';
import PropTypes from 'prop-types';
import { Icon, OverlayTrigger, Tooltip } from '@openedx/paragon';
import classNames from 'classnames';
import { Link, useLocation } from 'react-router-dom';
import { generatePath, Link } from 'react-router-dom';
import * as timeago from 'timeago.js';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Icon, OverlayTrigger, Tooltip } from '@edx/paragon';
import { Institution, School } from '@edx/paragon/icons';
import { useIntl } from '@edx/frontend-platform/i18n';
import { Routes } from '../../data/constants';
import { useShowLearnersTab } from '../data/hooks';
import messages from '../messages';
import { discussionsPath } from '../utils';
import { DiscussionContext } from './context';
import { getAuthorLabel } from '../utils';
import DiscussionContext from './context';
import timeLocale from './time-locale';
function AuthorLabel({
intl,
const AuthorLabel = ({
author,
authorLabel,
linkToProfile,
@@ -26,58 +23,48 @@ function AuthorLabel({
postCreatedAt,
authorToolTip,
postOrComment,
}) {
const location = useLocation();
const { courseId } = useContext(DiscussionContext);
let icon = null;
let authorLabelMessage = null;
}) => {
timeago.register('time-locale', timeLocale);
if (authorLabel === 'Staff') {
icon = Institution;
authorLabelMessage = intl.formatMessage(messages.authorLabelStaff);
}
if (authorLabel === 'Community TA') {
icon = School;
authorLabelMessage = intl.formatMessage(messages.authorLabelTA);
}
const intl = useIntl();
const { courseId, enableInContextSidebar } = useContext(DiscussionContext);
const { icon, authorLabelMessage } = useMemo(() => getAuthorLabel(intl, authorLabel), [authorLabel]);
const isRetiredUser = author ? author.startsWith('retired__user') : false;
const showTextPrimary = !authorLabelMessage && !isRetiredUser && !alert;
const className = classNames('d-flex align-items-center', { 'mb-0.5': !postOrComment }, labelColor);
const showUserNameAsLink = useShowLearnersTab()
&& linkToProfile && author && author !== intl.formatMessage(messages.anonymous);
const showUserNameAsLink = linkToProfile && author && author !== intl.formatMessage(messages.anonymous)
&& !enableInContextSidebar;
const labelContents = (
<div className={className}>
{!alert && (
<span
className={classNames('mr-1.5 font-size-14 font-style font-weight-500', {
'text-gray-700': isRetiredUser,
'text-primary-500': !authorLabelMessage && !isRetiredUser,
})}
role="heading"
aria-level="2"
>
{isRetiredUser ? '[Deactivated]' : author}
</span>
)}
const authorName = useMemo(() => (
<span
className={classNames('mr-1.5 font-style font-weight-500 author-name', {
'text-gray-700': isRetiredUser,
'text-primary-500': !authorLabelMessage && !isRetiredUser,
})}
role="heading"
aria-level="2"
>
{isRetiredUser ? '[Deactivated]' : author}
</span>
), [author, authorLabelMessage, isRetiredUser]);
const labelContents = useMemo(() => (
<>
<OverlayTrigger
placement={authorToolTip ? 'top' : 'right'}
overlay={(
<Tooltip id={`endorsed-by-${author}-tooltip`}>
{author}
<Tooltip id={authorToolTip ? `endorsed-by-${author}-tooltip` : `${authorLabel}-label-tooltip`}>
<>
{authorToolTip ? author : authorLabel}
<br />
{intl.formatMessage(messages.authorAdminDescription)}
</>
</Tooltip>
)}
trigger={['hover', 'focus']}
>
<div className={classNames('d-flex flex-row align-items-center', {
'disable-div': !authorToolTip,
})}
>
<div className={classNames('d-flex flex-row align-items-center')}>
<Icon
style={{
width: '1rem',
@@ -86,24 +73,23 @@ function AuthorLabel({
src={icon}
data-testid="author-icon"
/>
{authorLabelMessage && (
<span
className={classNames('mr-1.5 font-style font-weight-500', {
'text-primary-500': showTextPrimary,
'text-gray-700': isRetiredUser,
})}
style={{ marginLeft: '2px' }}
>
{authorLabelMessage}
</span>
)}
</div>
</OverlayTrigger>
{authorLabelMessage && (
<span
className={classNames('mr-1.5 font-size-14 font-style font-weight-500', {
'text-primary-500': showTextPrimary,
'text-gray-700': isRetiredUser,
})}
style={{ marginLeft: '2px' }}
>
{authorLabelMessage}
</span>
)}
{postCreatedAt && (
<span
title={postCreatedAt}
className={classNames('font-family-inter align-content-center', {
className={classNames('align-content-center post-summary-timestamp', {
'text-white': alert,
'text-gray-500': !alert,
})}
@@ -112,27 +98,53 @@ function AuthorLabel({
{timeago.format(postCreatedAt, 'time-locale')}
</span>
)}
</>
), [author, authorLabelMessage, authorToolTip, icon, isRetiredUser, postCreatedAt, showTextPrimary, alert]);
</div>
);
return showUserNameAsLink
? (
const learnerPostsLink = useMemo(() => {
if (!showUserNameAsLink) {
return null;
}
return (
<Link
data-testid="learner-posts-link"
id="learner-posts-link"
to={discussionsPath(Routes.LEARNERS.POSTS, { learnerUsername: author, courseId })(location)}
className="text-decoration-none"
to={generatePath(Routes.LEARNERS.POSTS, { learnerUsername: author, courseId })}
className="text-decoration-none text-reset"
style={{ width: 'fit-content' }}
>
{labelContents}
{!alert && authorName}
</Link>
);
}, [showUserNameAsLink, author, courseId, alert, authorName]);
return showUserNameAsLink
? (
<div className={`${className} flex-wrap`}>
{!authorLabel ? (
<OverlayTrigger
placement={authorToolTip ? 'top' : 'right'}
overlay={(
<Tooltip id={authorToolTip ? `endorsed-by-${author}-tooltip` : `${authorLabel}-label-tooltip`}>
<>
{intl.formatMessage(messages.authorLearnerTitle)}
<br />
{intl.formatMessage(messages.authorLearnerDescription)}
</>
</Tooltip>
)}
trigger={['hover', 'focus']}
>
{learnerPostsLink}
</OverlayTrigger>
) : learnerPostsLink }
{labelContents}
</div>
)
: <>{labelContents}</>;
}
: <div className={`${className} flex-wrap`}>{authorName}{labelContents}</div>;
};
AuthorLabel.propTypes = {
intl: intlShape.isRequired,
author: PropTypes.string.isRequired,
authorLabel: PropTypes.string,
linkToProfile: PropTypes.bool,
@@ -153,4 +165,4 @@ AuthorLabel.defaultProps = {
postOrComment: false,
};
export default injectIntl(AuthorLabel);
export default React.memo(AuthorLabel);

View File

@@ -9,11 +9,11 @@ import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { AppProvider } from '@edx/frontend-platform/react';
import { initializeStore } from '../../store';
import { executeThunk } from '../../test-utils';
import executeThunk from '../../test-utils';
import { getCourseConfigApiUrl } from '../data/api';
import { fetchCourseConfig } from '../data/thunks';
import fetchCourseConfig from '../data/thunks';
import AuthorLabel from './AuthorLabel';
import { DiscussionContext } from './context';
import DiscussionContext from './context';
const courseId = 'course-v1:edX+DemoX+Demo_Course';
const courseConfigApiUrl = getCourseConfigApiUrl();
@@ -21,11 +21,11 @@ let store;
let axiosMock;
let container;
function renderComponent(author, authorLabel, linkToProfile, labelColor) {
function renderComponent(author, authorLabel, linkToProfile, labelColor, enableInContextSidebar) {
const wrapper = render(
<IntlProvider locale="en">
<AppProvider store={store}>
<DiscussionContext.Provider value={{ courseId }}>
<DiscussionContext.Provider value={{ courseId, enableInContextSidebar }}>
<AuthorLabel
author={author}
authorLabel={authorLabel}
@@ -53,7 +53,6 @@ describe('Author label', () => {
store = initializeStore();
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
axiosMock.onGet(`${courseConfigApiUrl}${courseId}/`).reply(200, {
learners_tab_enabled: true,
has_moderation_privileges: true,
});
axiosMock.onGet(`${courseConfigApiUrl}${courseId}/settings`).reply(200, {});
@@ -63,46 +62,61 @@ describe('Author label', () => {
describe.each([
['anonymous', null, false, ''],
['ta_user', 'Community TA', true, 'text-TA-color'],
['moderator_user', 'Moderator', true, 'text-TA-color'],
['retired__user', null, false, ''],
['staff_user', 'Staff', true, 'text-staff-color'],
['learner_user', null, false, ''],
])('for %s', (
author, authorLabel, linkToProfile, labelColor,
) => {
it('it has author name text',
])('for %s', (author, authorLabel, linkToProfile, labelColor) => {
it(
'it has author name text',
async () => {
renderComponent(author, authorLabel, linkToProfile, labelColor);
const authorElement = container.querySelector('[role=heading]');
const authorName = author.startsWith('retired__user') ? '[Deactivated]' : author;
expect(authorElement).toHaveTextContent(authorName);
});
},
);
it(`it is "${!linkToProfile && 'not'}" clickable when linkToProfile is ${!!linkToProfile}`,
it(
`it is "${(!linkToProfile) && 'not'}" clickable when linkToProfile is ${!!linkToProfile} and enableInContextSidebar is false`,
async () => {
renderComponent(author, authorLabel, linkToProfile, labelColor);
renderComponent(author, authorLabel, linkToProfile, labelColor, false);
if (linkToProfile) {
expect(screen.queryByTestId('learner-posts-link')).toBeInTheDocument();
} else {
expect(screen.queryByTestId('learner-posts-link')).not.toBeInTheDocument();
}
});
},
);
it(`it has "${!linkToProfile && 'not'}" label text and label color when linkToProfile is ${!!linkToProfile}`,
it(
'it is not clickable when enableInContextSidebar is true',
async () => {
renderComponent(author, authorLabel, linkToProfile, labelColor, true);
expect(screen.queryByTestId('learner-posts-link')).not.toBeInTheDocument();
},
);
it(
`it has "${!linkToProfile && 'not'}" label text and label color when linkToProfile is ${!!linkToProfile}`,
async () => {
renderComponent(author, authorLabel, linkToProfile, labelColor);
const authorElement = container.querySelector('[role=heading]');
const labelElement = authorElement.parentNode.lastChild;
const label = ['TA', 'Staff'].includes(labelElement.textContent) && labelElement.textContent;
const labelParentNode = authorElement.parentNode.parentNode;
const labelElement = labelParentNode.lastChild.lastChild;
const label = ['CTA', 'TA', 'Staff'].includes(labelElement.textContent) && labelElement.textContent;
if (linkToProfile) {
expect(authorElement.parentNode).toHaveClass(labelColor);
expect(authorElement.parentNode.lastChild).toHaveTextContent(label);
expect(labelParentNode).toHaveClass(labelColor);
expect(labelElement).toHaveTextContent(label);
} else {
expect(authorElement.parentNode.lastChild).not.toHaveTextContent(label, { exact: true });
expect(authorElement.parentNode).not.toHaveClass(labelColor, { exact: true });
}
});
},
);
});
});

View File

@@ -1,62 +1,105 @@
import React from 'react';
import PropTypes from 'prop-types';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { ActionRow, Button, ModalDialog } from '@edx/paragon';
import {
ActionRow,
ModalDialog,
Spinner, StatefulButton,
} from '@openedx/paragon';
import { useIntl } from '@edx/frontend-platform/i18n';
import messages from '../messages';
function Confirmation({
intl,
const Confirmation = ({
isOpen,
title,
description,
boldDescription,
onClose,
comfirmAction,
closeButtonVaraint,
confirmAction,
closeButtonVariant,
confirmButtonState,
confirmButtonVariant,
confirmButtonText,
}) {
isDataLoading,
isConfirmButtonPending,
pendingConfirmButtonText,
closeButtonText,
}) => {
const intl = useIntl();
return (
<ModalDialog title={title} isOpen={isOpen} hasCloseButton={false} onClose={onClose} zIndex={5000}>
<ModalDialog.Header>
<ModalDialog.Title>
{title}
</ModalDialog.Title>
</ModalDialog.Header>
<ModalDialog.Body>
{description}
</ModalDialog.Body>
<ModalDialog.Footer>
<ActionRow>
<ModalDialog.CloseButton variant={closeButtonVaraint}>
{intl.formatMessage(messages.confirmationCancel)}
</ModalDialog.CloseButton>
<Button variant={confirmButtonVariant} onClick={comfirmAction}>
{ confirmButtonText || intl.formatMessage(messages.confirmationConfirm)}
</Button>
</ActionRow>
</ModalDialog.Footer>
{isDataLoading && !isConfirmButtonPending ? (
<ModalDialog.Body>
<div className="d-flex justify-content-center p-4">
<Spinner animation="border" variant="primary" size="lg" />
</div>
</ModalDialog.Body>
) : (
<>
<ModalDialog.Header>
<ModalDialog.Title>
{title}
</ModalDialog.Title>
</ModalDialog.Header>
<ModalDialog.Body style={{ whiteSpace: 'pre-line' }}>
{description}
{boldDescription && <><br /><p className="font-weight-bold pt-2">{boldDescription}</p></>}
</ModalDialog.Body>
<ModalDialog.Footer>
<ActionRow>
<ModalDialog.CloseButton variant={closeButtonVariant}>
{closeButtonText || intl.formatMessage(messages.confirmationCancel)}
</ModalDialog.CloseButton>
{confirmAction && (
<StatefulButton
labels={{
default: confirmButtonText || intl.formatMessage(messages.confirmationConfirm),
pending: pendingConfirmButtonText || confirmButtonText
|| intl.formatMessage(messages.confirmationConfirm),
}}
state={isConfirmButtonPending ? 'pending' : confirmButtonState}
variant={confirmButtonVariant}
onClick={confirmAction}
/>
)}
</ActionRow>
</ModalDialog.Footer>
</>
)}
</ModalDialog>
);
}
};
Confirmation.propTypes = {
intl: intlShape.isRequired,
isOpen: PropTypes.bool.isRequired,
onClose: PropTypes.func.isRequired,
comfirmAction: PropTypes.func.isRequired,
confirmAction: PropTypes.func.isRequired,
title: PropTypes.string.isRequired,
description: PropTypes.string.isRequired,
closeButtonVaraint: PropTypes.string,
boldDescription: PropTypes.string,
closeButtonVariant: PropTypes.string,
confirmButtonVariant: PropTypes.string,
confirmButtonText: PropTypes.string,
isDataLoading: PropTypes.bool,
isConfirmButtonPending: PropTypes.bool,
pendingConfirmButtonText: PropTypes.string,
closeButtonText: PropTypes.string,
confirmButtonState: PropTypes.string,
};
Confirmation.defaultProps = {
closeButtonVaraint: 'default',
closeButtonVariant: 'default',
confirmButtonVariant: 'primary',
confirmButtonText: '',
boldDescription: '',
isDataLoading: false,
isConfirmButtonPending: false,
pendingConfirmButtonText: '',
closeButtonText: '',
confirmButtonState: 'default',
};
export default injectIntl(Confirmation);
export default React.memo(Confirmation);

View File

@@ -1,30 +1,34 @@
import React from 'react';
import React, { useContext } from 'react';
import PropTypes from 'prop-types';
import { Alert, Icon } from '@openedx/paragon';
import { CheckCircle, Verified } from '@openedx/paragon/icons';
import * as timeago from 'timeago.js';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Alert, Icon } from '@edx/paragon';
import { CheckCircle, Verified } from '@edx/paragon/icons';
import { useIntl } from '@edx/frontend-platform/i18n';
import { ThreadType } from '../../data/constants';
import { commentShape } from '../post-comments/comments/comment/proptypes';
import messages from '../post-comments/messages';
import PostCommentsContext from '../post-comments/postCommentsContext';
import AuthorLabel from './AuthorLabel';
import timeLocale from './time-locale';
function EndorsedAlertBanner({
intl,
content,
postType,
}) {
const EndorsedAlertBanner = ({
endorsed,
endorsedAt,
endorsedBy,
endorsedByLabel,
}) => {
timeago.register('time-locale', timeLocale);
const intl = useIntl();
const { postType } = useContext(PostCommentsContext);
const isQuestion = postType === ThreadType.QUESTION;
const classes = isQuestion ? 'bg-success-500 text-white' : 'bg-dark-500 text-white';
const iconClass = isQuestion ? CheckCircle : Verified;
return (
content.endorsed && (
endorsed && (
<Alert
variant="plain"
className={`px-2.5 mb-0 py-8px align-items-center shadow-none ${classes}`}
@@ -39,17 +43,17 @@ function EndorsedAlertBanner({
height: '20px',
}}
/>
<strong className="ml-2 font-family-inter">
<strong className="ml-2">
{intl.formatMessage(isQuestion ? messages.answer : messages.endorsed)}
</strong>
</div>
<span className="d-flex align-items-center align-items-center flex-wrap" style={{ marginRight: '-1px' }}>
<AuthorLabel
author={content.endorsedBy}
authorLabel={content.endorsedByLabel}
author={endorsedBy}
authorLabel={endorsedByLabel}
linkToProfile
alert={content.endorsed}
postCreatedAt={content.endorsedAt}
alert={endorsed}
postCreatedAt={endorsedAt}
authorToolTip
postOrComment
/>
@@ -58,16 +62,19 @@ function EndorsedAlertBanner({
</Alert>
)
);
}
};
EndorsedAlertBanner.propTypes = {
intl: intlShape.isRequired,
content: PropTypes.oneOfType([commentShape.isRequired]).isRequired,
postType: PropTypes.string,
endorsed: PropTypes.bool.isRequired,
endorsedAt: PropTypes.string,
endorsedBy: PropTypes.string,
endorsedByLabel: PropTypes.string,
};
EndorsedAlertBanner.defaultProps = {
postType: null,
endorsedAt: null,
endorsedBy: null,
endorsedByLabel: null,
};
export default injectIntl(EndorsedAlertBanner);
export default React.memo(EndorsedAlertBanner);

View File

@@ -8,7 +8,8 @@ import { AppProvider } from '@edx/frontend-platform/react';
import { ThreadType } from '../../data/constants';
import { initializeStore } from '../../store';
import messages from '../post-comments/messages';
import { DiscussionContext } from './context';
import PostCommentsContext from '../post-comments/postCommentsContext';
import DiscussionContext from './context';
import EndorsedAlertBanner from './EndorsedAlertBanner';
import '../post-comments/data/__factories__';
@@ -21,24 +22,30 @@ function buildTestContent(type, buildParams) {
return camelCaseObject(Factory.build(type, { ...buildParamsSnakeCase }, null));
}
function renderComponent(
content, postType,
) {
const renderComponent = (content, postType) => {
render(
<IntlProvider locale="en">
<AppProvider store={store}>
<DiscussionContext.Provider
value={{ courseId: 'course-v1:edX+DemoX+Demo_Course' }}
>
<EndorsedAlertBanner
content={content}
postType={postType}
/>
<PostCommentsContext.Provider value={{
postType,
}}
>
<EndorsedAlertBanner
endorsed={content.endorsed}
endorsedAt={content.endorsedAt}
endorsedBy={content.endorsedBy}
endorsedByLabel={content.endorsedByLabel}
/>
</PostCommentsContext.Provider>
</DiscussionContext.Provider>
</AppProvider>
</IntlProvider>,
);
}
};
describe.each([
{
@@ -77,7 +84,6 @@ describe.each([
store = initializeStore({
config: {
hasModerationPrivileges: true,
reasonCodesEnabled: true,
},
});
const content = buildTestContent(type, props);

View File

@@ -1,46 +1,57 @@
import React, { useContext } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { injectIntl } from '@edx/frontend-platform/i18n';
import { Button, Icon, IconButton } from '@edx/paragon';
import {
Button, Icon, IconButton, OverlayTrigger, Tooltip,
} from '@openedx/paragon';
import {
StarFilled, StarOutline, ThumbUpFilled, ThumbUpOutline,
} from '../../components/icons';
import { useUserCanAddThreadInBlackoutDate } from '../data/hooks';
import { commentShape } from '../post-comments/comments/comment/proptypes';
import { postShape } from '../posts/post/proptypes';
import ActionsDropdown from './ActionsDropdown';
import { DiscussionContext } from './context';
} from '@openedx/paragon/icons';
import classNames from 'classnames';
function HoverCard({
commentOrPost,
import { useIntl } from '@edx/frontend-platform/i18n';
import { ThreadType } from '../../data/constants';
import { useHasLikePermission, useUserPostingEnabled } from '../data/hooks';
import PostCommentsContext from '../post-comments/postCommentsContext';
import messages from '../posts/post/messages';
import ActionsDropdown from './ActionsDropdown';
import DiscussionContext from './context';
const HoverCard = ({
id,
contentType,
actionHandlers,
handleResponseCommentButton,
addResponseCommentButtonMessage,
onLike,
onFollow,
isClosedPost,
voted,
following,
endorseIcons,
}) {
}) => {
const intl = useIntl();
const { enableInContextSidebar } = useContext(DiscussionContext);
const userCanAddThreadInBlackoutDate = useUserCanAddThreadInBlackoutDate();
const { isClosed } = useContext(PostCommentsContext);
const isUserPrivilegedInPostingRestriction = useUserPostingEnabled();
const userHasLikePermission = useHasLikePermission(contentType, id);
return (
<div
className="flex-fill justify-content-end align-items-center hover-card mr-n4 position-absolute"
data-testid={`hover-card-${commentOrPost.id}`}
id={`hover-card-${commentOrPost.id}`}
className="flex-fill justify-content-end align-items-center hover-card bg-white mr-n4 position-absolute"
data-testid={`hover-card-${id}`}
id={`hover-card-${id}`}
>
{userCanAddThreadInBlackoutDate && (
{isUserPrivilegedInPostingRestriction && (
<div className="d-flex">
<Button
variant="tertiary"
className={classNames('px-2.5 py-2 border-0 font-style text-gray-700 font-size-12',
{ 'w-100': enableInContextSidebar })}
className={classNames(
'px-2.5 py-2 border-0 font-style text-gray-700',
{ 'w-100': enableInContextSidebar },
)}
onClick={() => handleResponseCommentButton()}
disabled={isClosedPost}
disabled={isClosed}
style={{ lineHeight: '20px' }}
>
{addResponseCommentButtonMessage}
@@ -49,68 +60,117 @@ function HoverCard({
)}
{endorseIcons && (
<div className="hover-button">
<IconButton
src={endorseIcons.icon}
iconAs={Icon}
onClick={() => {
const actionFunction = actionHandlers[endorseIcons.action];
actionFunction();
}}
className={['endorse', 'unendorse'].includes(endorseIcons.id) ? 'text-dark-500' : 'text-success-500'}
size="sm"
alt="Endorse"
/>
<OverlayTrigger
overlay={(
<Tooltip id="endorsed-icon-tooltip">
{intl.formatMessage(endorseIcons.label)}
</Tooltip>
)}
trigger={['hover', 'focus']}
>
<IconButton
src={endorseIcons.icon}
iconAs={Icon}
onClick={() => {
const actionFunction = actionHandlers[endorseIcons.action];
actionFunction();
}}
className={['endorse', 'unendorse'].includes(endorseIcons.id) ? 'text-dark-500' : 'text-success-500'}
size="sm"
alt="Endorse"
/>
</OverlayTrigger>
</div>
)}
<div className="hover-button">
<IconButton
src={commentOrPost.voted ? ThumbUpFilled : ThumbUpOutline}
iconAs={Icon}
size="sm"
alt="Like"
iconClassNames="like-icon-dimentions"
onClick={(e) => {
e.preventDefault();
onLike();
}}
/>
</div>
{commentOrPost.following !== undefined && (
<div className="hover-button">
<OverlayTrigger
overlay={(
<Tooltip>
{intl.formatMessage(voted ? messages.removeLike : messages.like)}
</Tooltip>
)}
>
<IconButton
src={commentOrPost.following ? StarFilled : StarOutline}
src={voted ? ThumbUpFilled : ThumbUpOutline}
iconAs={Icon}
size="sm"
alt="Follow"
iconClassNames="follow-icon-dimentions"
alt="Like"
disabled={!userHasLikePermission}
iconClassNames="like-icon-dimensions"
onClick={(e) => {
e.preventDefault();
onFollow();
onLike();
}}
/>
</OverlayTrigger>
</div>
{following !== undefined && (
<div className="hover-button">
<OverlayTrigger
overlay={(
<Tooltip>
{intl.formatMessage(following ? messages.unFollow : messages.follow)}
</Tooltip>
)}
>
<IconButton
src={following ? StarFilled : StarOutline}
iconAs={Icon}
size="sm"
alt="Follow"
iconClassNames="follow-icon-dimensions"
onClick={(e) => {
e.preventDefault();
onFollow();
}}
/>
</OverlayTrigger>
</div>
)}
<div className="hover-button ml-auto">
<ActionsDropdown commentOrPost={commentOrPost} actionHandlers={actionHandlers} dropDownIconSize />
<ActionsDropdown
id={id}
contentType={contentType}
actionHandlers={actionHandlers}
dropDownIconSize
/>
</div>
</div>
);
}
};
HoverCard.propTypes = {
commentOrPost: PropTypes.oneOfType([commentShape, postShape]).isRequired,
id: PropTypes.string.isRequired,
contentType: PropTypes.string.isRequired,
actionHandlers: PropTypes.objectOf(PropTypes.func).isRequired,
handleResponseCommentButton: PropTypes.func.isRequired,
onLike: PropTypes.func.isRequired,
onFollow: PropTypes.func,
addResponseCommentButtonMessage: PropTypes.string.isRequired,
isClosedPost: PropTypes.bool.isRequired,
endorseIcons: PropTypes.objectOf(PropTypes.any),
onLike: PropTypes.func.isRequired,
voted: PropTypes.bool.isRequired,
endorseIcons: PropTypes.objectOf(PropTypes.shape(
{
id: PropTypes.string,
action: PropTypes.string,
icon: PropTypes.element,
label: {
id: PropTypes.string,
defaultMessage: PropTypes.string,
description: PropTypes.string,
},
conditions: {
endorsed: PropTypes.bool,
postType: ThreadType,
},
},
)),
onFollow: PropTypes.func,
following: PropTypes.bool,
};
HoverCard.defaultProps = {
onFollow: () => null,
endorseIcons: null,
following: undefined,
};
export default injectIntl(HoverCard);
export default React.memo(HoverCard);

View File

@@ -1,23 +1,25 @@
import {
render, screen, waitFor,
within,
fireEvent, render, screen, waitFor, within,
} from '@testing-library/react';
import MockAdapter from 'axios-mock-adapter';
import { IntlProvider } from 'react-intl';
import { MemoryRouter, Route } from 'react-router';
import { MemoryRouter } from 'react-router-dom';
import { Factory } from 'rosie';
import { camelCaseObject, initializeMockApp } from '@edx/frontend-platform';
import { initializeMockApp } from '@edx/frontend-platform';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { AppProvider } from '@edx/frontend-platform/react';
import { initializeStore } from '../../store';
import { executeThunk } from '../../test-utils';
import executeThunk from '../../test-utils';
import { getCourseConfigApiUrl } from '../data/api';
import fetchCourseConfig from '../data/thunks';
import DiscussionContent from '../discussions-home/DiscussionContent';
import { getCommentsApiUrl } from '../post-comments/data/api';
import { fetchCommentResponses } from '../post-comments/data/thunks';
import { getThreadsApiUrl } from '../posts/data/api';
import { fetchThreads } from '../posts/data/thunks';
import { DiscussionContext } from './context';
import DiscussionContext from './context';
import '../posts/data/__factories__';
import '../post-comments/data/__factories__';
@@ -25,73 +27,45 @@ import '../post-comments/data/__factories__';
const commentsApiUrl = getCommentsApiUrl();
const threadsApiUrl = getThreadsApiUrl();
const discussionPostId = 'thread-1';
const questionPostId = 'thread-2';
const courseId = 'course-v1:edX+TestX+Test_Course';
const reverseOrder = false;
let store;
let axiosMock;
let container;
function mockAxiosReturnPagedComments() {
[null, false, true].forEach(endorsed => {
const postId = endorsed === null ? discussionPostId : questionPostId;
[1, 2].forEach(page => {
axiosMock
.onGet(commentsApiUrl, {
params: {
thread_id: postId,
page,
page_size: undefined,
requested_fields: 'profile_image',
endorsed,
reverse_order: reverseOrder,
},
})
.reply(200, Factory.build('commentsResult', { can_delete: true }, {
threadId: postId,
page,
pageSize: 1,
count: 2,
endorsed,
childCount: page === 1 ? 2 : 0,
}));
});
});
}
function mockAxiosReturnPagedCommentsResponses() {
async function mockAxiosReturnPagedCommentsResponses() {
const parentId = 'comment-1';
const commentsResponsesApiUrl = `${commentsApiUrl}${parentId}/`;
const paramsTemplate = {
page: undefined,
page_size: undefined,
requested_fields: 'profile_image',
reverse_order: true,
};
for (let page = 1; page <= 2; page++) {
axiosMock
.onGet(commentsResponsesApiUrl, { params: { ...paramsTemplate, page } })
.reply(200, Factory.build('commentsResult', null, {
[1, 2].forEach(async (page) => {
axiosMock.onGet(commentsResponsesApiUrl, { params: { ...paramsTemplate, page } }).reply(
200,
Factory.build('commentsResult', null, {
parentId,
page,
pageSize: 1,
count: 2,
}));
}
}),
);
await executeThunk(fetchCommentResponses(parentId), store.dispatch, store.getState);
});
}
function renderComponent(postId) {
const wrapper = render(
<IntlProvider locale="en">
<AppProvider store={store}>
<AppProvider store={store} wrapWithRouter={false}>
<DiscussionContext.Provider
value={{ courseId }}
value={{ courseId, postId, page: 'posts' }}
>
<MemoryRouter initialEntries={[`/${courseId}/posts/${postId}`]}>
<DiscussionContent />
<Route
path="*"
/>
</MemoryRouter>
</DiscussionContext.Provider>
</AppProvider>
@@ -102,85 +76,89 @@ function renderComponent(postId) {
}
describe('HoverCard', () => {
beforeEach(() => {
beforeEach(async () => {
initializeMockApp({
authenticatedUser: {
userId: 3,
username: 'abc123',
administrator: true,
roles: [],
isPostingEnabled: true,
},
});
store = initializeStore();
Factory.resetAll();
axiosMock = new MockAdapter(getAuthenticatedHttpClient());
axiosMock.onGet(threadsApiUrl)
.reply(200, Factory.build('threadsResult'));
axiosMock.onPatch(new RegExp(`${commentsApiUrl}*`)).reply(({
url,
data,
}) => {
const commentId = url.match(/comments\/(?<id>[a-z1-9-]+)\//).groups.id;
const {
rawBody,
} = camelCaseObject(JSON.parse(data));
return [200, Factory.build('comment', {
id: commentId,
rendered_body: rawBody,
raw_body: rawBody,
})];
});
axiosMock.onPost(commentsApiUrl)
.reply(({ data }) => {
const {
rawBody,
threadId,
} = camelCaseObject(JSON.parse(data));
return [200, Factory.build(
'comment',
{
rendered_body: rawBody,
raw_body: rawBody,
thread_id: threadId,
},
)];
});
axiosMock.onGet(threadsApiUrl).reply(200, Factory.build('threadsResult'));
axiosMock.onGet(`${getCourseConfigApiUrl()}${courseId}/`).reply(200, { isPostingEnabled: true });
axiosMock.onGet(commentsApiUrl).reply(200, Factory.build('commentsResult', { can_delete: true }, {
threadId: discussionPostId,
endorsed: false,
pageSize: 1,
count: 2,
childCount: 2,
}));
executeThunk(fetchThreads(courseId), store.dispatch, store.getState);
mockAxiosReturnPagedComments();
mockAxiosReturnPagedCommentsResponses();
await executeThunk(fetchCourseConfig(courseId), store.dispatch, store.getState);
await executeThunk(fetchThreads(courseId), store.dispatch, store.getState);
await mockAxiosReturnPagedCommentsResponses();
});
test('it should have hover card on post', async () => {
renderComponent(discussionPostId);
const post = screen.getByTestId('post-thread-1');
const post = await screen.findByTestId('post-thread-1');
expect(within(post).getByTestId('hover-card-thread-1')).toBeInTheDocument();
});
test('it should have hover card on comment', async () => {
renderComponent(discussionPostId);
await waitFor(() => renderComponent(discussionPostId));
const comment = await waitFor(() => screen.findByTestId('comment-comment-1'));
expect(within(comment).getByTestId('hover-card-comment-1')).toBeInTheDocument();
});
test('it should show add response, like, follow and actions menu for hovered post', async () => {
renderComponent(discussionPostId);
await waitFor(() => renderComponent(discussionPostId));
const post = screen.getByTestId('post-thread-1');
const view = within(post).getByTestId('hover-card-thread-1');
expect(within(view).queryByRole('button', { name: /Add response/i })).toBeInTheDocument();
expect(within(view).getByRole('button', { name: /like/i })).toBeInTheDocument();
expect(within(view).queryByRole('button', { name: /follow/i })).toBeInTheDocument();
expect(within(view).queryByRole('button', { name: /actions menu/i })).toBeInTheDocument();
const hoverCard = within(post).getByTestId('hover-card-thread-1');
expect(within(hoverCard).queryByRole('button', { name: /Add response/i })).toBeInTheDocument();
expect(within(hoverCard).getByRole('button', { name: /like/i })).toBeInTheDocument();
expect(within(hoverCard).queryByRole('button', { name: /follow/i })).toBeInTheDocument();
expect(within(hoverCard).queryByRole('button', { name: /actions menu/i })).toBeInTheDocument();
});
test('it should show add comment, Endorse, like and actions menu Buttons for hovered comment', async () => {
renderComponent(questionPostId);
const comment = await waitFor(() => screen.findByTestId('comment-comment-3'));
const view = within(comment).getByTestId('hover-card-comment-3');
expect(within(view).queryByRole('button', { name: /Add comment/i })).toBeInTheDocument();
expect(within(view).getByRole('button', { name: /Endorse/i })).toBeInTheDocument();
expect(within(view).queryByRole('button', { name: /like/i })).toBeInTheDocument();
expect(within(view).queryByRole('button', { name: /actions menu/i })).toBeInTheDocument();
await waitFor(() => renderComponent(discussionPostId));
const comment = await waitFor(() => screen.findByTestId('comment-comment-1'));
const hoverCard = within(comment).getByTestId('hover-card-comment-1');
expect(within(hoverCard).queryByRole('button', { name: /Add comment/i })).toBeInTheDocument();
expect(within(hoverCard).getByRole('button', { name: /Endorse/i })).toBeInTheDocument();
expect(within(hoverCard).queryByRole('button', { name: /like/i })).toBeInTheDocument();
expect(within(hoverCard).queryByRole('button', { name: /actions menu/i })).toBeInTheDocument();
});
test('it should call onFollow when follow button is clicked', async () => {
await waitFor(() => renderComponent(discussionPostId));
const post = screen.getByTestId('post-thread-1');
const hoverCard = within(post).getByTestId('hover-card-thread-1');
const followBtn = within(hoverCard).getByRole('button', { name: /follow/i });
fireEvent.click(followBtn);
expect(followBtn).toBeEnabled();
});
test('it should call onLike when Like button is clicked', async () => {
await waitFor(() => renderComponent(discussionPostId));
const post = screen.getByTestId('post-thread-1');
const hoverCard = within(post).getByTestId('hover-card-thread-1');
const likeBtn = within(hoverCard).getByRole('button', { name: /like/i });
fireEvent.click(likeBtn);
expect(likeBtn).toBeEnabled();
});
});

View File

@@ -1,7 +1,6 @@
/* eslint-disable import/prefer-default-export */
import React from 'react';
export const DiscussionContext = React.createContext({
const DiscussionContext = React.createContext({
page: null,
courseId: null,
postId: null,
@@ -10,3 +9,5 @@ export const DiscussionContext = React.createContext({
category: null,
learnerUsername: null,
});
export default DiscussionContext;

View File

@@ -1,4 +1,4 @@
// eslint-disable-next-line no-unused-vars
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export default function timeLocale(number, index, totalSec) {
return [
['just now', 'right now'],

View File

@@ -0,0 +1,90 @@
import React, {
useCallback, useMemo, useState,
} from 'react';
import PropTypes from 'prop-types';
import { useDispatch, useSelector } from 'react-redux';
import { useIntl } from '@edx/frontend-platform/i18n';
import { RequestStatus } from '../../data/constants';
import { selectConfirmEmailStatus, selectContentCreationRateLimited, selectShouldShowEmailConfirmation } from '../data/selectors';
import { hidePostEditor } from '../posts/data';
import { sendAccountActivationEmail } from '../posts/data/thunks';
import postMessages from '../posts/post-actions-bar/messages';
import { Confirmation } from '.';
const withPostingRestrictions = (WrappedComponent) => {
const EnhancedComponent = ({ onCloseEditor, ...rest }) => {
const intl = useIntl();
const dispatch = useDispatch();
const [isConfirming, setIsConfirming] = useState(false);
const shouldShowEmailConfirmation = useSelector(selectShouldShowEmailConfirmation);
const contentCreationRateLimited = useSelector(selectContentCreationRateLimited);
const confirmEmailStatus = useSelector(selectConfirmEmailStatus);
const openConfirmation = useCallback(() => {
setIsConfirming(true);
}, []);
const closeConfirmation = useCallback(() => {
setIsConfirming(false);
dispatch(hidePostEditor());
onCloseEditor?.();
}, [onCloseEditor]);
const handleConfirmation = useCallback(() => {
dispatch(sendAccountActivationEmail());
}, [dispatch]);
const confirmButtonState = useMemo(() => {
if (confirmEmailStatus === RequestStatus.IN_PROGRESS) { return 'pending'; }
if (confirmEmailStatus === RequestStatus.SUCCESSFUL) { return 'complete'; }
return 'primary';
}, [confirmEmailStatus]);
return (
<>
<WrappedComponent
{...rest}
onCloseEditor={onCloseEditor}
openRestrictionDialogue={openConfirmation}
/>
{shouldShowEmailConfirmation
&& (
<Confirmation
isOpen={isConfirming}
title={intl.formatMessage(postMessages.confirmEmailTitle)}
description={intl.formatMessage(postMessages.confirmEmailDescription)}
onClose={closeConfirmation}
confirmAction={handleConfirmation}
closeButtonVariant="tertiary"
confirmButtonState={confirmButtonState}
confirmButtonText={intl.formatMessage(postMessages.confirmEmailButton)}
closeButtonText={intl.formatMessage(postMessages.closeButton)}
confirmButtonVariant="danger"
/>
)}
{contentCreationRateLimited
&& (
<Confirmation
isOpen={isConfirming}
title={intl.formatMessage(postMessages.postLimitTitle)}
description={intl.formatMessage(postMessages.postLimitDescription)}
onClose={closeConfirmation}
closeButtonText={intl.formatMessage(postMessages.closeButton)}
closeButtonVariant="danger"
/>
)}
</>
);
};
EnhancedComponent.propTypes = {
onCloseEditor: PropTypes.func,
};
return EnhancedComponent;
};
export default withPostingRestrictions;

View File

@@ -0,0 +1,84 @@
import { fireEvent, render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { IntlProvider } from 'react-intl';
import { Context as ResponsiveContext } from 'react-responsive';
import { MemoryRouter } from 'react-router';
import { initializeMockApp } from '@edx/frontend-platform';
import { AppProvider } from '@edx/frontend-platform/react';
import { initializeStore } from '../../store';
import EmptyPosts from '../empty-posts/EmptyPosts';
import messages from '../messages';
import { sendEmailForAccountActivation } from '../posts/data/api';
let store;
const courseId = 'course-v1:edX+DemoX+Demo_Course';
jest.mock('../posts/data/api', () => ({
sendEmailForAccountActivation: jest.fn(),
}));
function renderComponent(location = `/${courseId}/`) {
return render(
<IntlProvider locale="en">
<ResponsiveContext.Provider value={{ width: 1280 }}>
<AppProvider store={store} wrapWithRouter={false}>
<MemoryRouter initialEntries={[location]}>
<EmptyPosts subTitleMessage={messages.emptyMyPosts} />
</MemoryRouter>
</AppProvider>
</ResponsiveContext.Provider>
</IntlProvider>,
);
}
describe('EmptyPage', () => {
beforeEach(async () => {
initializeMockApp({
authenticatedUser: {
userId: 3,
username: 'abc123',
administrator: true,
roles: [],
},
});
store = initializeStore({
config: { provider: 'openedx', onlyVerifiedUsersCanPost: true },
});
});
it('should open the confirmation link dialogue box.', async () => {
renderComponent(`/${courseId}/my-posts/`);
const addPostButton = screen.getByRole('button', { name: 'Add a post' });
await userEvent.click(addPostButton);
expect(screen.queryByText('Send confirmation link')).toBeInTheDocument();
});
it('dispatches sendAccountActivationEmail on confirm', async () => {
sendEmailForAccountActivation.mockResolvedValue({ success: true });
renderComponent(`/${courseId}/my-posts/`);
const addPostButton = screen.getByRole('button', { name: 'Add a post' });
await userEvent.click(addPostButton);
const confirmButton = screen.getByText('Send confirmation link');
fireEvent.click(confirmButton);
expect(sendEmailForAccountActivation).toHaveBeenCalled();
});
it('should close the confirmation dialogue box.', async () => {
renderComponent(`/${courseId}/my-posts/`);
const addPostButton = screen.getByRole('button', { name: 'Add a post' });
await userEvent.click(addPostButton);
const confirmButton = screen.getByText('Close');
fireEvent.click(confirmButton);
expect(sendEmailForAccountActivation).toHaveBeenCalled();
expect(screen.queryByText('Close')).not.toBeInTheDocument();
});
});

View File

@@ -0,0 +1,54 @@
import React, { useCallback } from 'react';
import propTypes from 'prop-types';
import { Button } from '@openedx/paragon';
import classNames from 'classnames';
import { useSelector } from 'react-redux';
import { getConfig } from '@edx/frontend-platform';
import { useIntl } from '@edx/frontend-platform/i18n';
import ContentUnavailableIcon from '../../assets/ContentUnavailable';
import selectCourseTabs from '../../components/NavigationBar/data/selectors';
import { useIsOnTablet, useIsOnXLDesktop } from '../data/hooks';
import messages from '../messages';
const ContentUnavailable = ({ subTitleMessage }) => {
const intl = useIntl();
const isOnTabletorDesktop = useIsOnTablet();
const isOnXLDesktop = useIsOnXLDesktop();
const { courseId } = useSelector(selectCourseTabs);
const redirectToDashboard = useCallback(() => {
window.location.replace(`${getConfig().LMS_BASE_URL}/courses/${courseId}/about`);
}, [courseId]);
return (
<div className="min-content-height justify-content-center align-items-center d-flex w-100 flex-column bg-white">
<div className={classNames('d-flex flex-column align-items-center', {
'content-unavailable-desktop': isOnTabletorDesktop || isOnXLDesktop,
'py-0 px-3': !isOnTabletorDesktop && !isOnXLDesktop,
})}
>
<ContentUnavailableIcon />
<h3 className="pt-3 font-weight-bold text-primary-500 text-center">
{intl.formatMessage(messages.contentUnavailableTitle)}
</h3>
<p className="pb-2 text-gray-500 text-center">{intl.formatMessage(subTitleMessage)}</p>
<Button onClick={redirectToDashboard} variant="outline-dark" className="py-2 px-2.5">
{intl.formatMessage(messages.contentUnavailableAction)}
</Button>
</div>
</div>
);
};
ContentUnavailable.propTypes = {
subTitleMessage: propTypes.shape({
id: propTypes.string,
defaultMessage: propTypes.string,
description: propTypes.string,
}).isRequired,
};
export default React.memo(ContentUnavailable);

View File

@@ -1,5 +1,3 @@
/* eslint-disable import/prefer-default-export */
import { ensureConfig, getConfig } from '@edx/frontend-platform';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
@@ -7,15 +5,16 @@ ensureConfig([
'LMS_BASE_URL',
], 'Posts API service');
export const getCourseConfigApiUrl = () => `${getConfig().LMS_BASE_URL}/api/discussion/v1/courses/`;
export const getCourseConfigApiUrl = () => `${getConfig().LMS_BASE_URL}/api/discussion/v2/courses/`;
export const getCourseSettingsApiUrl = () => `${getConfig().LMS_BASE_URL}/api/discussion/v1/courses/`;
export const getDiscussionsConfigUrl = (courseId) => `${getCourseConfigApiUrl()}${courseId}/`;
export const getDiscussionsSettingsUrl = (courseId) => `${getCourseSettingsApiUrl()}${courseId}/settings`;
/**
* Get discussions course config
* @param {string} courseId
*/
export async function getDiscussionsConfig(courseId) {
const url = `${getCourseConfigApiUrl()}${courseId}/`;
const { data } = await getAuthenticatedHttpClient().get(url);
const { data } = await getAuthenticatedHttpClient().get(getDiscussionsConfigUrl(courseId));
return data;
}
@@ -24,7 +23,7 @@ export async function getDiscussionsConfig(courseId) {
* @param {string} courseId
*/
export async function getDiscussionsSettings(courseId) {
const url = `${getCourseConfigApiUrl()}${courseId}/settings`;
const url = `${getDiscussionsSettingsUrl(courseId)}`;
const { data } = await getAuthenticatedHttpClient().get(url);
return data;
}

View File

@@ -0,0 +1,12 @@
import { selectCommentOrResponseById } from '../post-comments/data/selectors';
import { selectThread } from '../posts/data/selectors';
export const ContentSelectors = {
POST: selectThread,
COMMENT: selectCommentOrResponseById,
};
export const ContentTypes = {
POST: 'POST',
COMMENT: 'COMMENT',
};

View File

@@ -1,70 +1,78 @@
/* eslint-disable import/prefer-default-export */
import {
useContext,
useEffect,
useRef,
useState,
useCallback,
useContext, useEffect, useMemo, useRef, useState,
} from 'react';
import { breakpoints, useWindowSize } from '@openedx/paragon';
import isEmpty from 'lodash/isEmpty';
import { useDispatch, useSelector } from 'react-redux';
import { useHistory, useLocation, useRouteMatch } from 'react-router';
import {
matchPath, useLocation, useMatch, useNavigate,
} from 'react-router-dom';
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
import { useIntl } from '@edx/frontend-platform/i18n';
import { AppContext } from '@edx/frontend-platform/react';
import { breakpoints, useWindowSize } from '@edx/paragon';
import { RequestStatus, Routes } from '../../data/constants';
import selectCourseTabs from '../../components/NavigationBar/data/selectors';
import { LOADED } from '../../components/NavigationBar/data/slice';
import fetchTab from '../../components/NavigationBar/data/thunks';
import { ContentActions, RequestStatus, Routes } from '../../data/constants';
import { selectTopicsUnderCategory } from '../../data/selectors';
import { fetchCourseBlocks } from '../../data/thunks';
import { DiscussionContext } from '../common/context';
import fetchCourseBlocks from '../../data/thunks';
import DiscussionContext from '../common/context';
import { selectTopics as selectInContextTopics } from '../in-context-topics/data/selectors';
import fetchCourseTopicsV3 from '../in-context-topics/data/thunks';
import PostCommentsContext from '../post-comments/postCommentsContext';
import { clearRedirect } from '../posts/data';
import { threadsLoadingStatus } from '../posts/data/selectors';
import { selectTopics } from '../topics/data/selectors';
import fetchCourseTopics from '../topics/data/thunks';
import tourCheckpoints from '../tours/constants';
import { selectTours } from '../tours/data/selectors';
import selectTours from '../tours/data/selectors';
import { updateTourShowStatus } from '../tours/data/thunks';
import messages from '../tours/messages';
import { discussionsPath, inBlackoutDateRange } from '../utils';
import { checkPermissions, discussionsPath } from '../utils';
import { ContentSelectors } from './constants';
import {
selectAreThreadsFiltered,
selectBlackoutDate,
selectDiscussionProvider,
selectEnableInContext,
selectIsCourseAdmin,
selectIsCourseStaff,
selectLearnersTabEnabled,
selectModerationSettings,
selectIsPostingEnabled,
selectIsUserLearner,
selectPostThreadCount,
selectUserHasModerationPrivileges,
selectUserIsGroupTa,
selectUserIsStaff,
} from './selectors';
import { fetchCourseConfig } from './thunks';
import fetchCourseConfig from './thunks';
export function useTotalTopicThreadCount() {
const topics = useSelector(selectTopics);
const count = useMemo(
() => (
Object.keys(topics)?.reduce((total, topicId) => {
const topic = topics[topicId];
return total + topic.threadCounts.discussion + topic.threadCounts.question;
}, 0)),
[],
);
if (!topics) {
return 0;
}
return Object.keys(topics)
.reduce((total, topicId) => {
const topic = topics[topicId];
return total + topic.threadCounts.discussion + topic.threadCounts.question;
}, 0);
return count;
}
export const useSidebarVisible = () => {
const location = useLocation();
const enableInContext = useSelector(selectEnableInContext);
const isViewingTopics = useRouteMatch(Routes.TOPICS.ALL);
const isViewingLearners = useRouteMatch(Routes.LEARNERS.PATH);
const isViewingTopics = useMatch(Routes.TOPICS.ALL);
const isViewingLearners = useMatch(`${Routes.LEARNERS.PATH}/*`);
const isFiltered = useSelector(selectAreThreadsFiltered);
const totalThreads = useSelector(selectPostThreadCount);
const isThreadsEmpty = Boolean(useSelector(threadsLoadingStatus()) === RequestStatus.SUCCESSFUL && !totalThreads);
const isIncontextTopicsView = Boolean(useRouteMatch(Routes.TOPICS.PATH) && enableInContext);
const hideSidebar = Boolean(isThreadsEmpty && !isFiltered && !(isViewingTopics?.isExact || isViewingLearners));
const matchInContextTopicView = Routes.TOPICS.PATH.find((route) => matchPath({ path: `${route}/*` }, location.pathname));
const isInContextTopicsView = Boolean(matchInContextTopicView && enableInContext);
const hideSidebar = Boolean(isThreadsEmpty && !isFiltered && !(isViewingTopics || isViewingLearners));
if (isIncontextTopicsView) {
if (isInContextTopicsView) {
return true;
}
@@ -73,27 +81,59 @@ export const useSidebarVisible = () => {
export function useCourseDiscussionData(courseId) {
const dispatch = useDispatch();
const { authenticatedUser } = useContext(AppContext);
useEffect(() => {
async function fetchBaseData() {
await dispatch(fetchCourseConfig(courseId));
await dispatch(fetchCourseBlocks(courseId, authenticatedUser.username));
await dispatch(fetchTab(courseId));
}
fetchBaseData();
}, [courseId]);
}
export function useCourseBlockData(courseId) {
const dispatch = useDispatch();
const { authenticatedUser } = useContext(AppContext);
const { isEnrolled, courseStatus } = useSelector(selectCourseTabs);
const isUserLearner = useSelector(selectIsUserLearner);
useEffect(() => {
async function fetchBaseData() {
if (courseStatus === LOADED && (!isUserLearner || isEnrolled)) {
await dispatch(fetchCourseBlocks(courseId, authenticatedUser.username));
}
}
fetchBaseData();
}, [courseId, isEnrolled, courseStatus, isUserLearner]);
}
export function useTopicsData(courseId, enableInContextSidebar) {
const dispatch = useDispatch();
const enableInContext = useSelector(selectEnableInContext);
const provider = useSelector(selectDiscussionProvider);
const topics = useSelector(enableInContext ? selectInContextTopics : selectTopics);
useEffect(() => {
if (isEmpty(topics) && provider) {
dispatch((enableInContext || enableInContextSidebar)
? fetchCourseTopicsV3(courseId)
: fetchCourseTopics(courseId));
}
}, [topics, provider, enableInContext, enableInContextSidebar]);
}
export function useRedirectToThread(courseId, enableInContextSidebar) {
const dispatch = useDispatch();
const navigate = useNavigate();
const location = useLocation();
const redirectToThread = useSelector(
(state) => state.threads.redirectToThread,
);
const history = useHistory();
const location = useLocation();
return useEffect(() => {
useEffect(() => {
// After posting a new thread we'd like to redirect users to it, the topic and post id are temporarily
// stored in redirectToThread
if (redirectToThread) {
@@ -103,19 +143,24 @@ export function useRedirectToThread(courseId, enableInContextSidebar) {
postId: redirectToThread.threadId,
topicId: redirectToThread.topicId,
})(location);
history.push(newLocation);
navigate({ ...newLocation });
}
}, [redirectToThread]);
}
export function useIsOnDesktop() {
const windowSize = useWindowSize();
return windowSize.width >= breakpoints.medium.minWidth;
return windowSize.width >= breakpoints.medium.maxWidth;
}
export function useIsOnTablet() {
const windowSize = useWindowSize();
return windowSize.width >= breakpoints.small.maxWidth;
}
export function useIsOnXLDesktop() {
const windowSize = useWindowSize();
return windowSize.width >= breakpoints.extraLarge.minWidth;
return windowSize.width >= breakpoints.extraLarge.maxWidth;
}
/**
@@ -128,9 +173,11 @@ export function useContainerSize(refContainer) {
const resizeObserver = useRef(new ResizeObserver(() => {
/* istanbul ignore if: ResizeObserver isn't available in the testing env */
if (refContainer?.current) {
setHeight(refContainer?.current?.clientHeight);
}
window.requestAnimationFrame(() => {
if (refContainer?.current) {
setHeight(refContainer?.current?.clientHeight);
}
});
}));
useEffect(() => {
@@ -151,22 +198,22 @@ export function useContainerSize(refContainer) {
return height;
}
export const useAlertBannerVisible = (content) => {
export const useAlertBannerVisible = (
{
author, abuseFlagged, lastEdit, closed,
} = {},
) => {
const userHasModerationPrivileges = useSelector(selectUserHasModerationPrivileges);
const userIsGroupTa = useSelector(selectUserIsGroupTa);
const { reasonCodesEnabled } = useSelector(selectModerationSettings);
const userIsContentAuthor = getAuthenticatedUser().username === content.author;
const userIsContentAuthor = getAuthenticatedUser().username === author;
const canSeeLastEditOrClosedAlert = (userHasModerationPrivileges || userIsContentAuthor || userIsGroupTa);
const canSeeReportedBanner = content.abuseFlagged;
const canSeeReportedBanner = abuseFlagged;
return (
(reasonCodesEnabled && canSeeLastEditOrClosedAlert && (content.lastEdit?.reason || content.closed))
|| (content.abuseFlagged && canSeeReportedBanner)
(canSeeLastEditOrClosedAlert && (lastEdit?.reason || closed)) || (canSeeReportedBanner)
);
};
export const useShowLearnersTab = () => useSelector(selectLearnersTabEnabled);
/**
* React hook that gets the current topic ID from the current topic or category.
* The topicId in the DiscussionContext only return the direct topicId from the URL.
@@ -188,41 +235,45 @@ export const useCurrentDiscussionTopic = () => {
return null;
};
export const useUserCanAddThreadInBlackoutDate = () => {
const blackoutDateRange = useSelector(selectBlackoutDate);
const isUserAdmin = useSelector(selectUserIsStaff);
const userHasModerationPrivilages = useSelector(selectUserHasModerationPrivileges);
export const useUserPostingEnabled = () => {
const isPostingEnabled = useSelector(selectIsPostingEnabled);
const userHasModerationPrivileges = useSelector(selectUserHasModerationPrivileges);
const isUserGroupTA = useSelector(selectUserIsGroupTa);
const isCourseAdmin = useSelector(selectIsCourseAdmin);
const isCourseStaff = useSelector(selectIsCourseStaff);
const isInBlackoutDateRange = inBlackoutDateRange(blackoutDateRange);
const isPrivileged = userHasModerationPrivileges || isUserGroupTA;
return (!(isInBlackoutDateRange)
|| (isUserAdmin || userHasModerationPrivilages || isUserGroupTA || isCourseAdmin || isCourseStaff));
return (isPostingEnabled || isPrivileged);
};
function camelToConstant(string) {
return string.replace(/[A-Z]/g, (match) => `_${match}`).toUpperCase();
}
export const useTourConfiguration = (intl) => {
export const useTourConfiguration = () => {
const intl = useIntl();
const dispatch = useDispatch();
const { enableInContextSidebar } = useContext(DiscussionContext);
const tours = useSelector(selectTours);
return tours.map((tour) => (
{
tourId: tour.tourName,
advanceButtonText: intl.formatMessage(messages.advanceButtonText),
dismissButtonText: intl.formatMessage(messages.dismissButtonText),
endButtonText: intl.formatMessage(messages.endButtonText),
enabled: tour && Boolean(tour.showTour && !enableInContextSidebar),
onDismiss: () => dispatch(updateTourShowStatus(tour.id)),
onEnd: () => dispatch(updateTourShowStatus(tour.id)),
checkpoints: tourCheckpoints(intl)[camelToConstant(tour.tourName)],
}
));
const handleOnDismiss = useCallback((id) => (
dispatch(updateTourShowStatus(id))
), []);
const handleOnEnd = useCallback((id) => (
dispatch(updateTourShowStatus(id))
), []);
const toursConfig = useMemo(() => (
tours?.map((tour) => Object.keys(tourCheckpoints(intl)).includes(tour.tourName) && (
{
tourId: tour.tourName,
advanceButtonText: intl.formatMessage(messages.advanceButtonText),
dismissButtonText: intl.formatMessage(messages.dismissButtonText),
endButtonText: intl.formatMessage(messages.endButtonText),
enabled: tour && Boolean(tour.enabled && tour.showTour && !enableInContextSidebar),
onDismiss: () => handleOnDismiss(tour.id),
onEnd: () => handleOnEnd(tour.id),
checkpoints: tourCheckpoints(intl)[tour.tourName],
}
))
), [tours, enableInContextSidebar]);
return toursConfig;
};
export const useDebounce = (value, delay) => {
@@ -245,3 +296,10 @@ export const useDebounce = (value, delay) => {
);
return debouncedValue;
};
export const useHasLikePermission = (contentType, id) => {
const { postType } = useContext(PostCommentsContext);
const content = { ...useSelector(ContentSelectors[contentType](id)), postType };
return checkPermissions(content, ContentActions.VOTE);
};

Some files were not shown because too many files have changed in this diff Show More