Compare commits

..

55 Commits

Author SHA1 Message Date
lebaudantoine d7e1e8032f 🔧(backend) fix sentry deprecated scope
`sentry_sdk.configure_scope` is deprecated and will
be removed in the next major version.

(commit taken from people by @qbey)
2025-01-14 09:43:33 +01:00
lebaudantoine a4cd6f4dfb 🎨(backend) removed unused imports
Please challenge this commit.
I feel these imports aren't used in this migration file.
2025-01-14 09:43:33 +01:00
lebaudantoine 9ccfb3183a 🎨(backend) remove redundant parentheses
These parentheses seem useless to me, remove them.
2025-01-14 09:43:33 +01:00
lebaudantoine 8655ffa8c2 ✏️(typo) fix minor typos
Found typos while working on the project using my IDE, fixed them.
Sorry for the big commit.

Not a big deal, can totally drop this commit.
2025-01-14 09:43:33 +01:00
lebaudantoine 194ac8e856 🎨(backend) remove unused variable
output seems to be redefined few lines after.
Please feel free to challenge this change.
2025-01-14 09:43:33 +01:00
lebaudantoine bc67d1b978 🚨(backend) fix Django deprecation warning for format_html
Resolved RemovedInDjango60Warning by ensuring format_html() is called
with required arguments, addressing compatibility with Django 6.0.

/!\ Fix by
Claude, need real-world testing. Linterand tests pass.
2025-01-14 09:43:33 +01:00
lebaudantoine a32036ba8c 🚨(backend) fix Django UnorderedObjectListWarning on User
Found this solution googling on Stack Overflow.
Without a default ordering on a model, Django raises a warning, that
pagination may yield inconsistent results.

Please feel free to challenge my fix.
2025-01-14 09:43:33 +01:00
lebaudantoine 30aab3fb9d 🚨(backend) fix Django deprecation warning in Factory
_after_postgeneration method will stop saving the instance after
postgeneration hooks in the next major release.

Solved using Claude, feel free to challenge my fix.
2025-01-13 23:07:27 +01:00
lebaudantoine 954ae0e510 🚨(backend) fix CheckConstraint deprecation warning
Fix a deprecation warning from Django, which appears while running
tests. 'check' argument is replaced by 'condition'.
2025-01-13 23:07:27 +01:00
Anthony LC a090f180f4 💄(frontend) make favicon more visible
In dark mode, the favicon is not correctly visible.
This commit makes it more visible by adding some
white color to the favicon.
2025-01-13 18:15:25 +01:00
Anthony LC c7e543d459 🐛(frontend) switch to other provider
When we redirect from a doc to another, the components
are not unmounted and states are not reset.
We now destroy the provider if we see that
the provider is not bind to the current doc.
2025-01-13 18:15:25 +01:00
Anthony LC 23e6b508f8 🚚(frontend) harmonize imports
Harmonize the imports in the frontend codebase
to keep the codebase consistent.
2025-01-13 11:02:24 +01:00
Nathan Panchout 49a3989977 💄(frontend) fix minor bugs and enhance DocTitle and DocShareModal
- Fixed minor bugs in the frontend codebase for improved stability.
- Enhanced DocTitle component to update title display dynamically using
useEffect.
- Refactored DocShareModal to improve modal content height calculation
for better responsiveness.
2025-01-13 11:02:24 +01:00
Nathan Panchout 8eb2b60937 (frontend) enhance document grid tests and component interactions
- Updated test cases to replace 'docs-grid-loader' with 'grid-loader'
for improved consistency across document grid tests.
- Refactored document interaction tests to utilize the 'docs-grid'
locator for better readability and maintainability.
- Enhanced document table content tests by refining element selection
methods for improved clarity and performance.
- Cleaned up test code to ensure better structure and maintainability.
2025-01-13 11:02:24 +01:00
Nathan Panchout f02dcae52a (frontend) enhance DocShareModal and footer components
- Refactored DocShareModal to improve user experience with dynamic list
height and responsive design adjustments.
- Introduced new styling for modal elements using createGlobalStyle for
better visual consistency.
- Updated footer button text from 'Ok' to 'OK' for improved clarity.
- Enhanced user selection handling and search functionality within the
modal for better document sharing experience.
2025-01-13 11:02:24 +01:00
Nathan Panchout 098df5c0b5 (frontend) enhance UI components and improve document management
- Updated DropdownMenu to include index-based styling for better visual
consistency.
- Refactored QuickSearchStyle to remove unnecessary transitions for
smoother performance.
- Adjusted modal styles in cunningham-style.css for improved layout.
- Changed BlockNoteEditor to update block type from 'heading' to
'paragraph' for better content structure.
- Enhanced DocHeader and DocToolBox components with updated color themes
for improved visibility.
- Modified ModalRemoveDoc to change size and clean up unnecessary props
for better usability.
- Improved Heading and TableContent components to handle empty states
more gracefully.
- Updated DocsGrid to conditionally render content based on document
availability, enhancing user experience.
- Refined LeftPanel components for better layout and visual hierarchy,
including adjustments to padding and separators.
2025-01-13 11:02:24 +01:00
Nathan Panchout 684b77cbe6 (frontend) enhance DocsGrid component and simplify layout
- add scroll inside the doc grid
2025-01-13 11:02:24 +01:00
Nathan Panchout 81e9fc49fe (frontend) enhance document interaction tests
- Updated test cases to improve accessibility by replacing 'more_vert'
with 'more_horiz' for action buttons across various components.
- Refactored document deletion confirmation messages to use consistent
heading roles for better visibility.
- Simplified keyboard interactions in document table content tests for
improved clarity.
- Adjusted visibility checks for the share button to utilize more
descriptive labels.
- Cleaned up test code for maintainability and consistency.
2025-01-13 11:02:24 +01:00
Nathan Panchout 7c696fc1ec (frontend) enhance document sharing components
- Refactored DocShareAddMemberList to simplify button styling and
improve loading state handling.
- Updated DocShareAddMemberListItem and DocShareMemberItem to enhance
spacing and button color for better visual consistency.
- Improved DocShareInvitationItem and SearchUserRow with new theming and
spacing tokens for a more cohesive design.
- Adjusted padding and layout in DocShareModal and DocShareModalFooter
for improved responsiveness.
- Enhanced DocVisibility component with updated padding and text styling
for better readability.
- Cleaned up unused imports and optimized component structures for
maintainability.
2025-01-13 11:02:24 +01:00
Nathan Panchout fc27043e9e (frontend) enhance document editor and header components
- Improved styling for headings in BlockNoteEditor for better visual
hierarchy.
- Adjusted padding in DocEditor and DocHeader based on device type for
responsive design.
- Updated DocTitle and ModalExport components to enhance typography and
spacing.
- Refactored DocToolBox to improve share button functionality and access
display.
- Enhanced versioning modal with better layout and accessibility
features.
- Cleaned up unused imports and optimized component structures for
maintainability.
2025-01-13 11:02:24 +01:00
Nathan Panchout 6ad1e27acf (frontend) enhance UI components and improve styling
- Updated DropdownMenu and ButtonLogin components for better
accessibility and visual consistency.
- Refactored Header and Title components to utilize new theming and
spacing tokens.
- Enhanced LanguagePicker styles for improved user experience.
- Introduced new utility functions in doc-management for better handling
of ProseMirror nodes and Yjs integration.
- Cleaned up unused imports and adjusted component styles for overall
code maintainability.
2025-01-13 11:02:24 +01:00
Nathan Panchout 899047d9a2 (frontend) enhance QuickSearch components
- Updated QuickSearchItemContent to ensure full width for better layout
consistency.
- Adjusted padding in QuickSearchStyle for improved spacing and visual
hierarchy.
- Refactored DocSearchItem to utilize Box component for consistent
styling and layout.
- Removed unused imports in DocSearchItem to streamline the codebase.
2025-01-13 11:02:24 +01:00
Nathan Panchout 78b5e2c1cc (frontend) enhance document grid
- Updated the layout and styling of the DocsGrid and DocsGridItem
components for improved responsiveness and visual consistency.
- Added a new background prop to the UserAvatar component for
customizable user avatars.
- Enhanced the DocsGridActions component to include a share option,
allowing users to share documents easily.
- Refactored SVG assets for pinned and simple documents to improve their
dimensions and visual representation.
- Improved the SimpleDocItem component to display document update times
and access indicators more effectively.
- Adjusted padding and spacing across various components to enhance
overall user experience.
2025-01-13 11:02:24 +01:00
Nathan Panchout 72f234027c (frontend) enhance left panel components
- Updated padding and radius styles in LeftPanelTargetFilters and
LeftPanelFavorites for improved layout consistency.
- Introduced LeftPanelDocContent component to display document details
when navigating to specific documentation pages.
- Enhanced LeftPanelContent to conditionally render LeftPanelDocContent
based on the current route.
- Adjusted LeftPanelHeader button colors for better visual hierarchy.
- Refactored MainLayout padding for a more responsive design.
2025-01-13 11:02:24 +01:00
Nathan Panchout 730efe7b74 (frontend) update color tokens and styles
- Modified color tokens for danger and info categories to enhance visual
consistency and accessibility.
- Updated button and modal styles, including adjustments to padding and
dimensions for improved layout.
- Replaced font files for Marianne with updated versions to ensure
better typography.
2025-01-13 11:02:24 +01:00
Nathan Panchout 63885117e1 (frontend) implement document favorites feature
- Added functionality to mark documents as favorites, including new
hooks `useMakeFavoriteDoc` and `useRemoveFavoriteDoc` for managing
favorite status.
- Enhanced the document management API to support favorite filtering
with the `is_favorite` parameter.
- Created a new e2e test for the favorite workflow to ensure proper
functionality.
- Updated the UI components to reflect favorite status, including
changes in `DocsGridActions`, `DocsGridItem`, and the new
`LeftPanelFavorites` component for displaying pinned documents.
- Adjusted SVG assets for better visual representation of pinned
documents.
2025-01-13 11:02:24 +01:00
Nathan Panchout 4f4c8905ff (frontend) update tests
- Enhanced test coverage for document sharing, member roles, and
visibility settings
2025-01-13 11:02:24 +01:00
Nathan Panchout a5f6cb542d 🔥(frontend) remove unused document management components
- Deleted `DocVisibility`, `ModalShare`, `InvitationList`, `MemberList`,
and related components to streamline the document management feature.
- Updated component exports to reflect the removal of these components.
- Cleaned up associated assets and styles to improve code
maintainability.
2025-01-13 11:02:24 +01:00
Nathan Panchout 8456f47260 (frontend) enhance dropdown components and add new LoadMoreText feature
- Updated DropButton and DropdownMenu components to include new props
for accessibility and improved layout.
- Introduced LoadMoreText component for better user experience in
loading additional content.
- Added SearchUserRow and UserAvatar components for improved user search
functionality.
- Cleaned up unused imports and adjusted styles for better consistency
across components.
2025-01-13 11:02:24 +01:00
Nathan Panchout eb35fdc7a9 (frontend) enhance document sharing features and role management
- Introduced new hooks and components for improved document sharing
functionality, including `useTranslatedShareSettings` and
`DocShareModal`.
- Added role management capabilities with `DocRoleDropdown` and
`DocShareAddMemberList` components, allowing users to manage document
access and roles effectively.
- Implemented user invitation handling with `DocShareInvitationItem` and
`DocShareMemberItem` components, enhancing the user experience for
managing document collaborators.
- Updated translation handling for role and visibility settings to
ensure consistency across the application.
- Refactored existing components to integrate new features and improve
overall code organization.
2025-01-13 11:02:24 +01:00
Nathan Panchout ceaf1e28f9 (frontend) refactor QuickSearch components
- Simplified QuickSearchProps by removing unused properties and
enhancing type definitions.
- Updated QuickSearch component to utilize children for rendering,
improving flexibility.
- Added separator prop to QuickSearchInput for better control over
layout.
- Removed data prop from DocSearchModal's QuickSearch to streamline the
component's usage.
2025-01-13 11:02:24 +01:00
Nathan Panchout c3da23f5d3 (frontend) add new color tokens and utility classes
- Introduced a comprehensive set of color tokens for blue, brown, cyan,
gold, green, olive, orange, pink, purple, and yellow shades.
2025-01-13 11:02:24 +01:00
Nathan Panchout 44784b2236 (frontend) implement document search functionality
- Added a new DocSearchModal component for searching documents.
- Introduced DocSearchItem component to display individua
 document results.
- Enhanced the useDocs API to support title-based searching.
- Implemented e2e tests for document search visibility and
functionality.
- Included an empty state illustration for no search results.
- Updated the LeftPanelHeader to open the document search modal.
2025-01-13 11:02:24 +01:00
Nathan Panchout 157f6200f2 (frontend) add Quick Search component suite
- Introduced a new Quick Search feature with multiple components
- Implemented styling for the Quick Search components to
ensure a cohesive look and feel across the application.
2025-01-13 11:02:24 +01:00
Nathan Panchout 2882348547 (frontend) update dependencies and enhance package configurations
- Added new dependencies: `luxon` and its type definitions
to the e2e app
- Introduced `cmdk` and `use-debounce` to the impress
app for enhanced UI components and debouncing functionality.
2025-01-13 11:02:24 +01:00
Nathan Panchout e016cfab70 🐛(frontend) fix document editor height and update translations
- Adjusted the document editor height in the DocEditor component
- Updated translations for various terms to ensure consistency
cross the application.
- Improved layout and spacing in the DocsGridItem
component for a cleaner presentation.
2025-01-13 11:02:24 +01:00
Nathan Panchout 23b11e4096 💄(frontend) update document summary UI
- Enhanced the document summary UI for better visibility
and interaction.
- Refactored the DocHeader and DocEditor components to
 improve layout and responsiveness.
- Updated tests for the DocTableContent to reflect changes
in heading interactions and visibility checks.
2025-01-13 11:02:24 +01:00
Nathan Panchout 7696872416 (frontend) implement document filtering
- Introduced a new enum for default document filters
to improve code clarity.
- Updated the API call to support filtering documents
based on the creator.
- Enhanced the DocsGrid component to accept a target
filter, allowing dynamic content rendering based on user selection.
- Modified the main layout to include a left panel for improved
navigation and user experience.
- Added a new test suite for document filters, verifying the visibility
and selection states of 'All docs', 'My docs', and 'Shared with me'.
2025-01-13 11:02:24 +01:00
Nathan Panchout 42d9fa70a2 🔧(frontend) remove deprecated routes and update service worker
- Removed the versioning route from the default configuration to
streamline the documentation structure.
- Updated the service worker to eliminate references to the deprecated
 versioning fallback, enhancing the offline experience for users.
2025-01-13 11:02:24 +01:00
Nathan Panchout a8a89def98 (frontend) enhance document versioning and loading experience
- Updated tests for document member list and versioning to utilize
'Load more' button instead of mouse wheel scrolling.
- Improved UI for document versioning, including visibility
checks and modal interactions.
- Refactored InfiniteScroll component to include a button for
loading more items, enhancing user experience.
- Adjusted DocEditor and DocHeader components to handle
version IDs more effectively.
- Removed deprecated versioning pages to streamline the codebase.
2025-01-13 11:02:24 +01:00
Nathan Panchout 5bcce0c64a 💄(frontend) update doc export modal
In the new interface, the export modal changes a little.

- We put the buttons on the right
- We remove the alert
- We transform the radio into select
2025-01-13 11:02:24 +01:00
Nathan Panchout 3a738fe701 (frontend) adapt all tests related to the new header
Since we no longer use an editable div but an input, we must
modify the tests accordingly
2025-01-13 11:02:24 +01:00
Nathan Panchout d5670640f5 (frontend) update doc header ui
Modification of the header style to be consistent with the new UI :
- We replace the option menu with the DropdownMenu component
- We add a dowload button
- We put an input in place of an editable div.
2025-01-13 11:02:24 +01:00
Nathan Panchout 1d85eee78f 💄(frontend) add dropdown option for DocGridItem
Implement dropdown menu with functionality to delete a document
from the list
2025-01-13 11:02:24 +01:00
Nathan Panchout 5a46ab0055 (frontend) update tests to align with the new interface changes
- Adjust selectors and assertions to reflect updates in the UI layout and
design.
- Ensure all modified tests maintain compatibility with the updated structure.
- Fix any broken test cases caused by the redesign.
2025-01-13 11:02:24 +01:00
Nathan Panchout 3d5ff93a51 💄(frontend) update DocsGrid component
Implement the new version of  the DocsGrid  component
2025-01-13 11:02:24 +01:00
Nathan Panchout b9c66c7c2a 🔧(frontend) update cunningham configuration
- update primary colors,and spacing.
- update tertiary button
2025-01-13 11:02:24 +01:00
Nathan Panchout 68a390ef59 (frontend) add react-intersection-observer package
- Install `react-intersection-observer` to manage element visibility detection.
- Enables features like lazy loading, animations on scroll, and triggering
events when elements appear in the viewport.
2025-01-13 11:02:24 +01:00
Nathan Panchout 192ab1121c 🔥(frontend) remove unused components due to new interface
Deleted two components that were no longer needed following the
implementation of the new interface. This cleanup helps streamline
he codebase and avoid unnecessary maintenance.
2025-01-13 11:02:24 +01:00
Nathan Panchout 83eb33d54a 💄(frontend) updating the header and leftpanel for responsive
Previously we added a left panel. We now need to adapt the layout
so that it becomesresponsive.

We therefore add a burger menu on the left on mobile which,
when clicked, deploys the left-panel over all the content.
2025-01-13 11:02:24 +01:00
Nathan Panchout ee937de2c4 (frontend) update tests
Some minor changes have been integrated into the list of documents.
The tests must therefore be adapted accordingly.
2025-01-13 11:02:24 +01:00
Nathan Panchout 8d514bd571 💄(frontend) add left panel
In the new interface there is a new left panel. We implement it and add it
to the MainLayout
2025-01-13 11:02:24 +01:00
Nathan Panchout e83c404e21 💄(frontend) add cunningham tokens
In order to use the spaces and grays of the DSFR,
we update the cunningham.ts file
2025-01-13 11:02:24 +01:00
Samuel Paccoud - DINUM 945f55f50d (backend) add test to secure updating user when matched on email
We had doubts that the user was correctly updated in the case where
its identity was matched on the email and not on the sub. I added
a test and confirmed that it was working correctly. I still modified
the backend to update the user based on its "id" instead of its "sub"
because it was confusing, but both actually work the same.
2025-01-10 19:30:17 +01:00
Samuel Paccoud - DINUM 9f83ea7111 ♻️(backend) rename required claims to essential claims as per spec
It was pointed by @lebaudantoine that the OIDC specification uses
the term "essential claims" for what we called required claims.

Further more, the Mozilla OIDC library that we use, validates claims
in a method called "verify_claims". Let's override this method.
2025-01-10 19:30:17 +01:00
112 changed files with 949 additions and 858 deletions
-1
View File
@@ -6,7 +6,6 @@ on:
push:
branches:
- 'main'
- 'main-new-ui'
tags:
- 'v*'
pull_request:
+12 -17
View File
@@ -10,28 +10,25 @@ and this project adheres to
## [Unreleased]
## Added
- 🔧(backend) add option to configure list of required OIDC claims #525
- 🔧(backend) add option to configure list of essential OIDC claims #525 & #531
- 🔧(helm) add option to disable default tls setting by @dominikkaminski #519
- ✨(frontend) WIP: New ui
- 💄(frontend) Add left panel #420
- 💄(frontend) add filtering to left panel #475
- ✨(frontend) new share modal ui #489
- ✨(frontend) add favorite feature #515
## Changed
- 🏗️(yjs-server) organize yjs server #528
- ♻️(frontend) better separation collaboration process #528
- 💄(frontend) updating the header and leftpanel for responsive #421
- 💄(frontend) update DocsGrid component #431
- 💄(frontend) update DocsGridOptions component #432
- 💄(frontend) update DocHeader ui #446
- 💄(frontend) update doc versioning ui #463
- 💄(frontend) update doc summary ui #473
- 🐛(frontend) fix doc grid button #478
- ✨(backend) add server-to-server API endpoint to create documents #467
- ✨(frontend) new share modal ui #489
- ✨(frontend) add favorite feature #515
- ✨(frontend) many ui fixes #524
- 💄(frontend) fix the ux of the new ui #539
- 💄(frontend) fix minor bugs #546
## Changed
- 💄(frontend) add filtering from left panel #475
- 🏗️(yjs-server) organize yjs server #528
- ♻️(frontend) better separation collaboration process #528
## [1.10.0] - 2024-12-17
@@ -98,8 +95,6 @@ and this project adheres to
- 🌐(backend) add German translation #259
- 🌐(frontend) add German translation #255
- ✨(frontend) add a broadcast store #387
- ✨(backend) config endpoint #425
- 💄(frontend) update DocsGrid component #431
- ✨(backend) whitelist pod's IP address #443
- ✨(backend) config endpoint #425
- ✨(frontend) config endpoint #424
@@ -202,7 +197,7 @@ and this project adheres to
- 🛂(frontend) match email if no existing user matches the sub
- 🐛(backend) gitlab oicd userinfo endpoint #232
- 🛂(frontend) redirect to the OIDC when private doc and unauthentified #292
- 🛂(frontend) redirect to the OIDC when private doc and unauthenticated #292
- ♻️(backend) getting list of document versions available for a user #258
- 🔧(backend) fix configuration to avoid different ssl warning #297
- 🐛(frontend) fix editor break line not working #302
@@ -331,7 +326,7 @@ and this project adheres to
- ⚡️(e2e) unique login between tests (#80)
- ⚡️(CI) improve e2e job (#86)
- ♻️(frontend) improve the error and message info ui (#93)
- ✏️(frontend) change all occurences of pad to doc (#99)
- ✏️(frontend) change all occurrences of pad to doc (#99)
## Fixed
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

+1 -1
View File
@@ -6,7 +6,7 @@ Whenever we are cooking a new release (e.g. `4.18.1`) we should follow a standar
2. Bump the release number for backend project, frontend projects, and Helm files:
- for backend, update the version number by hand in `pyproject.toml`,
- for each projects (`src/frontend`, `src/frontend/apps/*`, `src/frontend/packages/*`, `src/mail`), run `yarn version --new-version --no-git-tag-version 4.18.1` in their directory. This will update their `package.json` for you,
- for each project (`src/frontend`, `src/frontend/apps/*`, `src/frontend/packages/*`, `src/mail`), run `yarn version --new-version --no-git-tag-version 4.18.1` in their directory. This will update their `package.json` for you,
- for Helm, update Docker image tag in files located at `src/helm/env.d` for both `preprod` and `production` environments:
```yaml
+6 -5
View File
@@ -140,6 +140,7 @@ class UserViewSet(
permission_classes = [permissions.IsSelf]
queryset = models.User.objects.all()
serializer_class = serializers.UserSerializer
ordering = ["-created_at"]
def get_queryset(self):
"""
@@ -629,7 +630,7 @@ class DocumentViewSet(
nginx.ingress.kubernetes.io/auth-url annotation to understand how the Nginx ingress
is configured to do this.
Based on the original url and the logged in user, we must decide if we authorize Nginx
Based on the original url and the logged-in user, we must decide if we authorize Nginx
to let this request go through (by returning a 200 code) or if we block it (by returning
a 403 error). Note that we return 403 errors without any further details for security
reasons.
@@ -834,7 +835,7 @@ class DocumentAccessViewSet(
serializer_class = serializers.DocumentAccessSerializer
def perform_create(self, serializer):
"""Add a new access to the document and send an email to the new added user."""
"""Add new access to the document and email the new added user."""
access = serializer.save()
language = self.request.headers.get("Content-Language", "en-us")
@@ -846,7 +847,7 @@ class DocumentAccessViewSet(
)
def perform_update(self, serializer):
"""Update an access to the document and notify the collaboration server."""
"""Update access to the document and notify the collaboration server."""
access = serializer.save()
access_user_id = None
@@ -859,7 +860,7 @@ class DocumentAccessViewSet(
)
def perform_destroy(self, instance):
"""Delete an access to the document and notify the collaboration server."""
"""Delete access to the document and notify the collaboration server."""
instance.delete()
# Notify collaboration server about the access removed
@@ -1098,7 +1099,7 @@ class InvitationViewset(
return queryset
def perform_create(self, serializer):
"""Save invitation to a document then send an email to the invited user."""
"""Save invitation to a document then email the invited user."""
invitation = serializer.save()
language = self.request.headers.get("Content-Language", "en-us")
+27 -24
View File
@@ -1,5 +1,7 @@
"""Authentication Backends for the Impress core app."""
import logging
from django.conf import settings
from django.core.exceptions import SuspiciousOperation
from django.utils.translation import gettext_lazy as _
@@ -11,6 +13,8 @@ from mozilla_django_oidc.auth import (
from core.models import User
logger = logging.getLogger(__name__)
class OIDCAuthenticationBackend(MozillaOIDCAuthenticationBackend):
"""Custom OpenID Connect (OIDC) Authentication Backend.
@@ -57,24 +61,31 @@ class OIDCAuthenticationBackend(MozillaOIDCAuthenticationBackend):
_("Invalid response format or token verification failed")
) from e
# Validate required claims
missing_claims = [
claim
for claim in settings.USER_OIDC_REQUIRED_CLAIMS
if claim not in userinfo
]
if missing_claims:
raise SuspiciousOperation(
_("Missing required claims in user info: %(claims)s")
% {"claims": ", ".join(missing_claims)}
)
return userinfo
def verify_claims(self, claims):
"""
Verify the presence of essential claims and the "sub" (which is mandatory as defined
by the OIDC specification) to decide if authentication should be allowed.
"""
essential_claims = settings.USER_OIDC_ESSENTIAL_CLAIMS
missing_claims = [claim for claim in essential_claims if claim not in claims]
if missing_claims:
logger.error("Missing essential claims: %s", missing_claims)
return False
return True
def get_or_create_user(self, access_token, id_token, payload):
"""Return a User based on userinfo. Create a new user if no match is found."""
user_info = self.get_userinfo(access_token, id_token, payload)
if not self.verify_claims(user_info):
raise SuspiciousOperation("Claims verification failed.")
sub = user_info["sub"]
email = user_info.get("email")
# Get user's full name from OIDC fields defined in settings
@@ -87,12 +98,6 @@ class OIDCAuthenticationBackend(MozillaOIDCAuthenticationBackend):
"short_name": short_name,
}
sub = user_info.get("sub")
if not sub:
raise SuspiciousOperation(
_("User info contained no recognizable user identification")
)
user = self.get_existing_user(sub, email)
if user:
@@ -113,15 +118,13 @@ class OIDCAuthenticationBackend(MozillaOIDCAuthenticationBackend):
return full_name or None
def get_existing_user(self, sub, email):
"""Fetch existing user by sub or email."""
"""Fetch an existing user by sub (or email as a fallback respecting fallback setting."""
try:
return User.objects.get(sub=sub)
except User.DoesNotExist:
if email and settings.OIDC_FALLBACK_TO_EMAIL_FOR_IDENTIFICATION:
try:
return User.objects.get(email=email)
except User.DoesNotExist:
pass
return User.objects.filter(email=email).first()
return None
def update_user_if_needed(self, user, claims):
@@ -131,4 +134,4 @@ class OIDCAuthenticationBackend(MozillaOIDCAuthenticationBackend):
)
if has_changed:
updated_claims = {key: value for key, value in claims.items() if value}
self.UserModel.objects.filter(sub=user.sub).update(**updated_claims)
self.UserModel.objects.filter(id=user.id).update(**updated_claims)
+2 -2
View File
@@ -2,7 +2,7 @@
from django.urls import path
from mozilla_django_oidc.urls import urlpatterns as mozzila_oidc_urls
from mozilla_django_oidc.urls import urlpatterns as mozilla_oidc_urls
from .views import OIDCLogoutCallbackView, OIDCLogoutView
@@ -14,5 +14,5 @@ urlpatterns = [
OIDCLogoutCallbackView.as_view(),
name="oidc_logout_callback",
),
*mozzila_oidc_urls,
*mozilla_oidc_urls,
]
+5
View File
@@ -19,6 +19,7 @@ class UserFactory(factory.django.DjangoModelFactory):
class Meta:
model = models.User
skip_postgeneration_save = True
sub = factory.Sequence(lambda n: f"user{n!s}")
email = factory.Faker("email")
@@ -36,6 +37,8 @@ class UserFactory(factory.django.DjangoModelFactory):
if create and (extracted is True):
UserDocumentAccessFactory(user=self, role="owner")
self.save()
@factory.post_generation
def with_owned_template(self, create, extracted, **kwargs):
"""
@@ -45,6 +48,8 @@ class UserFactory(factory.django.DjangoModelFactory):
if create and (extracted is True):
UserTemplateAccessFactory(user=self, role="owner")
self.save()
class DocumentFactory(factory.django.DjangoModelFactory):
"""A factory to create documents"""
+2 -4
View File
@@ -1,7 +1,5 @@
# Generated by Django 5.0.3 on 2024-05-28 20:29
import django.contrib.auth.models
import django.core.validators
import django.db.models.deletion
import timezone_field.fields
import uuid
@@ -145,7 +143,7 @@ class Migration(migrations.Migration):
),
migrations.AddConstraint(
model_name='documentaccess',
constraint=models.CheckConstraint(check=models.Q(models.Q(('team', ''), ('user__isnull', False)), models.Q(('team__gt', ''), ('user__isnull', True)), _connector='OR'), name='check_document_access_either_user_or_team', violation_error_message='Either user or team must be set, not both.'),
constraint=models.CheckConstraint(condition=models.Q(models.Q(('team', ''), ('user__isnull', False)), models.Q(('team__gt', ''), ('user__isnull', True)), _connector='OR'), name='check_document_access_either_user_or_team', violation_error_message='Either user or team must be set, not both.'),
),
migrations.AddConstraint(
model_name='invitation',
@@ -161,6 +159,6 @@ class Migration(migrations.Migration):
),
migrations.AddConstraint(
model_name='templateaccess',
constraint=models.CheckConstraint(check=models.Q(models.Q(('team', ''), ('user__isnull', False)), models.Q(('team__gt', ''), ('user__isnull', True)), _connector='OR'), name='check_template_access_either_user_or_team', violation_error_message='Either user or team must be set, not both.'),
constraint=models.CheckConstraint(condition=models.Q(models.Q(('team', ''), ('user__isnull', False)), models.Q(('team__gt', ''), ('user__isnull', True)), _connector='OR'), name='check_template_access_either_user_or_team', violation_error_message='Either user or team must be set, not both.'),
),
]
@@ -0,0 +1,17 @@
# Generated by Django 5.1.4 on 2025-01-13 22:57
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0012_make_document_creator_and_invitation_issuer_optional'),
]
operations = [
migrations.AlterModelOptions(
name='user',
options={'ordering': ('-created_at',), 'verbose_name': 'user', 'verbose_name_plural': 'users'},
),
]
+6 -6
View File
@@ -155,7 +155,7 @@ class User(AbstractBaseUser, BaseModel, auth_models.PermissionsMixin):
email = models.EmailField(_("identity email address"), blank=True, null=True)
# Unlike the "email" field which stores the email coming from the OIDC token, this field
# stores the email used by staff users to login to the admin site
# stores the email used by staff users to log in to the admin site
admin_email = models.EmailField(
_("admin email address"), unique=True, blank=True, null=True
)
@@ -199,6 +199,7 @@ class User(AbstractBaseUser, BaseModel, auth_models.PermissionsMixin):
class Meta:
db_table = "impress_user"
ordering = ("-created_at",)
verbose_name = _("user")
verbose_name_plural = _("users")
@@ -695,7 +696,7 @@ class DocumentAccess(BaseAccess):
violation_error_message=_("This team is already in this document."),
),
models.CheckConstraint(
check=models.Q(user__isnull=False, team="")
condition=models.Q(user__isnull=False, team="")
| models.Q(user__isnull=True, team__gt=""),
name="check_document_access_either_user_or_team",
violation_error_message=_("Either user or team must be set, not both."),
@@ -760,7 +761,7 @@ class Template(BaseModel):
"""
document_html = weasyprint.HTML(
string=DjangoTemplate(self.code).render(
Context({"body": html.format_html(body_html), **metadata})
Context({"body": html.format_html("{}", body_html), **metadata})
)
)
css = weasyprint.CSS(
@@ -779,7 +780,7 @@ class Template(BaseModel):
Generate and return a docx document wrapped around the current template
"""
template_string = DjangoTemplate(self.code).render(
Context({"body": html.format_html(body_html), **metadata})
Context({"body": html.format_html("{}", body_html), **metadata})
)
html_string = f"""
@@ -797,7 +798,6 @@ class Template(BaseModel):
"""
reference_docx = "core/static/reference.docx"
output = BytesIO()
# Convert the HTML to a temporary docx file
with tempfile.NamedTemporaryFile(suffix=".docx", prefix="docx_") as tmp_file:
@@ -884,7 +884,7 @@ class TemplateAccess(BaseAccess):
violation_error_message=_("This team is already in this template."),
),
models.CheckConstraint(
check=models.Q(user__isnull=False, team="")
condition=models.Q(user__isnull=False, team="")
| models.Q(user__isnull=True, team__gt=""),
name="check_template_access_either_user_or_team",
violation_error_message=_("Either user or team must be set, not both."),
@@ -17,7 +17,7 @@ class CollaborationService:
def reset_connections(self, room, user_id=None):
"""
Reset connections of a room in the collaboration server.
Reseting a connection means that the user will be disconnected and will
Resetting a connection means that the user will be disconnected and will
have to reconnect to the collaboration server, with updated rights.
"""
endpoint = "reset-connections"
@@ -1,6 +1,8 @@
"""Unit tests for the Authentication Backends."""
import re
from logging import Logger
from unittest import mock
from django.core.exceptions import SuspiciousOperation
from django.test.utils import override_settings
@@ -128,11 +130,12 @@ def test_authentication_getter_existing_user_with_email(
("Jack", "Duy", "jack.duy@example.com"),
],
)
def test_authentication_getter_existing_user_change_fields(
def test_authentication_getter_existing_user_change_fields_sub(
first_name, last_name, email, django_assert_num_queries, monkeypatch
):
"""
It should update the email or name fields on the user when they change.
It should update the email or name fields on the user when they change
and the user was identified by its "sub".
"""
klass = OIDCAuthenticationBackend()
user = UserFactory(
@@ -162,6 +165,48 @@ def test_authentication_getter_existing_user_change_fields(
assert user.short_name == first_name
@pytest.mark.parametrize(
"first_name, last_name, email",
[
("Jack", "Doe", "john.doe@example.com"),
("John", "Duy", "john.doe@example.com"),
],
)
def test_authentication_getter_existing_user_change_fields_email(
first_name, last_name, email, django_assert_num_queries, monkeypatch
):
"""
It should update the name fields on the user when they change
and the user was identified by its "email" as fallback.
"""
klass = OIDCAuthenticationBackend()
user = UserFactory(
full_name="John Doe", short_name="John", email="john.doe@example.com"
)
def get_userinfo_mocked(*args):
return {
"sub": "123",
"email": user.email,
"first_name": first_name,
"last_name": last_name,
}
monkeypatch.setattr(OIDCAuthenticationBackend, "get_userinfo", get_userinfo_mocked)
# One and only one additional update query when a field has changed
with django_assert_num_queries(3):
authenticated_user = klass.get_or_create_user(
access_token="test-token", id_token=None, payload=None
)
assert user == authenticated_user
user.refresh_from_db()
assert user.email == email
assert user.full_name == f"{first_name:s} {last_name:s}"
assert user.short_name == first_name
def test_authentication_getter_new_user_no_email(monkeypatch):
"""
If no user matches the user's info sub, a user should be created.
@@ -213,29 +258,6 @@ def test_authentication_getter_new_user_with_email(monkeypatch):
assert models.User.objects.count() == 1
def test_authentication_getter_invalid_token(django_assert_num_queries, monkeypatch):
"""The user's info doesn't contain a sub."""
klass = OIDCAuthenticationBackend()
def get_userinfo_mocked(*args):
return {
"test": "123",
}
monkeypatch.setattr(OIDCAuthenticationBackend, "get_userinfo", get_userinfo_mocked)
with (
django_assert_num_queries(0),
pytest.raises(
SuspiciousOperation,
match="User info contained no recognizable user identification",
),
):
klass.get_or_create_user(access_token="test-token", id_token=None, payload=None)
assert models.User.objects.exists() is False
@override_settings(OIDC_OP_USER_ENDPOINT="http://oidc.endpoint.test/userinfo")
@responses.activate
def test_authentication_get_userinfo_json_response():
@@ -341,7 +363,7 @@ def test_authentication_getter_existing_disabled_user_via_email(
django_assert_num_queries, monkeypatch
):
"""
If an existing user does not matches the sub but matches the email and is disabled,
If an existing user does not match the sub but matches the email and is disabled,
an error should be raised and a user should not be created.
"""
@@ -367,85 +389,100 @@ def test_authentication_getter_existing_disabled_user_via_email(
assert models.User.objects.count() == 1
# Required claims
# Essential claims
@override_settings(
OIDC_OP_USER_ENDPOINT="http://oidc.endpoint.test/userinfo",
USER_OIDC_REQUIRED_CLAIMS=["email", "sub", "address"],
)
@responses.activate
def test_authentication_get_userinfo_required_claims_missing():
"""Ensure SuspiciousOperation is raised if required claims are missing."""
def test_authentication_verify_claims_default(django_assert_num_queries, monkeypatch):
"""The sub claim should be mandatory by default."""
klass = OIDCAuthenticationBackend()
responses.add(
responses.GET,
re.compile(r".*/userinfo"),
json={
"last_name": "Doe",
"email": "john.doe@example.com",
},
status=200,
)
def get_userinfo_mocked(*args):
return {
"test": "123",
}
oidc_backend = OIDCAuthenticationBackend()
monkeypatch.setattr(OIDCAuthenticationBackend, "get_userinfo", get_userinfo_mocked)
with pytest.raises(
SuspiciousOperation, match="Missing required claims in user info: sub, address"
with (
django_assert_num_queries(0),
pytest.raises(
KeyError,
match="sub",
),
):
oidc_backend.get_userinfo("fake_access_token", None, None)
klass.get_or_create_user(access_token="test-token", id_token=None, payload=None)
assert models.User.objects.exists() is False
@override_settings(
OIDC_OP_USER_ENDPOINT="http://oidc.endpoint.test/userinfo",
USER_OIDC_REQUIRED_CLAIMS=["email", "Sub"],
@pytest.mark.parametrize(
"essential_claims, missing_claims",
[
(["email", "sub"], ["email"]),
(["Email", "sub"], ["Email"]), # Case sensitivity
],
)
@responses.activate
def test_authentication_get_userinfo_required_claims_case_sensitivity():
"""Ensure the system respects case sensitivity for required claims."""
@override_settings(OIDC_OP_USER_ENDPOINT="http://oidc.endpoint.test/userinfo")
@mock.patch.object(Logger, "error")
def test_authentication_verify_claims_essential_missing(
mock_logger,
essential_claims,
missing_claims,
django_assert_num_queries,
monkeypatch,
):
"""Ensure SuspiciousOperation is raised if essential claims are missing."""
responses.add(
responses.GET,
re.compile(r".*/userinfo"),
json={
klass = OIDCAuthenticationBackend()
def get_userinfo_mocked(*args):
return {
"sub": "123",
"last_name": "Doe",
"email": "john.doe@example.com",
},
status=200,
)
}
oidc_backend = OIDCAuthenticationBackend()
monkeypatch.setattr(OIDCAuthenticationBackend, "get_userinfo", get_userinfo_mocked)
with pytest.raises(
SuspiciousOperation, match="Missing required claims in user info: Sub"
with (
django_assert_num_queries(0),
pytest.raises(
SuspiciousOperation,
match="Claims verification failed",
),
override_settings(USER_OIDC_ESSENTIAL_CLAIMS=essential_claims),
):
oidc_backend.get_userinfo("fake_access_token", None, None)
klass.get_or_create_user(access_token="test-token", id_token=None, payload=None)
assert models.User.objects.exists() is False
mock_logger.assert_called_once_with("Missing essential claims: %s", missing_claims)
@override_settings(
OIDC_OP_USER_ENDPOINT="http://oidc.endpoint.test/userinfo",
USER_OIDC_REQUIRED_CLAIMS=["email", "sub"],
USER_OIDC_ESSENTIAL_CLAIMS=["email", "last_name"],
)
@responses.activate
def test_authentication_get_userinfo_required_claims_success():
"""Ensure user is authenticated when required claims are present."""
def test_authentication_verify_claims_success(django_assert_num_queries, monkeypatch):
"""Ensure user is authenticated when all essential claims are present."""
responses.add(
responses.GET,
re.compile(r".*/userinfo"),
json={
"sub": "123",
"last_name": "Doe",
klass = OIDCAuthenticationBackend()
def get_userinfo_mocked(*args):
return {
"email": "john.doe@example.com",
},
status=200,
)
"last_name": "Doe",
"sub": "123",
}
oidc_backend = OIDCAuthenticationBackend()
result = oidc_backend.get_userinfo("fake_access_token", None, None)
monkeypatch.setattr(OIDCAuthenticationBackend, "get_userinfo", get_userinfo_mocked)
assert result["sub"] == "123"
assert result.get("first_name") is None
assert result["last_name"] == "Doe"
assert result["email"] == "john.doe@example.com"
with django_assert_num_queries(6):
user = klass.get_or_create_user(
access_token="test-token", id_token=None, payload=None
)
assert models.User.objects.filter(id=user.id).exists()
assert user.sub == "123"
assert user.full_name == "Doe"
assert user.short_name is None
assert user.email == "john.doe@example.com"
@@ -698,7 +698,7 @@ def test_api_document_accesses_delete_administrators_except_owners(
mock_reset_connections, # pylint: disable=redefined-outer-name
):
"""
Users who are administrators in a document should be allowed to delete an access
Users who are administrators in a document should be allowed to delete access
from the document provided it is not ownership.
"""
user = factories.UserFactory()
@@ -285,7 +285,7 @@ def test_api_document_versions_retrieve_authenticated_related(via, mock_user_tea
assert response.status_code == 404
# Create a new version should not make it available to the user because
# only the current version is available to the user but it is excluded
# only the current version is available to the user, but it is excluded
# from the list
document.content = "new content 1"
document.save()
@@ -134,7 +134,7 @@ def test_api_documents_ai_transform_authenticated_forbidden(reach, role):
@patch("openai.resources.chat.completions.Completions.create")
def test_api_documents_ai_transform_authenticated_success(mock_create, reach, role):
"""
Autenticated who are not related to a document should be able to request AI transform
Authenticated who are not related to a document should be able to request AI transform
if the link reach and role permit it.
"""
user = factories.UserFactory()
@@ -154,7 +154,7 @@ def test_api_documents_ai_translate_authenticated_forbidden(reach, role):
@patch("openai.resources.chat.completions.Completions.create")
def test_api_documents_ai_translate_authenticated_success(mock_create, reach, role):
"""
Autenticated who are not related to a document should be able to request AI translate
Authenticated who are not related to a document should be able to request AI translate
if the link reach and role permit it.
"""
user = factories.UserFactory()
@@ -111,7 +111,7 @@ def test_api_documents_attachment_upload_authenticated_forbidden(reach, role):
)
def test_api_documents_attachment_upload_authenticated_success(reach, role):
"""
Autenticated who are not related to a document should be able to upload a file
Authenticated who are not related to a document should be able to upload a file
if the link reach and role permit it.
"""
user = factories.UserFactory()
@@ -225,7 +225,7 @@ def test_api_documents_attachment_upload_invalid(client):
def test_api_documents_attachment_upload_size_limit_exceeded(settings):
"""The uploaded file should not exceeed the maximum size in settings."""
"""The uploaded file should not exceed the maximum size in settings."""
settings.DOCUMENT_IMAGE_MAX_SIZE = 1048576 # 1 MB for test
user = factories.UserFactory()
@@ -160,7 +160,7 @@ def test_api_documents_media_auth_authenticated_restricted():
@pytest.mark.parametrize("via", VIA)
def test_api_documents_media_auth_related(via, mock_user_teams):
"""
Users who have a specific access to a document, whatever the role, should be able to
Users who have specific access to a document, whatever the role, should be able to
retrieve related attachments.
"""
user = factories.UserFactory()
@@ -647,7 +647,7 @@ def test_api_template_accesses_delete_administrators_except_owners(
via, mock_user_teams
):
"""
Users who are administrators in a template should be allowed to delete an access
Users who are administrators in a template should be allowed to delete access
from the template provided it is not ownership.
"""
user = factories.UserFactory()
@@ -84,7 +84,7 @@ def test_models_documents_file_key():
def test_models_documents_get_abilities_forbidden(is_authenticated, reach, role):
"""
Check abilities returned for a document giving insufficient roles to link holders
i.e anonymous users or authenticated users who have no specific role on the document.
i.e. anonymous users or authenticated users who have no specific role on the document.
"""
document = factories.DocumentFactory(link_reach=reach, link_role=role)
user = factories.UserFactory() if is_authenticated else AnonymousUser()
@@ -121,7 +121,7 @@ def test_models_documents_get_abilities_forbidden(is_authenticated, reach, role)
def test_models_documents_get_abilities_reader(is_authenticated, reach):
"""
Check abilities returned for a document giving reader role to link holders
i.e anonymous users or authenticated users who have no specific role on the document.
i.e. anonymous users or authenticated users who have no specific role on the document.
"""
document = factories.DocumentFactory(link_reach=reach, link_role="reader")
user = factories.UserFactory() if is_authenticated else AnonymousUser()
@@ -158,7 +158,7 @@ def test_models_documents_get_abilities_reader(is_authenticated, reach):
def test_models_documents_get_abilities_editor(is_authenticated, reach):
"""
Check abilities returned for a document giving editor role to link holders
i.e anonymous users or authenticated users who have no specific role on the document.
i.e. anonymous users or authenticated users who have no specific role on the document.
"""
document = factories.DocumentFactory(link_reach=reach, link_role="editor")
user = factories.UserFactory() if is_authenticated else AnonymousUser()
@@ -449,7 +449,7 @@ def test_models_documents__email_invitation__success():
def test_models_documents__email_invitation__success_fr():
"""
The email invitation is sent successfully in french.
The email invitation is sent successfully in French.
"""
document = factories.DocumentFactory()
+2 -2
View File
@@ -27,7 +27,7 @@ def test_models_users_id_unique():
def test_models_users_send_mail_main_existing():
"""The "email_user' method should send mail to the user's email address."""
"""The 'email_user' method should send mail to the user's email address."""
user = factories.UserFactory()
with mock.patch("django.core.mail.send_mail") as mock_send:
@@ -37,7 +37,7 @@ def test_models_users_send_mail_main_existing():
def test_models_users_send_mail_main_missing():
"""The "email_user' method should fail if the user has no email address."""
"""The 'email_user' method should fail if the user has no email address."""
user = factories.UserFactory(email=None)
with pytest.raises(ValueError) as excinfo:
@@ -1,5 +1,5 @@
"""
Test ai API endpoints in the impress core app.
Test AI API endpoints in the impress core app.
"""
import json
@@ -15,7 +15,7 @@ class Command(BaseCommand):
"""Define required arguments "email" and "password"."""
parser.add_argument(
"--email",
help=("Email for the user."),
help="Email for the user.",
)
parser.add_argument(
"--password",
+5 -4
View File
@@ -474,8 +474,8 @@ class Base(Configuration):
environ_prefix=None,
)
USER_OIDC_REQUIRED_CLAIMS = values.ListValue(
default=[], environ_name="USER_OIDC_REQUIRED_CLAIMS", environ_prefix=None
USER_OIDC_ESSENTIAL_CLAIMS = values.ListValue(
default=[], environ_name="USER_OIDC_ESSENTIAL_CLAIMS", environ_prefix=None
)
USER_OIDC_FIELDS_TO_FULLNAME = values.ListValue(
default=["first_name", "last_name"],
@@ -622,8 +622,9 @@ class Base(Configuration):
release=get_release(),
integrations=[DjangoIntegration()],
)
with sentry_sdk.configure_scope() as scope:
scope.set_extra("application", "backend")
# Add the application name to the Sentry scope
scope = sentry_sdk.get_global_scope()
scope.set_tag("application", "backend")
class Build(Base):
@@ -123,7 +123,7 @@ test.describe('Doc Editor', () => {
const selectVisibility = page.getByLabel('Visibility', { exact: true });
// When the visibility is changed, the ws should closed the connection (backend signal)
// When the visibility is changed, the ws should close the connection (backend signal)
const wsClosePromise = webSocket.waitForEvent('close');
await selectVisibility.click();
@@ -204,6 +204,15 @@ test.describe('Doc Editor', () => {
await verifyDocName(page, firstDoc);
await expect(editor.getByText('Hello World Doc 2')).toBeHidden();
await expect(editor.getByText('Hello World Doc 1')).toBeVisible();
await page
.getByRole('button', {
name: 'New doc',
})
.click();
await expect(editor.getByText('Hello World Doc 1')).toBeHidden();
await expect(editor.getByText('Hello World Doc 2')).toBeHidden();
});
test('it saves the doc when we change pages', async ({
@@ -67,7 +67,7 @@ test.describe('Doc Visibility', () => {
test.describe('Doc Visibility: Restricted', () => {
test.use({ storageState: { cookies: [], origins: [] } });
test('A doc is not accessible when not authentified.', async ({
test('A doc is not accessible when not authenticated.', async ({
page,
browserName,
}) => {
@@ -98,7 +98,7 @@ test.describe('Doc Visibility: Restricted', () => {
await expect(page.getByRole('textbox', { name: 'password' })).toBeVisible();
});
test('A doc is not accessible when authentified but not member.', async ({
test('A doc is not accessible when authenticated but not member.', async ({
page,
browserName,
}) => {
@@ -316,7 +316,7 @@ test.describe('Doc Visibility: Public', () => {
test.describe('Doc Visibility: Authenticated', () => {
test.use({ storageState: { cookies: [], origins: [] } });
test('A doc is not accessible when unauthentified.', async ({
test('A doc is not accessible when unauthenticated.', async ({
page,
browserName,
}) => {
@@ -325,7 +325,7 @@ test.describe('Doc Visibility: Authenticated', () => {
const [docTitle] = await createDoc(
page,
'Authenticated unauthentified',
'Authenticated unauthenticated',
browserName,
1,
);
+3 -9
View File
@@ -5,7 +5,6 @@ const config = {
colors: {
'card-border': '#ededed',
'primary-bg': '#FAFAFA',
'primary-action': '#1212FF',
'primary-050': '#F5F5FE',
'primary-100': '#EDF5FA',
'primary-150': '#E5EEFA',
@@ -60,11 +59,6 @@ const config = {
h4: '1.375rem',
h5: '1.25rem',
h6: '1.125rem',
'xl-alt': '5rem',
'lg-alt': '4.5rem',
'md-alt': '4rem',
'sm-alt': '3.5rem',
'xs-alt': '3rem',
},
weights: {
thin: 100,
@@ -230,7 +224,7 @@ const config = {
'color-hover': 'var(--c--theme--colors--primary-700)',
},
border: {
color: 'var(--c--theme--colors--greyscale-300)',
color: 'var(--c--theme--colors--primary-200)',
},
},
tertiary: {
@@ -385,8 +379,8 @@ const config = {
'color-active': '#EDEDED',
},
border: {
color: 'var(--c--theme--colors--greyscale-300)',
'color-hover': 'var(--c--theme--colors--greyscale-300)',
color: 'var(--c--theme--colors--primary-600)',
'color-hover': 'var(--c--theme--colors--primary-600)',
},
color: 'var(--c--theme--colors--primary-text)',
},
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

@@ -29,7 +29,7 @@ describe('fetchAPI', () => {
});
});
it('check the versionning', () => {
it('check the versioning', () => {
fetchMock.mock('http://test.jest/api/v2.0/some/url', 200);
void fetchAPI('some/url', {}, '2.0');
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

@@ -1,4 +0,0 @@
<svg width="70" height="70" viewBox="0 0 70 70" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M47.2515 62.7264C49.7721 62.0196 52.1861 60.7298 53.7701 58.4505C55.3337 56.2187 55.774 53.3875 55.774 50.6632V10.749C55.774 10.0423 55.7356 9.33431 55.6503 8.63477C56.9023 9.12719 57.8672 9.94087 58.5451 11.0758C59.3448 12.3663 59.7447 14.0657 59.7447 16.1741V56.7153C59.7447 59.5689 59.0449 61.7046 57.6454 63.1223C56.2458 64.54 54.1374 65.2489 51.3202 65.2489H36.0065C36.3692 65.1854 36.7328 65.12 37.0972 65.0528C40.2928 64.4817 43.6701 63.7067 47.2256 62.7336L47.2515 62.7264Z" fill="#C9191E"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.4521 55.8703V15.2746C10.4521 12.8027 11.1156 10.9306 12.4424 9.65832C13.7874 8.38601 15.5686 7.68624 17.7861 7.55901C21.0577 7.35908 24.1567 7.08644 27.083 6.7411C30.0093 6.37758 32.8084 5.95954 35.4803 5.48697C38.1703 5.01439 40.7876 4.48729 43.3322 3.90567C45.9495 3.28769 47.958 3.59668 49.3575 4.83264C50.757 6.06859 51.4568 8.04067 51.4568 10.7489V50.663C51.4568 53.044 51.0479 54.8161 50.2299 55.9794C49.412 57.1608 48.0307 58.0242 46.0859 58.5695C42.6324 59.5146 39.379 60.2598 36.3254 60.8051C33.2719 61.3685 30.2911 61.7957 27.3829 62.0865C24.4748 62.3773 21.494 62.6045 18.4404 62.7681C15.914 62.9135 13.951 62.3864 12.5515 61.1868C11.1519 60.0053 10.4521 58.2332 10.4521 55.8703ZM20.4387 22.6454C24.4424 22.3905 28.1112 21.9992 31.4447 21.4709C32.2535 21.339 33.0611 21.2017 33.8657 21.0592C34.7076 20.9101 35.3165 20.1764 35.3165 19.3233C35.3165 18.2185 34.3166 17.3865 33.2308 17.5717C32.6071 17.6781 31.9801 17.7815 31.3497 17.8819C28.0496 18.4078 24.4029 18.7979 20.4098 19.052C19.8118 19.0906 19.336 19.2769 19.0302 19.6482C18.741 19.9994 18.5993 20.4218 18.5993 20.9033C18.5993 21.3928 18.7644 21.8188 19.0941 22.1691L19.0987 22.1737C19.456 22.531 19.91 22.6857 20.4387 22.6454ZM20.4374 31.6973C24.4416 31.4424 28.1108 31.051 31.4447 30.5227C34.7931 29.9768 38.1061 29.3397 41.3818 28.6117C42.0465 28.464 42.5444 28.2314 42.7935 27.8701C43.0383 27.5228 43.1594 27.1247 43.1594 26.6834C43.1594 26.1857 42.9698 25.757 42.601 25.4087C42.2038 25.0336 41.6486 24.9462 40.9915 25.0815L40.9873 25.0825C37.8658 25.7902 34.6533 26.4074 31.3497 26.9337C28.0496 27.4596 24.4029 27.8497 20.4098 28.1038C19.8118 28.1424 19.336 28.3287 19.0302 28.7C18.7424 29.0495 18.5993 29.4621 18.5993 29.9278C18.5993 30.4329 18.7625 30.8691 19.0939 31.2211L19.1034 31.2301C19.4595 31.5653 19.9088 31.7177 20.432 31.6976L20.4374 31.6973ZM20.4383 40.7488C24.4422 40.4758 28.111 40.0753 31.4446 39.547C34.7933 39.0192 38.1057 38.3913 41.3818 37.6633C42.0481 37.5152 42.5464 37.2729 42.7951 36.892C43.0376 36.5471 43.1594 36.1594 43.1594 35.735C43.1594 35.2373 42.9698 34.8086 42.601 34.4602C42.2038 34.0852 41.6486 33.9978 40.9915 34.1331L40.9885 34.1338C37.8666 34.8235 34.6537 35.4316 31.3497 35.958C28.0493 36.4839 24.4025 36.8832 20.4092 37.1554C19.8115 37.1941 19.3359 37.3804 19.0302 37.7516C18.7424 38.1011 18.5993 38.5137 18.5993 38.9794C18.5993 39.4845 18.7625 39.9206 19.0939 40.2727L19.1034 40.2816C19.4595 40.6168 19.9088 40.7693 20.432 40.7492L20.4383 40.7488ZM31.4447 48.5358C28.1112 49.0641 24.4424 49.4555 20.4388 49.7103C19.91 49.7506 19.456 49.596 19.0987 49.2386L19.0941 49.2339C18.7645 48.8837 18.5993 48.4577 18.5993 47.9682C18.5993 47.4867 18.741 47.0644 19.0302 46.7132C19.336 46.3418 19.812 46.1555 20.41 46.117C24.4031 45.8629 28.0496 45.4727 31.3497 44.9469C31.9801 44.8464 32.6071 44.743 33.2308 44.6366C34.3166 44.4514 35.3165 45.2834 35.3165 46.3882C35.3165 47.2413 34.7076 47.975 33.8657 48.1241C33.0611 48.2666 32.2535 48.404 31.4447 48.5358Z" fill="#000091"/>
</svg>

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 743 B

@@ -1,4 +0,0 @@
<svg width="70" height="70" viewBox="0 0 70 70" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M47.2515 62.7264C49.7721 62.0196 52.1861 60.7298 53.7701 58.4505C55.3337 56.2187 55.774 53.3875 55.774 50.6632V10.749C55.774 10.0423 55.7356 9.33431 55.6503 8.63477C56.9023 9.12719 57.8672 9.94087 58.5451 11.0758C59.3448 12.3663 59.7447 14.0657 59.7447 16.1741V56.7153C59.7447 59.5689 59.0449 61.7046 57.6454 63.1223C56.2458 64.54 54.1374 65.2489 51.3202 65.2489H36.0065C36.3692 65.1854 36.7328 65.12 37.0972 65.0528C40.2928 64.4817 43.6701 63.7067 47.2256 62.7336L47.2515 62.7264Z" fill="#C9191E"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.4521 55.8703V15.2746C10.4521 12.8027 11.1156 10.9306 12.4424 9.65832C13.7874 8.38601 15.5686 7.68624 17.7861 7.55901C21.0577 7.35908 24.1567 7.08644 27.083 6.7411C30.0093 6.37758 32.8084 5.95954 35.4803 5.48697C38.1703 5.01439 40.7876 4.48729 43.3322 3.90567C45.9495 3.28769 47.958 3.59668 49.3575 4.83264C50.757 6.06859 51.4568 8.04067 51.4568 10.7489V50.663C51.4568 53.044 51.0479 54.8161 50.2299 55.9794C49.412 57.1608 48.0307 58.0242 46.0859 58.5695C42.6324 59.5146 39.379 60.2598 36.3254 60.8051C33.2719 61.3685 30.2911 61.7957 27.3829 62.0865C24.4748 62.3773 21.494 62.6045 18.4404 62.7681C15.914 62.9135 13.951 62.3864 12.5515 61.1868C11.1519 60.0053 10.4521 58.2332 10.4521 55.8703ZM20.4387 22.6454C24.4424 22.3905 28.1112 21.9992 31.4447 21.4709C32.2535 21.339 33.0611 21.2017 33.8657 21.0592C34.7076 20.9101 35.3165 20.1764 35.3165 19.3233C35.3165 18.2185 34.3166 17.3865 33.2308 17.5717C32.6071 17.6781 31.9801 17.7815 31.3497 17.8819C28.0496 18.4078 24.4029 18.7979 20.4098 19.052C19.8118 19.0906 19.336 19.2769 19.0302 19.6482C18.741 19.9994 18.5993 20.4218 18.5993 20.9033C18.5993 21.3928 18.7644 21.8188 19.0941 22.1691L19.0987 22.1737C19.456 22.531 19.91 22.6857 20.4387 22.6454ZM20.4374 31.6973C24.4416 31.4424 28.1108 31.051 31.4447 30.5227C34.7931 29.9768 38.1061 29.3397 41.3818 28.6117C42.0465 28.464 42.5444 28.2314 42.7935 27.8701C43.0383 27.5228 43.1594 27.1247 43.1594 26.6834C43.1594 26.1857 42.9698 25.757 42.601 25.4087C42.2038 25.0336 41.6486 24.9462 40.9915 25.0815L40.9873 25.0825C37.8658 25.7902 34.6533 26.4074 31.3497 26.9337C28.0496 27.4596 24.4029 27.8497 20.4098 28.1038C19.8118 28.1424 19.336 28.3287 19.0302 28.7C18.7424 29.0495 18.5993 29.4621 18.5993 29.9278C18.5993 30.4329 18.7625 30.8691 19.0939 31.2211L19.1034 31.2301C19.4595 31.5653 19.9088 31.7177 20.432 31.6976L20.4374 31.6973ZM20.4383 40.7488C24.4422 40.4758 28.111 40.0753 31.4446 39.547C34.7933 39.0192 38.1057 38.3913 41.3818 37.6633C42.0481 37.5152 42.5464 37.2729 42.7951 36.892C43.0376 36.5471 43.1594 36.1594 43.1594 35.735C43.1594 35.2373 42.9698 34.8086 42.601 34.4602C42.2038 34.0852 41.6486 33.9978 40.9915 34.1331L40.9885 34.1338C37.8666 34.8235 34.6537 35.4316 31.3497 35.958C28.0493 36.4839 24.4025 36.8832 20.4092 37.1554C19.8115 37.1941 19.3359 37.3804 19.0302 37.7516C18.7424 38.1011 18.5993 38.5137 18.5993 38.9794C18.5993 39.4845 18.7625 39.9206 19.0939 40.2727L19.1034 40.2816C19.4595 40.6168 19.9088 40.7693 20.432 40.7492L20.4383 40.7488ZM31.4447 48.5358C28.1112 49.0641 24.4424 49.4555 20.4388 49.7103C19.91 49.7506 19.456 49.596 19.0987 49.2386L19.0941 49.2339C18.7645 48.8837 18.5993 48.4577 18.5993 47.9682C18.5993 47.4867 18.741 47.0644 19.0302 46.7132C19.336 46.3418 19.812 46.1555 20.41 46.117C24.4031 45.8629 28.0496 45.4727 31.3497 44.9469C31.9801 44.8464 32.6071 44.743 33.2308 44.6366C34.3166 44.4514 35.3165 45.2834 35.3165 46.3882C35.3165 47.2413 34.7076 47.975 33.8657 48.1241C33.0611 48.2666 32.2535 48.404 31.4447 48.5358Z" fill="#000091"/>
</svg>

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

@@ -1,29 +0,0 @@
import Image from 'next/image';
import { useTranslation } from 'react-i18next';
import IconDocs from '@/assets/common/logo-docs-sm.png';
import { useCunninghamTheme } from '@/cunningham';
import Title from '@/features/header/components/Title/Title';
import { useResponsiveStore } from '@/stores';
import { Box } from './Box';
export const DocsTitle = () => {
const theme = useCunninghamTheme();
const { t } = useTranslation();
const { isDesktop } = useResponsiveStore();
const spacings = theme.spacingsTokens();
const colors = theme.colorsTokens();
return (
<Box
$align="center"
$gap={spacings['3xs']}
$direction="row"
$position="relative"
$height="fit-content"
>
<Image priority src={IconDocs} alt={t('Docs Logo')} width={25} />
<Title />
</Box>
);
};
File diff suppressed because one or more lines are too long
@@ -6,7 +6,8 @@ export * from './DropdownMenu';
export * from './Icon';
export * from './InfiniteScroll';
export * from './Link';
export * from './LoadMoreText';
export * from './SideModal';
export * from './separators/SeparatedSection';
export * from './separators';
export * from './Text';
export * from './TextErrors';
@@ -3,7 +3,7 @@ import { Command } from 'cmdk';
import { ReactNode } from 'react';
import { useTranslation } from 'react-i18next';
import { HorizontalSeparator } from '@/components/separators/HorizontalSeparator';
import { HorizontalSeparator } from '@/components';
import { useCunninghamTheme } from '@/cunningham';
import { Box } from '../Box';
@@ -0,0 +1,2 @@
export * from './HorizontalSeparator';
export * from './SeparatedSection';
@@ -14,7 +14,7 @@ import { useAuthStore } from './useAuthStore';
* When we will have a homepage design for non-authenticated users, we will remove this restriction to have
* the full website accessible without authentication.
*/
const regexpUrlsAuth = [/\/docs\/$/g];
const regexpUrlsAuth = [/\/docs\/$/g, /^\/$/g];
export const Auth = ({ children }: PropsWithChildren) => {
const { initAuth, initiated, authenticated, login, getAuthUrl } =
@@ -33,7 +33,7 @@ export const Auth = ({ children }: PropsWithChildren) => {
setPathAllowed(!regexpUrlsAuth.some((regexp) => !!asPath.match(regexp)));
}, [asPath]);
// We force to login except on allowed paths
// We force to log in except on allowed paths
useEffect(() => {
if (!initiated || authenticated || pathAllowed) {
return;
@@ -38,20 +38,16 @@ export const useAuthStore = create<AuthStore>((set, get) => ({
});
},
login: () => {
console.log(window.location.pathname);
if (window.location.pathname !== '/') {
get().setAuthUrl(window.location.pathname);
window.location.replace(`/`);
}
get().setAuthUrl(window.location.pathname);
// window.location.replace(`${baseApiUrl()}authenticate/`);
window.location.replace(`${baseApiUrl()}authenticate/`);
},
logout: () => {
terminateCrispSession();
window.location.replace(`${baseApiUrl()}logout/`);
},
// If we try to access a specific page and we are not authenticated
// we store the path in the local storage to redirect to it after login
// If we try to access a specific page, and we are not authenticated
// we store the path in the local storage to redirect to it after log in
setAuthUrl() {
if (window.location.pathname !== '/') {
localStorage.setItem(PATH_AUTH_LOCAL_STORAGE, window.location.pathname);
File diff suppressed because it is too large Load Diff
@@ -75,7 +75,6 @@ export const tokens = {
'danger-text': '#fff',
'card-border': '#ededed',
'primary-bg': '#FAFAFA',
'primary-action': '#1212FF',
'primary-050': '#F5F5FE',
'primary-150': '#E5EEFA',
'primary-950': '#1B1B35',
@@ -130,11 +129,6 @@ export const tokens = {
ml: '0.938rem',
xl: '1.25rem',
t: '0.6875rem',
'xl-alt': '5rem',
'lg-alt': '4.5rem',
'md-alt': '4rem',
'sm-alt': '3.5rem',
'xs-alt': '3rem',
},
weights: {
thin: 100,
@@ -321,7 +315,7 @@ export const tokens = {
color: 'white',
'color-hover': 'var(--c--theme--colors--primary-700)',
},
border: { color: 'var(--c--theme--colors--greyscale-300)' },
border: { color: 'var(--c--theme--colors--primary-200)' },
},
tertiary: {
color: 'var(--c--theme--colors--primary-text)',
@@ -508,8 +502,8 @@ export const tokens = {
secondary: {
background: { 'color-hover': '#F6F6F6', 'color-active': '#EDEDED' },
border: {
color: 'var(--c--theme--colors--greyscale-300)',
'color-hover': 'var(--c--theme--colors--greyscale-300)',
color: 'var(--c--theme--colors--primary-600)',
'color-hover': 'var(--c--theme--colors--primary-600)',
},
color: 'var(--c--theme--colors--primary-text)',
},
@@ -10,7 +10,7 @@ import { toBase64 } from '../utils';
const useSaveDoc = (docId: string, doc: Y.Doc, canSave: boolean) => {
const { mutate: updateDoc } = useUpdateDoc({
listInvalideQueries: [KEY_LIST_DOC_VERSIONS],
listInvalidQueries: [KEY_LIST_DOC_VERSIONS],
});
const [initialDoc, setInitialDoc] = useState<string>(
toBase64(Y.encodeStateAsUpdate(doc)),
@@ -2,8 +2,7 @@ import { DateTime } from 'luxon';
import { useTranslation } from 'react-i18next';
import { css } from 'styled-components';
import { Box, Icon, Text } from '@/components';
import { HorizontalSeparator } from '@/components/separators/HorizontalSeparator';
import { Box, HorizontalSeparator, Icon, Text } from '@/components';
import { useCunninghamTheme } from '@/cunningham';
import {
Doc,
@@ -62,7 +62,7 @@ const DocTitleInput = ({ doc }: DocTitleProps) => {
const { broadcast } = useBroadcastStore();
const { mutate: updateDoc } = useUpdateDoc({
listInvalideQueries: [KEY_DOC, KEY_LIST_DOC],
listInvalidQueries: [KEY_DOC, KEY_LIST_DOC],
onSuccess(data) {
if (data.title !== untitledDocument) {
toast(t('Document title updated successfully'), VariantType.SUCCESS);
@@ -20,14 +20,13 @@ import { useAuthStore } from '@/core';
import { useCunninghamTheme } from '@/cunningham';
import { useEditorStore } from '@/features/docs/doc-editor/';
import { Doc, ModalRemoveDoc } from '@/features/docs/doc-management';
import { DocShareModal } from '@/features/docs/doc-share';
import {
KEY_LIST_DOC_VERSIONS,
ModalSelectVersion,
} from '@/features/docs/doc-versioning';
import { useResponsiveStore } from '@/stores';
import { DocShareModal } from '../../doc-share/component/DocShareModal';
import { ModalPDF } from './ModalExport';
interface DocToolBoxProps {
@@ -1,7 +1,6 @@
import { useTranslation } from 'react-i18next';
import { Box } from '@/components';
import { HorizontalSeparator } from '@/components/separators/HorizontalSeparator';
import { Box, HorizontalSeparator } from '@/components';
import { useCunninghamTheme } from '@/cunningham';
import { DocTitleText } from './DocTitle';
@@ -94,7 +94,7 @@ const convertToImg = (html: string) => {
const doc = parser.parseFromString(html, 'text/html');
const divs = doc.querySelectorAll('div[data-content-type="image"]');
// Loop through each div and replace it with a img
// Loop through each div and replace it with an img
divs.forEach((div) => {
const img = document.createElement('img');
@@ -1,4 +1,5 @@
export * from './useCreateDoc';
export * from './useDeleteFavoriteDoc';
export * from './useDoc';
export * from './useDocOptions';
export * from './useDocs';
@@ -20,18 +20,18 @@ export const createFavoriteDoc = async ({ id }: CreateFavoriteDocParams) => {
interface CreateFavoriteDocProps {
onSuccess?: () => void;
listInvalideQueries?: string[];
listInvalidQueries?: string[];
}
export function useCreateFavoriteDoc({
onSuccess,
listInvalideQueries,
listInvalidQueries,
}: CreateFavoriteDocProps) {
const queryClient = useQueryClient();
return useMutation<void, APIError, CreateFavoriteDocParams>({
mutationFn: createFavoriteDoc,
onSuccess: () => {
listInvalideQueries?.forEach((queryKey) => {
listInvalidQueries?.forEach((queryKey) => {
void queryClient.invalidateQueries({
queryKey: [queryKey],
});
@@ -20,18 +20,18 @@ export const deleteFavoriteDoc = async ({ id }: DeleteFavoriteDocParams) => {
interface DeleteFavoriteDocProps {
onSuccess?: () => void;
listInvalideQueries?: string[];
listInvalidQueries?: string[];
}
export function useDeleteFavoriteDoc({
onSuccess,
listInvalideQueries,
listInvalidQueries,
}: DeleteFavoriteDocProps) {
const queryClient = useQueryClient();
return useMutation<void, APIError, DeleteFavoriteDocParams>({
mutationFn: deleteFavoriteDoc,
onSuccess: () => {
listInvalideQueries?.forEach((queryKey) => {
listInvalidQueries?.forEach((queryKey) => {
void queryClient.invalidateQueries({
queryKey: [queryKey],
});
@@ -26,18 +26,18 @@ export const updateDoc = async ({
interface UpdateDocProps {
onSuccess?: (data: Doc) => void;
listInvalideQueries?: string[];
listInvalidQueries?: string[];
}
export function useUpdateDoc({
onSuccess,
listInvalideQueries,
listInvalidQueries,
}: UpdateDocProps = {}) {
const queryClient = useQueryClient();
return useMutation<Doc, APIError, UpdateDocParams>({
mutationFn: updateDoc,
onSuccess: (data) => {
listInvalideQueries?.forEach((queryKey) => {
listInvalidQueries?.forEach((queryKey) => {
void queryClient.invalidateQueries({
queryKey: [queryKey],
});
@@ -30,12 +30,12 @@ export const updateDocLink = async ({
interface UpdateDocLinkProps {
onSuccess?: (data: Doc) => void;
listInvalideQueries?: string[];
listInvalidQueries?: string[];
}
export function useUpdateDocLink({
onSuccess,
listInvalideQueries,
listInvalidQueries,
}: UpdateDocLinkProps = {}) {
const queryClient = useQueryClient();
const { broadcast } = useBroadcastStore();
@@ -43,7 +43,7 @@ export function useUpdateDocLink({
return useMutation<Doc, APIError, UpdateDocLinkParams>({
mutationFn: updateDocLink,
onSuccess: (data, variable) => {
listInvalideQueries?.forEach((queryKey) => {
listInvalidQueries?.forEach((queryKey) => {
void queryClient.invalidateQueries({
queryKey: [queryKey],
});
@@ -1,9 +0,0 @@
<svg width="32" height="36" viewBox="0 0 32 36" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="2.01394" y="1.23611" width="25.9722" height="33.5278" rx="3.54167" fill="white"/>
<rect x="2.01394" y="1.23611" width="25.9722" height="33.5278" rx="3.54167" stroke="#E3E3FD" stroke-width="0.472222"/>
<path d="M6.5 8.55554H15" stroke="#6A6AF4" stroke-width="1.88889" stroke-linecap="round"/>
<path d="M6.5 11.3889H23.5M6.5 14.2222H23.5M6.5 17.0556H23.5M6.5 19.8889H23.5M6.5 22.7222H20.6667" stroke="#CACAFB" stroke-width="1.88889" stroke-linecap="round"/>
<rect x="7" y="10" width="16" height="16" rx="8" fill="#6A6AF4"/>
<rect x="7" y="10" width="16" height="16" rx="8" stroke="white" stroke-width="1.5"/>
<path d="M16.8 18L18 19.2V20.1H15.45V22.95L15 23.4L14.55 22.95V20.1H12V19.2L13.2 18V14.7H12.6V13.8H17.4V14.7H16.8V18Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 853 B

@@ -1,6 +0,0 @@
<svg width="32" height="36" viewBox="0 0 32 36" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="2.01394" y="1.23611" width="25.9722" height="33.5278" rx="3.54167" fill="white"/>
<rect x="2.01394" y="1.23611" width="25.9722" height="33.5278" rx="3.54167" stroke="#E3E3FD" stroke-width="0.472222"/>
<path d="M6.5 8.55554H15" stroke="#6A6AF4" stroke-width="1.88889" stroke-linecap="round"/>
<path d="M6.5 11.3889H23.5M6.5 14.2222H23.5M6.5 17.0556H23.5M6.5 19.8889H23.5M6.5 22.7222H20.6667" stroke="#CACAFB" stroke-width="1.88889" stroke-linecap="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 568 B

@@ -27,9 +27,14 @@ export const useCollaboration = (room?: string, initialContent?: Base64) => {
setBroadcastProvider,
]);
/**
* Destroy the provider when the component is unmounted
*/
useEffect(() => {
return () => {
destroyProvider();
if (room) {
destroyProvider();
}
};
}, [destroyProvider]);
}, [destroyProvider, room]);
};
@@ -1,7 +1,7 @@
import { Box, Icon } from '@/components';
import { QuickSearchItemContent } from '@/components/quick-search/QuickSearchItemContent';
import { QuickSearchItemContent } from '@/components/quick-search/';
import { Doc } from '@/features/docs/doc-management';
import { SimpleDocItem } from '@/features/docs/docs-grid/components/SimpleDocItem';
import { SimpleDocItem } from '@/features/docs/docs-grid/';
import { useResponsiveStore } from '@/stores';
type DocSearchItemProps = {
@@ -12,10 +12,10 @@ import {
QuickSearchData,
QuickSearchGroup,
} from '@/components/quick-search';
import EmptySearchIcon from '@/features/docs/doc-search/assets/illustration-docs-empty.png';
import { Doc, useInfiniteDocs } from '@/features/docs/doc-management';
import { useResponsiveStore } from '@/stores';
import { Doc, useInfiniteDocs } from '../../doc-management';
import EmptySearchIcon from '../assets/illustration-docs-empty.png';
import { DocSearchItem } from './DocSearchItem';
@@ -0,0 +1 @@
export * from './DocSearchModal';
@@ -0,0 +1 @@
export * from './components';
@@ -1,9 +1,7 @@
import { css } from 'styled-components';
import { DropdownMenu, DropdownMenuOption, Text } from '@/components';
import { useTrans } from '../../doc-management/hooks';
import { Role } from '../../doc-management/types';
import { Role, useTrans } from '@/features/docs/doc-management/';
type Props = {
currentRole: Role;
@@ -12,13 +12,11 @@ import { Box } from '@/components';
import { User } from '@/core';
import { useCunninghamTheme } from '@/cunningham';
import { Doc, Role } from '@/features/docs';
import {
useCreateDocAccess,
useCreateDocInvitation,
} from '@/features/docs/doc-share';
import { OptionType } from '@/features/docs/doc-share/types';
import { useLanguage } from '@/i18n/hooks/useLanguage';
import { useCreateDocAccess, useCreateDocInvitation } from '../api';
import { OptionType } from '../types';
import { DocRoleDropdown } from './DocRoleDropdown';
import { DocShareAddMemberListItem } from './DocShareAddMemberListItem';
@@ -10,14 +10,12 @@ import {
import { User } from '@/core';
import { useCunninghamTheme } from '@/cunningham';
import { Doc, Role } from '@/features/docs/doc-management';
import {
useDeleteDocInvitation,
useUpdateDocInvitation,
} from '@/features/docs/doc-share';
import { SearchUserRow } from '@/features/docs/doc-share/component/SearchUserRow';
import { Invitation } from '@/features/docs/doc-share/types';
import { useDeleteDocInvitation, useUpdateDocInvitation } from '../api';
import { Invitation } from '../types';
import { DocRoleDropdown } from './DocRoleDropdown';
import { SearchUserRow } from './SearchUserRow';
type Props = {
doc: Doc;
@@ -8,14 +8,14 @@ import {
IconOptions,
} from '@/components';
import { useCunninghamTheme } from '@/cunningham';
import { SearchUserRow } from '@/features/docs/doc-share/component/SearchUserRow';
import { useWhoAmI } from '@/features/docs/doc-share/hooks/useWhoAmI';
import { Access, Doc, Role } from '@/features/docs/doc-management/';
import { useResponsiveStore } from '@/stores';
import { Access, Doc, Role } from '../../doc-management/types';
import { useDeleteDocAccess, useUpdateDocAccess } from '../index';
import { useDeleteDocAccess, useUpdateDocAccess } from '../api';
import { useWhoAmI } from '../hooks/';
import { DocRoleDropdown } from './DocRoleDropdown';
import { SearchUserRow } from './SearchUserRow';
type Props = {
doc: Doc;
@@ -4,24 +4,24 @@ import { useTranslation } from 'react-i18next';
import { createGlobalStyle, css } from 'styled-components';
import { useDebouncedCallback } from 'use-debounce';
import { Box } from '@/components';
import { LoadMoreText } from '@/components/LoadMoreText';
import { Box, LoadMoreText } from '@/components';
import {
QuickSearch,
QuickSearchData,
} from '@/components/quick-search/QuickSearch';
import { QuickSearchGroup } from '@/components/quick-search/QuickSearchGroup';
QuickSearchGroup,
} from '@/components/quick-search/';
import { User } from '@/core';
import { Access, Doc } from '@/features/docs';
import { useResponsiveStore } from '@/stores';
import { isValidEmail } from '@/utils';
import {
KEY_LIST_USER,
useDocAccessesInfinite,
useDocInvitationsInfinite,
useUsers,
} from '@/features/docs/doc-share';
import { Invitation } from '@/features/docs/doc-share/types';
import { useResponsiveStore } from '@/stores';
import { isValidEmail } from '@/utils';
} from '../api';
import { Invitation } from '../types';
import { DocShareAddMemberList } from './DocShareAddMemberList';
import { DocShareInvitationItem } from './DocShareInvitationItem';
@@ -6,8 +6,7 @@ import {
import { useTranslation } from 'react-i18next';
import { css } from 'styled-components';
import { Box } from '@/components';
import { HorizontalSeparator } from '@/components/separators/HorizontalSeparator';
import { Box, HorizontalSeparator } from '@/components';
import { Doc } from '@/features/docs';
import { DocVisibility } from './DocVisibility';
@@ -3,7 +3,8 @@ import { css } from 'styled-components';
import { Box, Icon, Text } from '@/components';
import { User } from '@/core';
import { SearchUserRow } from '@/features/docs/doc-share/component/SearchUserRow';
import { SearchUserRow } from './SearchUserRow';
type Props = {
user: User;
@@ -19,9 +19,10 @@ import {
LinkRole,
useUpdateDocLink,
} from '@/features/docs';
import { useTranslatedShareSettings } from '@/features/docs/doc-share';
import { useResponsiveStore } from '@/stores';
import { useTranslatedShareSettings } from '../hooks/';
interface DocVisibilityProps {
doc: Doc;
}
@@ -48,7 +49,7 @@ export const DocVisibility = ({ doc }: DocVisibilityProps) => {
},
);
},
listInvalideQueries: [KEY_LIST_DOC, KEY_DOC],
listInvalidQueries: [KEY_LIST_DOC, KEY_DOC],
});
const updateReach = (link_reach: LinkReach) => {
@@ -2,7 +2,7 @@ import { Box, Text } from '@/components';
import {
QuickSearchItemContent,
QuickSearchItemContentProps,
} from '@/components/quick-search/QuickSearchItemContent';
} from '@/components/quick-search';
import { User } from '@/core';
import { useCunninghamTheme } from '@/cunningham';
@@ -0,0 +1 @@
export * from './DocShareModal';
@@ -1,2 +1,4 @@
export * from './api';
export * from './components';
export * from './hooks';
export * from './types';
@@ -44,7 +44,7 @@ export const Heading = ({
onMouseOver={() => setIsHover(true)}
onMouseLeave={() => setIsHover(false)}
onClick={() => {
// With mobile the focus open the keyboard and the scroll is not working
// With mobile the focus open the keyboard and the scroll are not working
if (!isMobile) {
editor.focus();
}
@@ -42,7 +42,7 @@ export const ModalConfirmationVersion = ({
const { push } = useRouter();
const { provider } = useProviderStore();
const { mutate: updateDoc } = useUpdateDoc({
listInvalideQueries: [KEY_LIST_DOC_VERSIONS],
listInvalidQueries: [KEY_LIST_DOC_VERSIONS],
onSuccess: () => {
const onDisplaySuccess = () => {
toast(t('Version restored successfully'), VariantType.SUCCESS);
@@ -4,9 +4,9 @@ import { useTranslation } from 'react-i18next';
import { createGlobalStyle, css } from 'styled-components';
import { Box, Icon, Text } from '@/components';
import { DocEditor } from '@/features/docs/doc-editor';
import { Doc } from '@/features/docs/doc-management';
import { DocEditor } from '../../doc-editor/components/DocEditor';
import { Doc } from '../../doc-management';
import { Versions } from '../types';
import { ModalConfirmationVersion } from './ModalConfirmationVersion';
@@ -0,0 +1,51 @@
<svg
width="32"
height="36"
viewBox="0 0 32 36"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<rect
x="2.01394"
y="1.23611"
width="25.9722"
height="33.5278"
rx="3.54167"
fill="white"
/>
<rect
x="2.01394"
y="1.23611"
width="25.9722"
height="33.5278"
rx="3.54167"
stroke="#E3E3FD"
stroke-width="0.472222"
/>
<path
d="M6.5 8.55554H15"
stroke="#6A6AF4"
stroke-width="1.88889"
stroke-linecap="round"
/>
<path
d="M6.5 11.3889H23.5M6.5 14.2222H23.5M6.5 17.0556H23.5M6.5 19.8889H23.5M6.5 22.7222H20.6667"
stroke="#CACAFB"
stroke-width="1.88889"
stroke-linecap="round"
/>
<rect x="7" y="10" width="16" height="16" rx="8" fill="#6A6AF4" />
<rect
x="7"
y="10"
width="16"
height="16"
rx="8"
stroke="white"
stroke-width="1.5"
/>
<path
d="M16.8 18L18 19.2V20.1H15.45V22.95L15 23.4L14.55 22.95V20.1H12V19.2L13.2 18V14.7H12.6V13.8H17.4V14.7H16.8V18Z"
fill="white"
/>
</svg>

After

Width:  |  Height:  |  Size: 1017 B

@@ -0,0 +1,37 @@
<svg
width="32"
height="36"
viewBox="0 0 32 36"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<rect
x="2.01394"
y="1.23611"
width="25.9722"
height="33.5278"
rx="3.54167"
fill="white"
/>
<rect
x="2.01394"
y="1.23611"
width="25.9722"
height="33.5278"
rx="3.54167"
stroke="#E3E3FD"
stroke-width="0.472222"
/>
<path
d="M6.5 8.55554H15"
stroke="#6A6AF4"
stroke-width="1.88889"
stroke-linecap="round"
/>
<path
d="M6.5 11.3889H23.5M6.5 14.2222H23.5M6.5 17.0556H23.5M6.5 19.8889H23.5M6.5 22.7222H20.6667"
stroke="#CACAFB"
stroke-width="1.88889"
stroke-linecap="round"
/>
</svg>

After

Width:  |  Height:  |  Size: 683 B

@@ -4,10 +4,12 @@ import { InView } from 'react-intersection-observer';
import { css } from 'styled-components';
import { Box, Card, Text } from '@/components';
import {
DocDefaultFilter,
useInfiniteDocs,
} from '@/features/docs/doc-management';
import { useResponsiveStore } from '@/stores';
import { DocDefaultFilter, useInfiniteDocs } from '../../doc-management';
import { DocsGridItem } from './DocsGridItem';
import { DocsGridLoader } from './DocsGridLoader';
@@ -7,10 +7,9 @@ import {
KEY_LIST_DOC,
ModalRemoveDoc,
useCreateFavoriteDoc,
useDeleteFavoriteDoc,
} from '@/features/docs/doc-management';
import { useDeleteFavoriteDoc } from '../../doc-management/api/useDeleteFavoriteDoc';
interface DocsGridActionsProps {
doc: Doc;
openShareModal?: () => void;
@@ -24,10 +23,10 @@ export const DocsGridActions = ({
const deleteModal = useModal();
const removeFavoriteDoc = useDeleteFavoriteDoc({
listInvalideQueries: [KEY_LIST_DOC],
listInvalidQueries: [KEY_LIST_DOC],
});
const makeFavoriteDoc = useCreateFavoriteDoc({
listInvalideQueries: [KEY_LIST_DOC],
listInvalidQueries: [KEY_LIST_DOC],
});
const options: DropdownMenuOption[] = [
@@ -3,11 +3,10 @@ import { DateTime } from 'luxon';
import { css } from 'styled-components';
import { Box, Icon, StyledLink, Text } from '@/components';
import { DocShareModal } from '@/features/docs/doc-share/component/DocShareModal';
import { Doc, LinkReach } from '@/features/docs/doc-management';
import { DocShareModal } from '@/features/docs/doc-share';
import { useResponsiveStore } from '@/stores';
import { Doc, LinkReach } from '../../doc-management';
import { DocsGridActions } from './DocsGridActions';
import { SimpleDocItem } from './SimpleDocItem';
@@ -3,11 +3,12 @@ import { css } from 'styled-components';
import { Box, Icon, Text } from '@/components';
import { useCunninghamTheme } from '@/cunningham';
import { Doc, LinkReach } from '@/features/docs';
import PinnedDocumentIcon from '@/features/docs/doc-management/assets/pinned-document.svg';
import SimpleFileIcon from '@/features/docs/doc-management/assets/simple-document.svg';
import { Doc, LinkReach } from '@/features/docs/doc-management';
import { useResponsiveStore } from '@/stores';
import PinnedDocumentIcon from '../assets/pinned-document.svg';
import SimpleFileIcon from '../assets/simple-document.svg';
const ItemTextCss = css`
overflow: hidden;
text-overflow: ellipsis;
@@ -60,13 +60,14 @@ export const Header = () => {
}
/>
)}
<StyledLink href="/docs">
<StyledLink href="/">
<Box
$align="center"
$gap={spacings['3xs']}
$direction="row"
$position="relative"
$height="fit-content"
$margin={{ top: 'auto' }}
>
<Image priority src={IconDocs} alt={t('Docs Logo')} width={25} />
<Title />
@@ -1,47 +1,26 @@
import { useTranslation } from 'react-i18next';
import { css } from 'styled-components';
import { Box, Text } from '@/components/';
import { useCunninghamTheme } from '@/cunningham';
type TitleProps = {
size?: 'sm' | 'md';
};
const Title = ({ size = 'sm' }: TitleProps) => {
const Title = () => {
const { t } = useTranslation();
const theme = useCunninghamTheme();
const spacings = theme.spacingsTokens();
const colors = theme.colorsTokens();
return (
<Box
$direction="row"
$align="center"
$gap={size === 'sm' ? spacings['2xs'] : '18px'}
>
<Text
$margin="none"
as="h2"
$color="#000091"
$zIndex={1}
$size={size === 'sm' ? '1.30rem' : '48px'}
>
<Box $direction="row" $align="center" $gap={spacings['2xs']}>
<Text $margin="none" as="h2" $color="#000091" $zIndex={1} $size="1.30rem">
{t('Docs')}
</Text>
<Text
$padding={{
horizontal: size === 'sm' ? '2xs' : '13px',
vertical: size === 'sm' ? '3xs' : '9px',
}}
$size={size === 'sm' ? '11px' : '24px'}
$padding={{ horizontal: 'xs', vertical: '1px' }}
$size="11px"
$theme="primary"
$variation="500"
$weight="bold"
$radius={size === 'sm' ? '12px' : '24px'}
$css={css`
line-height: ${size === 'sm' ? '16px' : '20px'};
`}
$radius="12px"
$background={colors['primary-200']}
>
BETA
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 385 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 284 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 919 B

@@ -1,84 +0,0 @@
import { Button } from '@openfun/cunningham-react';
import Image from 'next/image';
import { useTranslation } from 'react-i18next';
import { css } from 'styled-components';
import { Box, Icon, Text } from '@/components';
import { ProConnectButton } from '@/components/ProConnectButton';
import { useCunninghamTheme } from '@/cunningham';
import { useResponsiveStore } from '@/stores';
import firstImage from '../assets/first.png';
import DocLogo from '../assets/logo-docs.png';
export default function HomeBanner() {
const { t } = useTranslation();
const { spacingsTokens } = useCunninghamTheme();
const spacings = spacingsTokens();
const { isDesktop } = useResponsiveStore();
return (
<Box $maxWidth="78rem" $width="100%" $justify="center" $align="center">
<Box
$width="100%"
$padding={{ top: 'xxxl', bottom: 'calc(3.5rem + 94px)' }}
$justify="space-between"
$align="center"
$height="calc(100dvh - 94px)"
$position="relative"
$direction={isDesktop ? 'row' : 'column'}
>
<Box
$width="100%"
$justify="center"
$align="center"
$padding={{ horizontal: '10px' }}
$gap={spacings['sm']}
>
<Image src={DocLogo} alt="DocLogo" />
<Text
$size={isDesktop ? 'xs-alt' : '2.3rem'}
$variation="800"
$weight="bold"
$textAlign="center"
$css={css`
line-height: 56px;
`}
>
{t('Collaborative writing made simple')}
</Text>
<Text $size="lg" $variation="700" $textAlign="center">
{t(
'Collaborate and write in real time, without layout constraints.',
)}
</Text>
<ProConnectButton />
</Box>
{isDesktop && (
<Image src={firstImage} alt="first" style={{ maxWidth: '50%' }} />
)}
<Box
$position="absolute"
$padding="base"
$justify="center"
$align="center"
$css={css`
bottom: 0;
left: 0;
right: 0;
`}
>
<Button
href="#docs-app-info"
color="secondary"
icon={
<Icon $theme="primary" $variation="800" iconName="expand_more" />
}
>
Voir plus
</Button>
</Box>
</Box>
</Box>
);
}
@@ -1,106 +0,0 @@
import { useTranslation } from 'react-i18next';
import IconDocs from '@/assets/common/logo-docs.svg';
import { Box, Text } from '@/components';
import { ProConnectButton } from '@/components/ProConnectButton';
import { useCunninghamTheme } from '@/cunningham';
import { Footer } from '@/features/footer';
import Title from '@/features/header/components/Title/Title';
import { useResponsiveStore } from '@/stores';
import SC1Responsive from '../assets/SC1-responsive.png';
import SC1 from '../assets/SC1.png';
import SC2 from '../assets/SC2.png';
import SC3 from '../assets/SC3.png';
import SC4Responsive from '../assets/SC4-responsive.png';
import SC4 from '../assets/SC4.png';
import HomeBanner from './HomeBanner';
import { HomeHeader } from './HomeHeader';
import { HomeSection } from './HomeSection';
export default function HomeContent() {
const { t } = useTranslation();
const { isDesktop } = useResponsiveStore();
const { spacingsTokens } = useCunninghamTheme();
const spacings = spacingsTokens();
return (
<Box $background="white">
<HomeHeader />
<Box $align="center" $justify="center">
<Box $maxWidth="78rem" $width="100%" $justify="center" $align="center">
<HomeBanner />
<Box
id="docs-app-info"
$gap={isDesktop ? '230px' : '115px'}
$padding={{
vertical: spacings['6xl'],
}}
>
<HomeSection
shadow={isDesktop}
isColumn={true}
illustration={isDesktop ? SC1 : SC1Responsive}
title={t('An uncompromising writing experience.')}
tag={t('Write')}
description={t(
'Docs offers an intuitive writing experience. Its minimalist interface favors content over layout, while offering the essentials: media import, offline mode and keyboard shortcuts for greater efficiency.',
)}
/>
<HomeSection
isColumn={false}
illustration={SC2}
title={t('Simple and secure collaboration.')}
tag={t('Collaborate')}
description={t(
'Docs makes real-time collaboration simple. Invite collaborators - public officials or external partners - with one click to see their changes live, while maintaining precise access control for data security.',
)}
/>
<HomeSection
isColumn={false}
reverse={true}
illustration={SC3}
title={t('Flexible export.')}
tag={t('Export')}
description={t(
'To facilitate the circulation of documents, Docs allows you to export your content to the most common formats: PDF, Word or OpenDocument.',
)}
/>
<HomeSection
illustration={isDesktop ? SC4 : SC4Responsive}
title={t('A new way to organize knowledge.')}
tag={t('Organize')}
availableSoon={true}
description={t(
'Docs transforms your documents into knowledge bases thanks to subpages, powerful search and the ability to pin your important documents.',
)}
/>
<Box
$gap={spacings['md']}
$justify="center"
$align="center"
$padding={{ vertical: '140px' }}
>
<Box
$align="center"
$gap={spacings['3xs']}
$direction="row"
$position="relative"
$height="fit-content"
>
<IconDocs />
<Title size="md" />
</Box>
<Text $size="md" $variation="1000" $textAlign="center">
{t('Docs is already available, log in to use it now.')}
</Text>
<ProConnectButton />
</Box>
</Box>
</Box>
</Box>
<Footer />
</Box>
);
}
@@ -1,57 +0,0 @@
import Image from 'next/image';
import { useTranslation } from 'react-i18next';
import IconDocs from '@/assets/common/logo-docs-sm.png';
import { Box } from '@/components';
import { useCunninghamTheme } from '@/cunningham';
import { LaGaufre } from '@/features/header/components/LaGaufre';
import Title from '@/features/header/components/Title/Title';
import { LanguagePicker } from '@/features/language';
import { useResponsiveStore } from '@/stores';
export const HomeHeader = () => {
const { t } = useTranslation();
const { isDesktop } = useResponsiveStore();
const { themeTokens, spacingsTokens } = useCunninghamTheme();
const logo = themeTokens().logo;
const spacings = spacingsTokens();
return (
<Box
$direction="row"
$justify="space-between"
$align="center"
$width="100%"
$padding={{ horizontal: '18px', vertical: 'base' }}
>
<Box $align="center" $gap="3rem" $direction="row">
{logo && (
<Image
priority
src={logo.src}
alt={logo.alt}
width={0}
height={0}
style={{ width: 109, height: 'auto' }}
/>
)}
{isDesktop && (
<Box
$align="center"
$gap={spacings['3xs']}
$direction="row"
$position="relative"
$height="fit-content"
>
<Image src={IconDocs} alt="Docs app logo" />
<Title />
</Box>
)}
</Box>
<Box $direction="row" $gap="1rem" $align="center">
<LanguagePicker />
<LaGaufre />
</Box>
</Box>
);
};
@@ -1,126 +0,0 @@
import Image, { ImageProps } from 'next/image';
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { css } from 'styled-components';
import { Box, Text } from '@/components';
import { useCunninghamTheme } from '@/cunningham';
import { useResponsiveStore } from '@/stores';
export type HomeSectionProps = {
illustration?: ImageProps['src'];
title: string;
description: string;
tag: string;
availableSoon?: boolean;
shadow?: boolean;
isColumn?: boolean;
reverse?: boolean;
};
export const HomeSection = ({
illustration,
title,
description,
tag,
reverse = false,
isColumn = true,
availableSoon = false,
shadow = false,
}: HomeSectionProps) => {
const { t } = useTranslation();
const { spacingsTokens } = useCunninghamTheme();
const spacings = spacingsTokens();
const { isDesktop } = useResponsiveStore();
const direction = useMemo(() => {
if (!isDesktop) {
return 'column';
} else if (isColumn) {
return reverse ? 'column-reverse' : 'column';
}
return reverse ? 'row-reverse' : 'row';
}, [isColumn, isDesktop, reverse]);
return (
<Box
$direction={direction}
$gap={spacings['lg']}
$padding={{
horizontal: isDesktop ? '6xl' : spacings['md'],
}}
$align={isDesktop ? 'flex-start' : 'center'}
$justify={isDesktop ? 'flex-start' : 'center'}
>
<Box $gap={spacings['sm']} $maxWidth="850px" $width="100%">
<Box $direction="row" $gap={spacings['sm']} $wrap="wrap">
<SectionTag tag={tag} />
{availableSoon && (
<SectionTag tag={t('Available soon')} availableSoon />
)}
</Box>
<Text
$css={css`
line-height: 50px;
`}
$variation="1000"
$weight="bold"
$size={isDesktop ? 'xs-alt' : 'h1'}
>
{title}
</Text>
<Text $variation="700" $weight="400" $size="md">
{description}
</Text>
</Box>
{illustration && (
<Image
src={illustration}
alt="SC4Illustration"
style={{
maxWidth: 'calc(100dvw - 50px)',
height: 'auto',
boxShadow: shadow
? '0px 5px 25.1px 0px rgba(0, 0, 0, 0.08)'
: 'none',
}}
/>
)}
</Box>
);
};
const SectionTag = ({
tag,
availableSoon,
}: {
tag: string;
availableSoon?: boolean;
}) => {
const { colorsTokens, spacingsTokens } = useCunninghamTheme();
const spacings = spacingsTokens();
const colors = colorsTokens();
return (
<Box
$background={
!availableSoon ? colors['primary-100'] : colors['warning-100']
}
$padding={{ horizontal: spacings['sm'], vertical: '6px' }}
$css={css`
align-self: flex-start;
border-radius: 4px;
`}
>
<Text
$size="md"
$variation={availableSoon ? '600' : '800'}
$weight="bold"
$theme={availableSoon ? 'warning' : 'primary'}
>
{tag}
</Text>
</Box>
);
};

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