Compare commits

..

20 Commits

Author SHA1 Message Date
Ephraim Atta-Duncan 5cb03a9448 Merge branch 'main' into feat/handle-redirectto-param 2025-06-16 22:02:57 +00:00
Timur Ercan 4fd8a767b2 chore: Update README.md (#1840) 2025-06-13 22:42:38 +10:00
David Nguyen b8e08e88ac fix: api keys not showing (#1839) 2025-06-13 17:20:03 +10:00
David Nguyen 031a7b9e36 fix: visibility 2025-06-13 01:02:40 +10:00
David Nguyen 12fe045195 fix: visiblity 2025-06-13 00:05:08 +10:00
David Nguyen 614106a5e4 fix: rework documents limits logic (#1836) 2025-06-12 13:42:31 +10:00
Lucas Smith 8be7137b59 v1.12.0-rc.4 2025-06-12 10:27:41 +10:00
Lucas Smith 31e2a6443e fix: legacy authOptions support for api v1 2025-06-12 10:21:41 +10:00
Ephraim Duncan 400d2a2b1a feat: sign out of all sessions (#1797) 2025-06-11 17:57:38 +10:00
David Nguyen e3ce7f94e6 chore: update build 2025-06-11 14:52:23 +10:00
Shubham Palriwala cad04f26e7 feat: sitemap auto-generation for docs (#1822) 2025-06-11 14:09:45 +10:00
Ephraim Duncan d27f0ee0ef fix: duplicate field bugs (#1685) 2025-06-11 13:26:19 +10:00
Ephraim Duncan fd2b413ed9 chore: increase wait times for tests (#1778) 2025-06-11 13:25:21 +10:00
Catalin Pit d11ec8fa2a feat: show field coordinates in devmode (#1802)
Show the fields coordinates when the `devmode` search param is present.
It's meant to help API users understand where to position the fields.
2025-06-11 12:28:39 +10:00
Damien B. b1127b4f0d chore: update readme 2025-06-11 10:42:32 +10:00
Lucas Smith be4244fb62 chore: add translations (#1832)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2025-06-11 10:39:33 +10:00
David Nguyen 504a0893ab chore: add organisation docs (#1831) 2025-06-10 20:54:36 +10:00
Ephraim Duncan 266f2eab71 Merge branch 'main' into feat/handle-redirectto-param 2025-05-23 03:12:45 +00:00
Ephraim Atta-Duncan e1fc49fa49 chore: use cookie in production 2025-05-21 11:36:30 +00:00
Ephraim Atta-Duncan d213b378b8 feat: handle redirectTo query parameter in middleware 2025-05-21 11:31:00 +00:00
99 changed files with 1871 additions and 519 deletions
+3 -5
View File
@@ -49,8 +49,6 @@ Join us in creating the next generation of open trust infrastructure.
## Community and Next Steps 🎯
We're currently working on a redesign of the application, including a revamp of the codebase, so Documenso can be more intuitive to use and robust to develop upon.
- Check out the first source code release in this repository and test it.
- Tell us what you think in the [Discussions](https://github.com/documenso/documenso/discussions).
- Join the [Discord server](https://documen.so/discord) for any questions and getting to know to other community members.
@@ -247,14 +245,14 @@ Now you can install the dependencies and build it:
```
npm i
npm run build:web
npm run build
npm run prisma:migrate-deploy
```
Finally, you can start it with:
```
cd apps/web
cd apps/remix
npm run start
```
@@ -275,7 +273,7 @@ After=network.target
Environment=PATH=/path/to/your/node/binaries
Type=simple
User=www-data
WorkingDirectory=/var/www/documenso/apps/web
WorkingDirectory=/var/www/documenso/apps/remix
ExecStart=/usr/bin/next start -p 3500
TimeoutSec=15
Restart=always
+5
View File
@@ -34,3 +34,8 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts
# next-sitemap output
/public/sitemap.xml
/public/robots.txt
/public/sitemap-*.xml
@@ -0,0 +1,5 @@
/** @type {import('next-sitemap').IConfig} */
module.exports = {
siteUrl: 'https://docs.documenso.com', // Replace with your actual site URL
generateRobotsTxt: true, // Generates robots.txt
};
+3 -2
View File
@@ -4,7 +4,7 @@
"private": true,
"scripts": {
"dev": "next dev -p 3002",
"build": "next build",
"build": "next build && next-sitemap",
"start": "next start -p 3002",
"lint:fix": "next lint --fix",
"clean": "rimraf .next && rimraf node_modules"
@@ -26,6 +26,7 @@
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"next-sitemap": "^4.2.3",
"typescript": "5.6.2"
}
}
}
@@ -5,6 +5,7 @@
"title": "Development & Deployment"
},
"local-development": "Local Development",
"developer-mode": "Developer Mode",
"self-hosting": "Self Hosting",
"contributing": "Contributing",
"-- API & Integration Guides": {
@@ -0,0 +1,18 @@
---
title: Field Coordinates
description: Learn how to get the coordinates of a field in a document.
---
## Field Coordinates
Field coordinates represent the position of a field in a document. They are returned in the `pageX` and `pageY` properties of the field.
To enable field coordinates, you can use the `devmode` query parameter.
```bash
https://app.documenso.com/documents/<document-id>/edit?devmode=true
```
You should then see the coordinates on top of each field.
![Field Coordinates](/developer-mode/field-coordinates.webp)
+5 -4
View File
@@ -6,11 +6,12 @@
"title": "How To Use"
},
"get-started": "Get Started",
"profile": "User Profile",
"signing-documents": "Signing Documents",
"profile": "Public Profile",
"organisations": "Organisations",
"documents": "Documents",
"templates": "Templates",
"branding": "Branding",
"direct-links": "Direct Signing Links",
"teams": "Teams",
"-- Legal Overview": {
"type": "separator",
"title": "Legal Overview"
@@ -18,4 +19,4 @@
"fair-use": "Fair Use Policy",
"licenses": "Licenses",
"compliance": "Compliance"
}
}
@@ -0,0 +1,28 @@
---
title: Branding Preferences
description: Learn how to set the branding preferences for your team account.
---
import Image from 'next/image';
import { Callout, Steps } from 'nextra/components';
# Branding Preferences
Branding preferences allow you to set the default settings when emailing documents to your recipients.
## Preferences
Branding preferences can be set on either the organisation or team level.
By default, teams inherit the preferences from the organisation. You can override these preferences on the team level at any time.
To access the preferences, navigate to either the organisation or teams settings page and click the **Preferences** tab. This page contains both the preferences for documents and branding, the branding section is located at the bottom of the page.
![A screenshot of the organisation's document preferences page](/organisations/organisation-branding.webp)
On this page, you can:
- **Upload a Logo** - Upload your team's logo to be displayed instead of the default Documenso logo.
- **Set the Brand Website** - Enter the URL of your team's website to be displayed in the email communications sent by the team.
- **Add Additional Brand Details** - You can add additional information to display at the bottom of the emails sent by the team. This can include contact information, social media links, and other relevant details.
@@ -0,0 +1,5 @@
{
"sending-documents": "Sending Documents",
"document-preferences": "Document Preferences",
"document-visibility": "Document Visibility"
}
@@ -0,0 +1,42 @@
---
title: Preferences
description: Learn how to manage your team's global preferences.
---
import Image from 'next/image';
import { Callout, Steps } from 'nextra/components';
# Document Preferences
Document preferences allow you to set the default settings when creating new documents and templates.
For example, you can set the default language for documents sent by the team, or set the allowed signatures types.
## Preferences
Document preferences can be set on either the organisation or team level.
By default, teams inherit the preferences from the organisation. You can override these preferences on the team level at any time.
To access the preferences, navigate to either the organisation or teams settings page and click the **Preferences** tab.
![A screenshot of the organisation's document preferences page](/organisations/organisation-document-preferences.webp)
- **Document Visibility** - Set the default visibility of the documents created by team members. Learn more about [document visibility](/users/documents/document-visibility).
- **Default Document Language** - This setting allows you to set the default language for the documents uploaded in the organisation. The default language is used as the default language in the email communications with the document recipients.
- **Signature Settings** - Controls what signatures are allowed to be used when signing the documents.
- **Sender Details** - Set whether the sender's name should be included in the emails sent by the team. See more below [sender details](/users/documents/document-preferences#sender-details).
- **Include the Signing Certificate** - This setting controls whether the signing certificate should be included in the signed documents. If enabled, the signing certificate is included in the signed documents. If disabled, the signing certificate is not included in the signed documents. Regardless of this setting, the signing certificate is always available in the document's audit log page.
Document visibility, language and signature settings can be overriden on a per document basis.
### Sender Details
If the **Sender Details** setting is enabled, the emails sent by the team will include the sender's name. The email will say:
> "Example User" on behalf of "Example Team" has invited you to sign "document.pdf"
If the **Sender Details** setting is disabled, the emails sent by the team will not include the sender's name. The email will say:
> "Example Team" has invited you to sign "document.pdf"
@@ -5,19 +5,25 @@ description: Learn how to control the visibility of your team documents.
import { Callout } from 'nextra/components';
# Team's Document Visibility
# Document Visibility
The default document visibility option allows you to control who can view and access the documents uploaded to your team account. The document visibility can be set to one of the following options:
The default document visibility option allows you to control who can view and access the documents uploaded within a team.
This value can either be set in the [document preferences](/users/documents/document-preferences), or when you [create the document](/users/documents/send-document)
## Document Visibility Options
The document visibility can be set to one of the following options:
- **Everyone** - The document is visible to all team members.
- **Managers and above** - The document is visible to team members with the role of _Manager or above_ and _Admin_.
- **Admin only** - The document is only visible to the team's admins.
![A screenshot of the document visibility selector from the team's global preferences page](/teams/team-preferences-document-visibility.webp)
The default document visibility is set to "_EVERYONE_" by default. You can change this setting by going to the [document preferences page](/users/documents/document-preferences) and selecting a different visibility option.
The default document visibility is set to "_EVERYONE_" by default. You can change this setting by going to the [team's general preferences page](/users/teams/preferences) and selecting a different visibility option.
![Document visibility preference](/organisations/organisation-document-visibility.webp)
Here's how it works:
## How it works
- If a user with the "_Member_" role creates a document and the default document visibility is set to "_Everyone_", the document's visibility is set to "_EVERYONE_".
- The user can't change the visibility of the document in the document editor.
@@ -115,7 +115,7 @@ All fields can be placed anywhere on the document and resized as needed.
<Callout type="info">
Learn more about the available field types and how to use them on the [Fields
page](signing-documents/fields).
page](/users/documents/fields).
</Callout>
#### Signature Required
@@ -10,7 +10,7 @@ import { Callout, Steps } from 'nextra/components';
<Steps>
### Pick a Plan
The first step to start using Documenso is to pick a plan and create an account. At the moment of writing this guide, we have 3 plans available: Free, Individual, and Teams.
The first step to start using Documenso is to pick a plan and create an account. At the moment of writing this guide, we have 3 plans available: Free, Individual, Teams and Platform.
Explore each plan's features and choose the one that best suits your needs. The [pricing page](https://documen.so/pricing) has more information about the plans.
@@ -28,6 +28,6 @@ You can claim a premium username by upgrading to a paid plan. After upgrading to
### Optional: Create a Team
If you are working with others, you can create a team and invite your team members to collaborate on your documents. More information about teams is available in the [Teams section](/users/get-started/teams).
If you are working with others, you can create a team and invite your team members to collaborate on your documents. More information about teams is available in the [Teams section](/users/organisations/teams).
</Steps>
@@ -1,99 +0,0 @@
---
title: Teams
description: Learn how to create and manage teams in Documenso.
---
import Image from 'next/image';
import { Callout, Steps } from 'nextra/components';
# Teams
Documenso allows you to create teams to collaborate with others on creating and signing documents.
<Steps>
### Create a New Team
Anyone can create a team from their account by clicking on the "+" (plus) button in the "Teams" section from the account dropdown.
![Documenso account dropdown menu](/get-started-images/add-team.webp)
Each team is a separate entity with its members, documents, and templates. You can create as many teams as you like but remember that each team is billed separately.
<Callout type="info">You can transfer the ownership of the team at any time.</Callout>
### Name and URL
Clicking the "+" button will open a modal where you must pick your team's name and URL. The URL is the team's identifier and will link to the team's page and settings. An example URL would be:
```bash
https://app.documenso.com/t/<your-team-name>
```
![Documenso create team modal](/get-started-images/add-team-2.webp)
You can select a different name and URL for your team, but we recommend using the same or similar name.
### Invite Team Members
After creating the team, you can invite team members by navigating to the "Members" tab in the team settings and clicking the "Invite member" button.
To access the team settings, click on the team's name in the account dropdown and select the appropriate team. Lastly, click again on the avatar and then "Team Settings".
Or you can copy this URL:
```bash
https://app.documenso.com/t/<your-team-name>/settings/members
```
Once you click on the "Invite member" button, you will be prompted to enter the email address of the person you want to invite. You can also select the role of the person you are inviting.
![Invite team members in Documenso dashboard](/get-started-images/add-team-members-documenso.webp)
You can also bulk-invite members by uploading a CSV file with the email addresses and roles of the people you want to invite.
The table below shows how the CSV file should be structured:
| Email address | Role |
| -------------------------- | ------- |
| team-admin@documenso.com | Admin |
| team-manager@documenso.com | Manager |
| team-member@documenso.com | Member |
<Callout type="info">
The basic team plan includes 5 members. You can invite as many members as you like by upgrading
your team's seats on the team's billing page.
</Callout>
#### Roles
You can assign different permissions to team members based on their roles. The roles available are:
| Role | Create, Edit, Send Documents | Manage Users | Manage Admins | Settings | Billing | Delete/ Transfer |
| :-----: | :--------------------------: | :----------: | :-----------: | :------: | :-----: | :--------------: |
| Member | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
| Manager | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ |
| Admin | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
| Owner | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
### Set a Team Email
You can add a team email to make signing and sending documents easier. Adding a team email allows you to:
- See a signing request sent to this email (Team Inbox)
- See all documents sent on behalf of the team
### (Optional) Transfer Team Ownership
You can transfer the team's ownership at any time. To do this, navigate to the "General" tab in the team settings and click the "Transfer team" button.
Use this URL to get to the team settings:
```bash
https://app.documenso.com/t/<your-team-name>/settings
```
### [Send your First Document](https://app.documenso.com/)
</Steps>
@@ -0,0 +1,7 @@
{
"index": "Introduction",
"members": "Members",
"groups": "Groups",
"teams": "Teams",
"billing": "Billing"
}
@@ -0,0 +1,19 @@
---
title: Billing
description: Learn how to manage your organisation's billing and subscription.
---
import Image from 'next/image';
import { Callout, Steps } from 'nextra/components';
### Billing and Subscription Management
Organisations handle billing centrally, making it easier to manage:
- **Unified Billing**: One subscription covers all teams in the organisation
- **Seat Management**: Add or remove seats across all teams automatically (Teams plan)
You can change plans, view invoices and manage your subscription from the billing page which is accessible from the organisation settings.
![A screenshot of the organisation's billing page](/organisations/organisations-billing.webp)
@@ -0,0 +1,75 @@
---
title: Preferences
description: Learn how to manage your team's global preferences.
---
import Image from 'next/image';
import { Callout, Steps } from 'nextra/components';
# Organisation Groups
Organisation groups are a powerful administrative tool that streamlines user management across your entire organisation. Instead of manually assigning individual users to multiple teams, groups allow you to manage access at scale.
This automated approach ensures consistent permissions while reducing administrative overhead for tasks like onboarding employees or managing contractor access.
## Understanding groups
### Key Benefits
- **Instant Access Management**: New hires get immediate, appropriate access across all relevant teams
- **Bulk Operations**: Remove an entire group (like a departing contractor team) and all members lose access simultaneously
- **Role Consistency**: Ensure the same role is applied consistently across teams—no more accidentally giving admin access when member access was intended
- **Audit Trail**: Easily track which groups have access to which teams
### Example use case: Legal Compliance Team
Imagine you have a legal compliance team that needs access to review documents across all departments. Instead of manually adding each legal team member to every departmental team (Sales, Marketing, HR, Operations), you can:
1. Create a "Legal Compliance" group with the "Member" Organisation Role
2. Add legal team members to this group
3. Assign the "Legal Compliance" group to the required teams
Now, when Sarah from Legal joins the company, you can simply add her to the "Legal Compliance" group. Once added, she automatically gains access to all teams the "Legal Compliance" group is assigned to.
When John from Legal leaves the company, you remove him from the group and his access is instantly revoked across all teams.
## Getting started with groups
Navigate to the "Groups" section in your organisation settings to create and manage groups.
There are two types of roles when using groups:
- **Organisation Role**: A global organisation role given to all members of the group
- **Team Role**: A team role you select when assigning the group to a team
You should generally have the "Organisation Role" set to "Organisation Member", otherwise these members would by default have access to all teams anyway due to the high organisation role.
### Creating Custom Groups
When creating a custom group, you can:
1. **Name the Group**: Give it a descriptive name that reflects its purpose
2. **Set Organisation Role**: Define the default **organisation role** for group members
3. **Add Members**: Include organisation members in the group
![Organisation group creation](/organisations/organisation-group-create.webp)
### Manage Custom Groups
By clicking the "Manage" button on a custom group, you can view all teams it is assigned to and modify the group's settings.
![Organisation group management](/organisations/organisation-group-manage.webp)
### Assigning a group to a team
To assign a group to a team, you need to navigate to the team settings and click the "Groups" tab.
![Organisation group assignment](/organisations/organisation-group-assignment.webp)
From here, click the "Add groups" button to begin the process of assigning a group to a team. Once you have added the group you can see that the members have been automatically added to the team in the members tab.
## What's next?
- [Create Your First Team](/users/organisations/teams)
- [Manage Default Settings](/users/documents/document-preferences)
@@ -0,0 +1,65 @@
---
title: Organisations
description: Learn how to create and manage organisations in Documenso.
---
import Image from 'next/image';
import { Callout, Steps } from 'nextra/components';
# Organisations
Organisations allow you to manage multiple teams and users under a single managed entity. This powerful feature enables enterprise-level collaboration and streamlined management across your entire organisation.
## What are Organisations?
Organisations are the top-level entity in Documenso's hierarchy structure:
![Organisations diagram](/organisations/organisations-basic-diagram.webp)
Each organisation can contain multiple teams, and each team can have multiple members. This structure provides:
- **Centralized Management**: Control multiple teams from a single organisational dashboard
- **Unified Billing**: Manage billing and subscriptions at the organisation level
- **Access Control**: Define roles and groups across the entire organisation
- **Group Management**: Create custom groups to organise members and control team access
- **Global Settings**: Apply consistent settings across all teams in your organisation
## Create a new organisation
You can create multiple organisations, but each organisation will be billed separately.
<Steps>
### Creating Organisations
To create a new organisation, navigate to the organisation section in your account settings and click the "Create Organisation" button.
![Create organisation in Documenso dashboard](/organisations/organisations-create.webp)
### Select your plan
Choose from our range of plans for your new organisation. If you want to instead upgrade your current organisation, you can do so by going into your settings billing page and upgrade it there.
### Name setup
When creating an organisation, you'll need to provide:
- **Organisation Name**: The display name for your organisation
</Steps>
Once your organisation is established, you can create teams to organise your work and collaborate effectively. Each team operates independently but inherits organisation-level settings and branding.
## Best Practices for Organisation Management
1. **Use groups effectively**: Leverage groups to simplify permission management
2. **Set default settings**: Configure organisation-wide settings for consistency
## What's next?
- [Create Your First Team](/users/organisations/teams)
- [Invite Organisation Members](/users/organisations/members)
- [Create Organisation Groups](/users/organisations/groups)
- [Manage Default Settings](/users/documents/document-preferences)
- [Manage Default Branding](/users/branding)
@@ -0,0 +1,65 @@
---
title: Members
description: Learn how to invite and manage your organisation's members.
---
import Image from 'next/image';
import { Callout, Steps } from 'nextra/components';
# Organisation Members
Organisation members are the core users of your organisation. They are the ones who can access the team resources and collaborate with other members.
## Organisation Roles
You can assign different permissions to organisation members by using roles. The roles available are:
| Role | Manage Settings/Teams/Members | Billing | Delete Organisation |
| :------------------: | :---------------------------: | :-----: | ------------------- |
| Organisation Owner | ✅ | ✅ | ✅ |
| Organisation Admin | ✅ | ✅ | ✅ |
| Organisation Manager | ✅ | ❌ | ❌ |
| Organisation Member | ❌ | ❌ | ❌ |
<Callout type="info">
Organisation admins and managers will automatically have access to all teams as the "Team Admin"
role. When creating a team you can also decide whether to automatically allow normal members to
access it by default as well.
</Callout>
## Invite Organisation Members
To invite organisation members, you need to be an organisation owner, admin or manager.
1. Open the menu switcher top right
2. Hover over your new organisation and click the settings icon
3. Navigate to the "Members" tab
4. Click "Invite Member"
Once you click on the "Invite member" button, you will be prompted to enter the email address of the person you want to invite. You can also select the role of the person you are inviting.
![Invite organisation members](/organisations/organisations-member-invite.webp)
You can also bulk-invite members by uploading a CSV file with the email addresses and roles of the people you want to invite.
The table below shows how the CSV file should be structured:
| Email address | Role |
| ------------------------- | ------- |
| org-admin@documenso.com | Admin |
| org-manager@documenso.com | Manager |
| org-member@documenso.com | Member |
<Callout type="info">
The basic team plan includes 5 organisation members. Going over the 5 members will charge your
organisation according to the seat plan pricing.
</Callout>
## Manage Organisation Members
On the same page, you can change the organisation member's roles or remove them from the organisation.
## What's next?
- [Use groups to organise your members](/users/organisations/groups)
@@ -0,0 +1,121 @@
---
title: Teams
description: Learn how to create and manage teams in Documenso.
---
import Image from 'next/image';
import { Callout, Steps } from 'nextra/components';
# Teams
Documenso teams allow you to collaborate with others on creating, sending and receiving documents within your organisation. Teams operate within the organisational structure and inherit settings and branding from their parent organisation.
## Team Structure
Teams provide focused collaboration spaces while benefiting from organisation-level management and settings.
Each team within an organisation has its own:
- Team members and roles
- Documents and templates
- Team-specific settings (that can override organisation defaults)
- Team email and branding (if enabled)
## Creating a Team
Only members with the "Organisation Admin" or "Organisation Manager" role can create teams.
<Steps>
### Create Team
To create a team, navigate to the organisation settings page and click the "Teams" tab. Then you can click the "Create Team" button.
![Create Team Dialog](/teams/team-create.webp)
### Name and URL
When creating a team, you'll need to provide:
- **Team Name**: The display name for your team
- **Team URL**: A unique identifier for your team
The team URL will follow this format:
```bash
https://app.documenso.com/t/<team-url>
```
You can select different names and URLs for your team, but we recommend using the same or similar names for consistency.
![Documenso create team modal](/teams/team-create-dialog.webp)
You can also decide whether to automatically inherit members from the organisation into the team. This means that all members of the organisation will have access to this team.
Members with the "Organisation Admin" or "Organisation Manager" role will be assigned as "Team Admin" regardless of this setting. This will only affect members with the "Organisation Member" role, who will be added to the team as a "Team Member".
Disabling this setting will remove all these members automatically. This can always be turned on or off later in the teams member settings page.
</Steps>
## Team Members
After creating the team, you can add organisation members into your team using two methods:
- Directly adding members to the team
- Add members using groups
### Directly adding members
1. Navigate to the team settings member page
2. Click the "Add Members" button
3. Choose which members you want to add
4. Assign the team roles for each team member
If you want to add people outside of the organisation, you will need to invite them to the organisation first.
See the [organisation members](/users/organisations/members#invite-organisation-members) page for more information.
### Adding members using groups
1. Navigate to the teams settings groups page
2. Click the "Add groups" button
3. Choose which groups you want to add
4. Assign the team roles for each group
See the [organisation groups](/users/organisations/groups) page for more information.
### Team Member Roles
You can assign different permissions to team members based on their roles. The roles available are:
| Role | Manage Documents | Manage Team | Delete Team |
| :----------: | :--------------: | :---------: | :---------: |
| Team Admin | ✅ | ✅ | ✅ |
| Team Manager | ✅ | ✅ | ❌ |
| Team Member | ✅ | ❌ | ❌ |
These roles can be used for document visibility and management as well.
## Set a Team Email
You can add a team email which allows you to:
- See signing requests sent to this email (Team Inbox)
- See documents sent from this email
- Send documents on behalf of the team
- Maintain consistent team branding in communications
## Team Settings and Branding
You can override preferences and settings from the Organisation on the Team level. See the following pages for more information:
- [Document preferences](/users/documents/document-preferences)
- [Branding preferences](/users/branding/branding-preferences)
## What's next?
- [Send your first document](/users/documents/sending-documents)
- [Setup your default document preferences](/users/documents/document-preferences)
- [Setup your default branding preferences](/users/branding)
+4 -2
View File
@@ -1,5 +1,5 @@
---
title: User Profile
title: Public Profile
description: Learn how to set up your public profile on Documenso.
---
@@ -15,7 +15,7 @@ Documenso allows you to create a public profile to share your templates for anyo
### Navigate to Your Profile Settings
Click on your profile picture in the top right corner and select "User settings". Then, navigate to the "Public Profile" tab to configure your profile.
Click on your profile picture in the top right corner and select "Settings" or "Team Settings". Then, navigate to the "Public Profile" tab to configure your profile.
![The profile settings page](/public-profile/documenso-public-profile-settings.webp)
@@ -45,6 +45,8 @@ You can choose to make your profile public or private. Only you can access it if
To make your profile public, toggle the switch to the right ("Show") at the top right-hand side of the page.
![An example of a enabling a public profile on Documenso](/public-profile/documenso-enable-public-profile-settings.webp)
### (Optional) Link Templates
Linking templates to your profile is optional, but it's what makes your profile helpful. Linking templates allow people to sign documents directly from your profile. As a result, we recommend linking at least one template you want to share with others.
@@ -1,4 +0,0 @@
{
"index": "Send Documents",
"fields": "Document Fields"
}
@@ -1,6 +0,0 @@
{
"preferences": "Preferences",
"document-visibility": "Document Visibility",
"sender-details": "Email Sender Details",
"branding-preferences": "Branding Preferences"
}
@@ -1,16 +0,0 @@
---
title: Branding Preferences
description: Learn how to set the branding preferences for your team account.
---
# Branding Preferences
You can set the branding preferences for your team account by going to the **Branding Preferences** tab in the team's settings dashboard.
![A screenshot of the team's branding preferences page](/teams/team-branding-preferences.webp)
On this page, you can:
- **Upload a Logo** - Upload your team's logo to be displayed instead of the default Documenso logo.
- **Set the Brand Website** - Enter the URL of your team's website to be displayed in the email communications sent by the team.
- **Add Additional Brand Details** - You can add additional information to display at the bottom of the emails sent by the team. This can include contact information, social media links, and other relevant details.
@@ -1,19 +0,0 @@
---
title: Preferences
description: Learn how to manage your team's global preferences.
---
# Preferences
You can manage your team's global preferences by clicking on the **Preferences** tab in the team's settings dashboard.
![A screenshot of the team's global preferences page](/teams/team-preferences.webp)
The preferences page allows you to update the following settings:
- **Document Visibility** - Set the default visibility of the documents created by team members. Learn more about [document visibility](/users/teams/document-visibility).
- **Default Document Language** - This setting allows you to set the default language for the documents uploaded in the team account. The default language is used as the default language in the email communications with the document recipients. You can change the language for individual documents when uploading them.
- **Sender Details** - Set whether the sender's name should be included in the emails sent by the team. Learn more about [sender details](/users/teams/sender-details).
- **Typed Signature** - It controls whether the document recipients can sign the documents with a typed signature or not. If enabled, the recipients can sign the document using either a drawn or a typed signature. If disabled, the recipients can only sign the documents usign a drawn signature. This setting can also be changed for individual documents when uploading them.
- **Include the Signing Certificate** - This setting controls whether the signing certificate should be included in the signed documents. If enabled, the signing certificate is included in the signed documents. If disabled, the signing certificate is not included in the signed documents. Regardless of this setting, the signing certificate is always available in the document's audit log page.
- **Branding Preferences** - Set the branding preferences and defaults for the team account. Learn more about [branding preferences](/users/teams/branding-preferences).
@@ -1,14 +0,0 @@
---
title: Email Sender Details
description: Learn how to update the sender details for your team's email notifications.
---
## Sender Details
If the **Sender Details** setting is enabled, the emails sent by the team will include the sender's name. The email will say:
> "Example User" on behalf of "Example Team" has invited you to sign "document.pdf"
If the **Sender Details** setting is disabled, the emails sent by the team will not include the sender's name. The email will say:
> "Example Team" has invited you to sign "document.pdf"
Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 KiB

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 165 KiB

@@ -83,7 +83,9 @@ export const OrganisationCreateDialog = ({ trigger, ...props }: OrganisationCrea
const { mutateAsync: createOrganisation } = trpc.organisation.create.useMutation();
const { data: plansData } = trpc.billing.plans.get.useQuery();
const { data: plansData } = trpc.billing.plans.get.useQuery(undefined, {
enabled: IS_BILLING_ENABLED(),
});
const onFormSubmit = async ({ name }: TCreateOrganisationFormSchema) => {
try {
@@ -0,0 +1,94 @@
import { useState } from 'react';
import { Trans, useLingui } from '@lingui/react/macro';
import { authClient } from '@documenso/auth/client';
import { Button } from '@documenso/ui/primitives/button';
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '@documenso/ui/primitives/dialog';
import { useToast } from '@documenso/ui/primitives/use-toast';
type SessionLogoutAllDialogProps = {
onSuccess?: () => Promise<unknown>;
disabled?: boolean;
};
export const SessionLogoutAllDialog = ({ onSuccess, disabled }: SessionLogoutAllDialogProps) => {
const { t } = useLingui();
const { toast } = useToast();
const [isOpen, setIsOpen] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const handleSignOutAllSessions = async () => {
setIsLoading(true);
try {
await authClient.signOutAllSessions();
if (onSuccess) {
await onSuccess();
}
toast({
title: t`Sessions have been revoked`,
});
setIsOpen(false);
} catch (error) {
console.error(error);
toast({
title: t`Error`,
description: t`Failed to sign out all sessions`,
variant: 'destructive',
});
}
setIsLoading(false);
};
return (
<Dialog open={isOpen} onOpenChange={(value) => (isLoading ? undefined : setIsOpen(value))}>
<DialogTrigger asChild>
<Button variant="secondary" disabled={disabled}>
<Trans>Revoke all sessions</Trans>
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>
<Trans>Revoke all sessions</Trans>
</DialogTitle>
<DialogDescription>
<Trans>
This will sign you out of all other devices. You will need to sign in again on those
devices to continue using your account.
</Trans>
</DialogDescription>
</DialogHeader>
<DialogFooter>
<DialogClose asChild>
<Button variant="secondary" disabled={isLoading}>
<Trans>Cancel</Trans>
</Button>
</DialogClose>
<Button loading={isLoading} variant="destructive" onClick={handleSignOutAllSessions}>
<Trans>Revoke all sessions</Trans>
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
};
@@ -142,11 +142,11 @@ export const TeamGroupCreateDialog = ({ ...props }: TeamGroupCreateDialogProps)
.with('SELECT', () => (
<DialogHeader>
<DialogTitle>
<Trans>Add members</Trans>
<Trans>Add groups</Trans>
</DialogTitle>
<DialogDescription>
<Trans>Select members or groups of members to add to the team.</Trans>
<Trans>Select groups of members to add to the team.</Trans>
</DialogDescription>
</DialogHeader>
))
@@ -171,6 +171,27 @@ export default function SettingsSecurity({ loaderData }: Route.ComponentProps) {
</Link>
</Button>
</Alert>
<Alert
className="mt-6 flex flex-col justify-between p-6 sm:flex-row sm:items-center"
variant="neutral"
>
<div className="mb-4 mr-4 sm:mb-0">
<AlertTitle>
<Trans>Active sessions</Trans>
</AlertTitle>
<AlertDescription className="mr-2">
<Trans>View and manage all active sessions for your account.</Trans>
</AlertDescription>
</div>
<Button asChild variant="outline" className="bg-background">
<Link to="/settings/security/sessions">
<Trans>Manage sessions</Trans>
</Link>
</Button>
</Alert>
</div>
);
}
@@ -0,0 +1,192 @@
import { useMemo, useState } from 'react';
import { useLingui } from '@lingui/react/macro';
import { Trans } from '@lingui/react/macro';
import { useQuery } from '@tanstack/react-query';
import { DateTime } from 'luxon';
import { UAParser } from 'ua-parser-js';
import { authClient } from '@documenso/auth/client';
import { useSession } from '@documenso/lib/client-only/providers/session';
import { Badge } from '@documenso/ui/primitives/badge';
import { Button } from '@documenso/ui/primitives/button';
import type { DataTableColumnDef } from '@documenso/ui/primitives/data-table';
import { DataTable } from '@documenso/ui/primitives/data-table';
import { Skeleton } from '@documenso/ui/primitives/skeleton';
import { TableCell } from '@documenso/ui/primitives/table';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { SessionLogoutAllDialog } from '~/components/dialogs/session-logout-all-dialog';
import { SettingsHeader } from '~/components/general/settings-header';
import { appMetaTags } from '~/utils/meta';
export function meta() {
return appMetaTags('Active Sessions');
}
const parser = new UAParser();
export default function SettingsSecuritySessions() {
const { t } = useLingui();
const { data, isLoading, isLoadingError, refetch } = useQuery({
queryKey: ['active-sessions'],
queryFn: async () => await authClient.getSessions(),
});
const { session } = useSession();
const results = data?.sessions ?? [];
const columns = useMemo(() => {
return [
{
header: t`Device`,
accessorKey: 'userAgent',
cell: ({ row }) => {
const userAgent = row.original.userAgent || '';
parser.setUA(userAgent);
const result = parser.getResult();
const browser = result.browser.name || t`Unknown`;
const os = result.os.name || t`Unknown`;
const isCurrentSession = row.original.id === session?.id;
return (
<div className="flex items-center gap-2">
<span>
{browser} ({os})
</span>
{isCurrentSession && (
<Badge>
<Trans>Current</Trans>
</Badge>
)}
</div>
);
},
},
{
header: t`IP Address`,
accessorKey: 'ipAddress',
cell: ({ row }) => row.original.ipAddress || t`Unknown`,
},
{
header: t`Last Active`,
accessorKey: 'updatedAt',
cell: ({ row }) => DateTime.fromJSDate(row.original.updatedAt).toRelative(),
},
{
header: t`Created`,
accessorKey: 'createdAt',
cell: ({ row }) => DateTime.fromJSDate(row.original.createdAt).toRelative(),
},
{
id: 'actions',
cell: ({ row }) => (
<SessionRevokeButton
sessionId={row.original.id}
isCurrentSession={row.original.id === session?.id}
onSuccess={refetch}
/>
),
},
] satisfies DataTableColumnDef<(typeof results)[number]>[];
}, []);
return (
<div>
<SettingsHeader
title={t`Active sessions`}
subtitle={t`View and manage all active sessions for your account.`}
>
<SessionLogoutAllDialog onSuccess={refetch} disabled={results.length === 1 || isLoading} />
</SettingsHeader>
<div className="mt-4">
<DataTable
columns={columns}
data={results}
hasFilters={false}
error={{
enable: isLoadingError,
}}
skeleton={{
enable: isLoading,
rows: 3,
component: (
<>
<TableCell>
<Skeleton className="h-4 w-40 rounded-full" />
</TableCell>
<TableCell>
<Skeleton className="h-4 w-24 rounded-full" />
</TableCell>
<TableCell>
<Skeleton className="h-4 w-24 rounded-full" />
</TableCell>
<TableCell>
<Skeleton className="h-4 w-24 rounded-full" />
</TableCell>
<TableCell>
<Skeleton className="h-8 w-16 rounded" />
</TableCell>
</>
),
}}
/>
</div>
</div>
);
}
type SessionRevokeButtonProps = {
sessionId: string;
isCurrentSession: boolean;
onSuccess: () => Promise<unknown>;
};
const SessionRevokeButton = ({
sessionId,
isCurrentSession,
onSuccess,
}: SessionRevokeButtonProps) => {
const { toast } = useToast();
const { t } = useLingui();
const [isLoading, setIsLoading] = useState(false);
const handleRevoke = async () => {
setIsLoading(true);
try {
await authClient.signOutSession({
sessionId,
redirectPath: isCurrentSession ? '/signin' : undefined,
});
if (!isCurrentSession) {
await onSuccess();
}
toast({
title: t`Session revoked`,
});
} catch (error) {
console.error(error);
toast({
title: t`Error`,
description: t`Failed to revoke session`,
variant: 'destructive',
});
}
setIsLoading(false);
};
return (
<Button variant="destructive" size="sm" onClick={handleRevoke} loading={isLoading}>
<Trans>Revoke</Trans>
</Button>
);
};
@@ -1,6 +1,7 @@
import { Trans } from '@lingui/react/macro';
import { Link, redirect } from 'react-router';
import { extractCookieFromHeaders } from '@documenso/auth/server/lib/utils/cookies';
import { getOptionalSession } from '@documenso/auth/server/lib/utils/get-session';
import {
IS_GOOGLE_SSO_ENABLED,
@@ -20,13 +21,18 @@ export function meta() {
export async function loader({ request }: Route.LoaderArgs) {
const { isAuthenticated } = await getOptionalSession(request);
const redirectToCookie = extractCookieFromHeaders('redirectTo', request.headers);
const redirectToAfterLogin = redirectToCookie ? decodeURIComponent(redirectToCookie) : '';
// SSR env variables.
const isGoogleSSOEnabled = IS_GOOGLE_SSO_ENABLED;
const isOIDCSSOEnabled = IS_OIDC_SSO_ENABLED;
const oidcProviderLabel = OIDC_PROVIDER_LABEL;
if (isAuthenticated) {
if (redirectToAfterLogin) {
throw redirect(redirectToAfterLogin);
}
throw redirect('/');
}
@@ -34,11 +40,13 @@ export async function loader({ request }: Route.LoaderArgs) {
isGoogleSSOEnabled,
isOIDCSSOEnabled,
oidcProviderLabel,
redirectToAfterLogin,
};
}
export default function SignIn({ loaderData }: Route.ComponentProps) {
const { isGoogleSSOEnabled, isOIDCSSOEnabled, oidcProviderLabel } = loaderData;
const { isGoogleSSOEnabled, isOIDCSSOEnabled, oidcProviderLabel, redirectToAfterLogin } =
loaderData;
return (
<div className="w-screen max-w-lg px-4">
@@ -56,6 +64,7 @@ export default function SignIn({ loaderData }: Route.ComponentProps) {
isGoogleSSOEnabled={isGoogleSSOEnabled}
isOIDCSSOEnabled={isOIDCSSOEnabled}
oidcProviderLabel={oidcProviderLabel}
returnTo={redirectToAfterLogin ?? undefined}
/>
{env('NEXT_PUBLIC_DISABLE_SIGNUP') !== 'true' && (
@@ -1,28 +0,0 @@
import { Trans } from '@lingui/react/macro';
import { Link } from 'react-router';
import { Button } from '@documenso/ui/primitives/button';
import type { Route } from './+types/team.verify.transfer.$token';
export default function VerifyTeamTransferPage({ loaderData }: Route.ComponentProps) {
return (
<div className="w-screen max-w-lg px-4">
<div className="w-full">
<h1 className="text-4xl font-semibold">
<Trans>Invalid link</Trans>
</h1>
<p className="text-muted-foreground mb-4 mt-2 text-sm">
<Trans>This link is invalid or has expired.</Trans>
</p>
<Button asChild>
<Link to="/">
<Trans>Return</Trans>
</Link>
</Button>
</div>
</div>
);
}
+1 -1
View File
@@ -100,5 +100,5 @@
"vite-plugin-babel-macros": "^1.0.6",
"vite-tsconfig-paths": "^5.1.4"
},
"version": "1.12.0-rc.3"
"version": "1.12.0-rc.4"
}
+22 -2
View File
@@ -20,12 +20,33 @@ export const appMiddleware = async (c: Context, next: Next) => {
const { req } = c;
const { path } = req;
// PRE-HANDLER CODE: Place code here to execute BEFORE the route handler runs.
const redirectTo = req.query('redirectTo');
if (redirectTo) {
if (redirectTo.startsWith('/') && !redirectTo.startsWith('//') && !redirectTo.includes('..')) {
debug.log('Setting redirectTo cookie to:', redirectTo);
setCookie(c, 'redirectTo', redirectTo, {
path: '/',
httpOnly: true,
sameSite: 'Lax',
maxAge: 150,
secure: process.env.NODE_ENV === 'production',
});
debug.log('Redirecting to (from param):', redirectTo);
return c.redirect(redirectTo, 307);
} else {
debug.log('Invalid redirectTo parameter encountered:', redirectTo);
}
}
// Paths to ignore.
if (nonPagePathRegex.test(path)) {
return next();
}
// PRE-HANDLER CODE: Place code here to execute BEFORE the route handler runs.
// Handle team-based routing redirects (documents/templates to team URLs)
const redirectPath = await handleRedirects(c);
if (redirectPath) {
@@ -34,7 +55,6 @@ export const appMiddleware = async (c: Context, next: Next) => {
return c.redirect(redirectPath);
}
await next();
// POST-HANDLER CODE: Place code here to execute AFTER the route handler completes.
+46 -3
View File
@@ -1,12 +1,12 @@
{
"name": "@documenso/root",
"version": "1.12.0-rc.3",
"version": "1.12.0-rc.4",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@documenso/root",
"version": "1.12.0-rc.3",
"version": "1.12.0-rc.4",
"workspaces": [
"apps/*",
"packages/*"
@@ -69,6 +69,7 @@
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"next-sitemap": "^4.2.3",
"typescript": "5.6.2"
}
},
@@ -88,7 +89,7 @@
},
"apps/remix": {
"name": "@documenso/remix",
"version": "1.12.0-rc.3",
"version": "1.12.0-rc.4",
"dependencies": {
"@documenso/api": "*",
"@documenso/assets": "*",
@@ -2122,6 +2123,13 @@
"node": ">=v14"
}
},
"node_modules/@corex/deepmerge": {
"version": "4.0.43",
"resolved": "https://registry.npmjs.org/@corex/deepmerge/-/deepmerge-4.0.43.tgz",
"integrity": "sha512-N8uEMrMPL0cu/bdboEWpQYb/0i2K5Qn8eCsxzOmxSggJbbQte7ljMRoXm917AbntqTGOzdTu+vP3KOOzoC70HQ==",
"dev": true,
"license": "MIT"
},
"node_modules/@cspotcode/source-map-support": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
@@ -25341,6 +25349,41 @@
"react-dom": ">=16.0.0"
}
},
"node_modules/next-sitemap": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/next-sitemap/-/next-sitemap-4.2.3.tgz",
"integrity": "sha512-vjdCxeDuWDzldhCnyFCQipw5bfpl4HmZA7uoo3GAaYGjGgfL4Cxb1CiztPuWGmS+auYs7/8OekRS8C2cjdAsjQ==",
"dev": true,
"funding": [
{
"url": "https://github.com/iamvishnusankar/next-sitemap.git"
}
],
"license": "MIT",
"dependencies": {
"@corex/deepmerge": "^4.0.43",
"@next/env": "^13.4.3",
"fast-glob": "^3.2.12",
"minimist": "^1.2.8"
},
"bin": {
"next-sitemap": "bin/next-sitemap.mjs",
"next-sitemap-cjs": "bin/next-sitemap.cjs"
},
"engines": {
"node": ">=14.18"
},
"peerDependencies": {
"next": "*"
}
},
"node_modules/next-sitemap/node_modules/@next/env": {
"version": "13.5.11",
"resolved": "https://registry.npmjs.org/@next/env/-/env-13.5.11.tgz",
"integrity": "sha512-fbb2C7HChgM7CemdCY+y3N1n8pcTKdqtQLbC7/EQtPdLvlMUT9JX/dBYl8MMZAtYG4uVMyPFHXckb68q/NRwqg==",
"dev": true,
"license": "MIT"
},
"node_modules/next-themes": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.2.1.tgz",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"private": true,
"version": "1.12.0-rc.3",
"version": "1.12.0-rc.4",
"scripts": {
"build": "turbo run build",
"dev": "turbo run dev --filter=@documenso/remix",
+35 -7
View File
@@ -176,8 +176,16 @@ export const ZCreateDocumentMutationSchema = z.object({
.default({}),
authOptions: z
.object({
globalAccessAuth: z.array(ZDocumentAccessAuthTypesSchema).optional().default([]),
globalActionAuth: z.array(ZDocumentActionAuthTypesSchema).optional().default([]),
globalAccessAuth: z
.union([ZDocumentAccessAuthTypesSchema, z.array(ZDocumentAccessAuthTypesSchema)])
.transform((val) => (Array.isArray(val) ? val : [val]))
.optional()
.default([]),
globalActionAuth: z
.union([ZDocumentActionAuthTypesSchema, z.array(ZDocumentActionAuthTypesSchema)])
.transform((val) => (Array.isArray(val) ? val : [val]))
.optional()
.default([]),
})
.optional()
.openapi({
@@ -236,8 +244,16 @@ export const ZCreateDocumentFromTemplateMutationSchema = z.object({
.optional(),
authOptions: z
.object({
globalAccessAuth: z.array(ZDocumentAccessAuthTypesSchema).optional().default([]),
globalActionAuth: z.array(ZDocumentActionAuthTypesSchema).optional().default([]),
globalAccessAuth: z
.union([ZDocumentAccessAuthTypesSchema, z.array(ZDocumentAccessAuthTypesSchema)])
.transform((val) => (Array.isArray(val) ? val : [val]))
.optional()
.default([]),
globalActionAuth: z
.union([ZDocumentActionAuthTypesSchema, z.array(ZDocumentActionAuthTypesSchema)])
.transform((val) => (Array.isArray(val) ? val : [val]))
.optional()
.default([]),
})
.optional(),
formValues: z.record(z.string(), z.union([z.string(), z.boolean(), z.number()])).optional(),
@@ -309,8 +325,16 @@ export const ZGenerateDocumentFromTemplateMutationSchema = z.object({
.optional(),
authOptions: z
.object({
globalAccessAuth: z.array(ZDocumentAccessAuthTypesSchema).optional().default([]),
globalActionAuth: z.array(ZDocumentActionAuthTypesSchema).optional().default([]),
globalAccessAuth: z
.union([ZDocumentAccessAuthTypesSchema, z.array(ZDocumentAccessAuthTypesSchema)])
.transform((val) => (Array.isArray(val) ? val : [val]))
.optional()
.default([]),
globalActionAuth: z
.union([ZDocumentActionAuthTypesSchema, z.array(ZDocumentActionAuthTypesSchema)])
.transform((val) => (Array.isArray(val) ? val : [val]))
.optional()
.default([]),
})
.optional(),
formValues: z.record(z.string(), z.union([z.string(), z.boolean(), z.number()])).optional(),
@@ -349,7 +373,11 @@ export const ZCreateRecipientMutationSchema = z.object({
signingOrder: z.number().nullish(),
authOptions: z
.object({
actionAuth: z.array(ZRecipientActionAuthTypesSchema).optional().default([]),
actionAuth: z
.union([ZRecipientActionAuthTypesSchema, z.array(ZRecipientActionAuthTypesSchema)])
.transform((val) => (Array.isArray(val) ? val : [val]))
.optional()
.default([]),
})
.optional()
.openapi({
@@ -197,7 +197,7 @@ test.describe('Template Field Prefill API v1', () => {
id: numberField.id,
type: 'number',
label: 'Prefilled Number',
value: '42',
value: '98765',
},
{
id: radioField.id,
@@ -256,7 +256,7 @@ test.describe('Template Field Prefill API v1', () => {
expect(documentNumberField?.fieldMeta).toMatchObject({
type: 'number',
label: 'Prefilled Number',
value: '42',
value: '98765',
});
const documentRadioField = document?.fields.find(
@@ -329,7 +329,7 @@ test.describe('Template Field Prefill API v1', () => {
await expect(page.getByText('This is prefilled')).toBeVisible();
// Number field
await expect(page.getByText('42')).toBeVisible();
await expect(page.getByText('98765', { exact: true })).toBeVisible();
// Radio field
await expect(page.getByText('Option A')).toBeVisible();
@@ -383,7 +383,7 @@ test.describe('Template Field Prefill API v1', () => {
// 5. Add fields to the template
// Add TEXT field
const textField = await prisma.field.create({
await prisma.field.create({
data: {
templateId: template.id,
recipientId: recipient.id,
@@ -403,7 +403,7 @@ test.describe('Template Field Prefill API v1', () => {
});
// Add NUMBER field
const numberField = await prisma.field.create({
await prisma.field.create({
data: {
templateId: template.id,
recipientId: recipient.id,
@@ -194,7 +194,7 @@ test.describe('Template Field Prefill API v2', () => {
id: numberField.id,
type: 'number',
label: 'Prefilled Number',
value: '42',
value: '98765',
},
{
id: radioField.id,
@@ -253,7 +253,7 @@ test.describe('Template Field Prefill API v2', () => {
expect(documentNumberField?.fieldMeta).toMatchObject({
type: 'number',
label: 'Prefilled Number',
value: '42',
value: '98765',
});
const documentRadioField = document?.fields.find(
@@ -326,7 +326,7 @@ test.describe('Template Field Prefill API v2', () => {
await expect(page.getByText('This is prefilled')).toBeVisible();
// Number field
await expect(page.getByText('42')).toBeVisible();
await expect(page.getByText('98765', { exact: true })).toBeVisible();
// Radio field
await expect(page.getByText('Option A')).toBeVisible();
@@ -380,7 +380,7 @@ test.describe('Template Field Prefill API v2', () => {
// 5. Add fields to the template
// Add TEXT field
const textField = await prisma.field.create({
await prisma.field.create({
data: {
templateId: template.id,
recipientId: recipient.id,
@@ -400,7 +400,7 @@ test.describe('Template Field Prefill API v2', () => {
});
// Add NUMBER field
const numberField = await prisma.field.create({
await prisma.field.create({
data: {
templateId: template.id,
recipientId: recipient.id,
@@ -633,7 +633,7 @@ test('[DOCUMENT_FLOW]: should be able to create and sign a document with 3 recip
}
// Wait for the document to be signed.
await page.waitForTimeout(5000);
await page.waitForTimeout(10000);
const finalDocument = await prisma.document.findFirst({
where: { id: createdDocument?.id },
@@ -283,10 +283,10 @@ test('[DOCUMENTS]: deleting documents as a recipient should only hide it for the
}).toPass();
// Delete document.
await page.getByRole('menuitem', { name: 'Hide' }).click();
await page.getByRole('button', { name: 'Hide' }).click();
await page.waitForTimeout(1000);
await page.getByRole('menuitem', { name: 'Hide' }).waitFor({ state: 'visible' });
await page.getByRole('menuitem', { name: 'Hide' }).click({ force: true });
await page.getByRole('button', { name: 'Hide' }).click({ force: true });
await page.waitForTimeout(2000);
await expect(async () => {
await page
@@ -300,8 +300,10 @@ test('[DOCUMENTS]: deleting documents as a recipient should only hide it for the
}).toPass();
// Delete document.
await page.getByRole('menuitem', { name: 'Hide' }).click();
await page.getByRole('button', { name: 'Hide' }).click();
await page.getByRole('menuitem', { name: 'Hide' }).waitFor({ state: 'visible' });
await page.getByRole('menuitem', { name: 'Hide' }).click({ force: true });
await page.getByRole('button', { name: 'Hide' }).click({ force: true });
await page.waitForTimeout(2000);
// Check document counts.
await expect(page.getByRole('row', { name: /Document 1 - Completed/ })).not.toBeVisible();
@@ -49,9 +49,11 @@ test.describe('Signing Certificate Tests', () => {
}
await page.getByRole('button', { name: 'Complete' }).click();
await page.getByRole('button', { name: 'Sign' }).click();
await page.getByRole('button', { name: 'Sign' }).click({ force: true });
await page.waitForURL(`/sign/${recipient.token}/complete`);
await page.waitForTimeout(10000);
await expect(async () => {
const { status } = await getDocumentByToken({
token: recipient.token,
@@ -3,8 +3,7 @@ import path from 'node:path';
import { prisma } from '@documenso/prisma';
import { DocumentVisibility, FolderType, TeamMemberRole } from '@documenso/prisma/client';
import { seedTeamDocuments } from '@documenso/prisma/seed/documents';
import { seedBlankDocument } from '@documenso/prisma/seed/documents';
import { seedBlankDocument, seedTeamDocuments } from '@documenso/prisma/seed/documents';
import { seedBlankFolder } from '@documenso/prisma/seed/folders';
import { seedTeamMember } from '@documenso/prisma/seed/teams';
import { seedBlankTemplate } from '@documenso/prisma/seed/templates';
@@ -1328,7 +1327,7 @@ test('[TEAMS]: team admin can move manager document to admin folder', async ({ p
const managerDocRow = page.getByRole('row', { name: /\[TEST\] Manager Document/ });
await managerDocRow.getByTestId('document-table-action-btn').click();
await page.getByRole('menuitem', { name: 'Move to Folder' }).click();
await page.getByRole('menuitem', { name: 'Move to Folder' }).click({ force: true });
await expect(page.getByRole('button', { name: 'Admin Folder' })).toBeVisible();
await page.getByRole('button', { name: 'Admin Folder' }).click();
@@ -1379,7 +1378,7 @@ test('[TEAMS]: team admin can move manager document to manager folder', async ({
const managerDocRow = page.getByRole('row', { name: /\[TEST\] Manager Document/ });
await managerDocRow.getByTestId('document-table-action-btn').click();
await page.getByRole('menuitem', { name: 'Move to Folder' }).click();
await page.getByRole('menuitem', { name: 'Move to Folder' }).click({ force: true });
await expect(page.getByRole('button', { name: 'Manager Folder' })).toBeVisible();
await page.getByRole('button', { name: 'Manager Folder' }).click();
@@ -1430,7 +1429,7 @@ test('[TEAMS]: team admin can move manager document to everyone folder', async (
const managerDocRow = page.getByRole('row', { name: /\[TEST\] Manager Document/ });
await managerDocRow.getByTestId('document-table-action-btn').click();
await page.getByRole('menuitem', { name: 'Move to Folder' }).click();
await page.getByRole('menuitem', { name: 'Move to Folder' }).click({ force: true });
await expect(page.getByRole('button', { name: 'Everyone Folder' })).toBeVisible();
await page.getByRole('button', { name: 'Everyone Folder' }).click();
@@ -31,7 +31,7 @@ test('[ORGANISATIONS]: create and delete organisation', async ({ page }) => {
await page.getByRole('button', { name: 'Delete' }).click();
await page.waitForURL(`/settings/organisations`);
await expect(page.getByText('No results found')).toBeVisible();
await expectTextToBeVisible(page, 'No results found');
await page.getByRole('button', { name: 'Create organisation' }).click();
await page.getByLabel('Organisation Name*').fill('test');
@@ -1,8 +1,11 @@
import { expect, test } from '@playwright/test';
import { DocumentStatus, DocumentVisibility, TeamMemberRole } from '@prisma/client';
import { seedBlankDocument } from '@documenso/prisma/seed/documents';
import { seedDocuments, seedTeamDocuments } from '@documenso/prisma/seed/documents';
import {
seedBlankDocument,
seedDocuments,
seedTeamDocuments,
} from '@documenso/prisma/seed/documents';
import { seedTeam, seedTeamEmail, seedTeamMember } from '@documenso/prisma/seed/teams';
import { seedUser } from '@documenso/prisma/seed/users';
@@ -314,9 +317,9 @@ test('[TEAMS]: delete pending team document', async ({ page }) => {
await expect(page.getByRole('menuitem', { name: 'Delete' })).toBeVisible();
}).toPass();
await page.getByRole('menuitem', { name: 'Delete' }).click();
await page.getByRole('menuitem', { name: 'Delete' }).click({ force: true });
await page.getByPlaceholder("Type 'delete' to confirm").fill('delete');
await page.getByRole('button', { name: 'Delete' }).click();
await page.getByRole('button', { name: 'Delete' }).click({ force: true });
await checkDocumentTabCount(page, 'Pending', 1);
@@ -359,9 +362,9 @@ test('[TEAMS]: delete completed team document', async ({ page }) => {
await expect(page.getByRole('menuitem', { name: 'Delete' })).toBeVisible();
}).toPass();
await page.getByRole('menuitem', { name: 'Delete' }).click();
await page.getByRole('menuitem', { name: 'Delete' }).click({ force: true });
await page.getByPlaceholder("Type 'delete' to confirm").fill('delete');
await page.getByRole('button', { name: 'Delete' }).click();
await page.getByRole('button', { name: 'Delete' }).click({ force: true });
await checkDocumentTabCount(page, 'Completed', 0);
@@ -80,18 +80,27 @@ test('[TEAMS]: check signature modes can be disabled', async ({ page }) => {
await page.getByRole('button', { name: 'Update' }).first().click();
// Wait for the update to complete
const toast = page.locator('li[role="status"][data-state="open"]').first();
await expect(toast).toBeVisible();
await expect(toast.getByText('Document preferences updated', { exact: true })).toBeVisible();
const document = await seedTeamDocumentWithMeta(team);
// Go to document and check that the signatured tabs are correct.
// Go to document and check that the signature tabs are correct.
await page.goto(`/sign/${document.recipients[0].token}`);
await page.getByTestId('signature-pad-dialog-button').click();
// Wait for signature dialog to fully load
await page.waitForSelector('[role="dialog"]');
// Check the tab values
for (const tab of allTabs) {
if (tabs.includes(tab)) {
await expect(page.getByRole('tab', { name: tab })).toBeVisible();
} else {
await expect(page.getByRole('tab', { name: tab })).not.toBeVisible();
// await expect(page.getByRole('tab', { name: tab })).not.toBeVisible();
await expect(page.getByRole('tab', { name: tab })).toHaveCount(0);
}
}
}
@@ -297,10 +297,22 @@ test('[TEMPLATE]: should create a document from a template with custom document'
},
});
const expectedDocumentDataType =
process.env.NEXT_PUBLIC_UPLOAD_TRANSPORT === 's3'
? DocumentDataType.S3_PATH
: DocumentDataType.BYTES_64;
expect(document.title).toEqual('TEMPLATE_WITH_CUSTOM_DOC');
expect(document.documentData.type).toEqual(DocumentDataType.BYTES_64);
expect(document.documentData.data).toEqual(pdfContent);
expect(document.documentData.initialData).toEqual(pdfContent);
expect(document.documentData.type).toEqual(expectedDocumentDataType);
if (expectedDocumentDataType === DocumentDataType.BYTES_64) {
expect(document.documentData.data).toEqual(pdfContent);
expect(document.documentData.initialData).toEqual(pdfContent);
} else {
// For S3, we expect the data/initialData to be the S3 path (non-empty string)
expect(document.documentData.data).toBeTruthy();
expect(document.documentData.initialData).toBeTruthy();
}
});
/**
@@ -378,11 +390,23 @@ test('[TEMPLATE]: should create a team document from a template with custom docu
},
});
const expectedDocumentDataType =
process.env.NEXT_PUBLIC_UPLOAD_TRANSPORT === 's3'
? DocumentDataType.S3_PATH
: DocumentDataType.BYTES_64;
expect(document.teamId).toEqual(team.id);
expect(document.title).toEqual('TEAM_TEMPLATE_WITH_CUSTOM_DOC');
expect(document.documentData.type).toEqual(DocumentDataType.BYTES_64);
expect(document.documentData.data).toEqual(pdfContent);
expect(document.documentData.initialData).toEqual(pdfContent);
expect(document.documentData.type).toEqual(expectedDocumentDataType);
if (expectedDocumentDataType === DocumentDataType.BYTES_64) {
expect(document.documentData.data).toEqual(pdfContent);
expect(document.documentData.initialData).toEqual(pdfContent);
} else {
// For S3, we expect the data/initialData to be the S3 path (non-empty string)
expect(document.documentData.data).toBeTruthy();
expect(document.documentData.initialData).toBeTruthy();
}
});
/**
@@ -178,4 +178,7 @@ test('[DIRECT_TEMPLATES]: use direct template link with 1 recipient', async ({ p
await page.getByRole('button', { name: 'Sign' }).click();
await page.waitForURL(/\/sign/);
await expect(page.getByRole('heading', { name: 'Document Signed' })).toBeVisible();
// Add a longer waiting period to ensure document status is updated
await page.waitForTimeout(3000);
});
@@ -27,6 +27,9 @@ test('[USER] can sign up with email and password', async ({ page }: { page: Page
await page.waitForURL('/unverified-account');
// Wait to ensure token is created in the database
await page.waitForTimeout(2000);
const { token } = await extractUserVerificationToken(email);
const team = await prisma.team.findFirstOrThrow({
@@ -0,0 +1,82 @@
import { type Page, expect, test } from '@playwright/test';
import { seedUser } from '@documenso/prisma/seed/users';
import { apiSignin } from '../fixtures/authentication';
import { expectTextToBeVisible } from '../fixtures/generic';
test('[USER] revoke sessions', async ({ page }: { page: Page }) => {
const { user, team } = await seedUser();
await apiSignin({
page,
email: user.email,
password: 'password',
redirectPath: '/settings/security/sessions',
});
// Expect 2 rows length (header + 1)
await expect(page.getByRole('row')).toHaveCount(2);
// Clear cookies
await page.context().clearCookies();
await apiSignin({
page,
email: user.email,
password: 'password',
redirectPath: '/settings/security/sessions',
});
await page.context().clearCookies();
await apiSignin({
page,
email: user.email,
password: 'password',
redirectPath: '/settings/security/sessions',
});
// Expect 4 (3 sessions + 1 header) rows length
await expect(page.getByRole('row')).toHaveCount(4);
// Revoke all sessions
await page.getByRole('button', { name: 'Revoke all sessions' }).click();
await page.getByRole('button', { name: 'Revoke all sessions' }).click();
await expectTextToBeVisible(page, 'Sessions have been revoked');
// Expect (1 sessions + 1 header) rows length
await expect(page.getByRole('row')).toHaveCount(2);
await page.context().clearCookies();
await apiSignin({
page,
email: user.email,
password: 'password',
redirectPath: '/settings/security/sessions',
});
// Find table row which does not have text 'Current' and click the button called Revoke within the row.
await page
.getByRole('row')
.filter({ hasNotText: 'Current' })
.nth(1)
.getByRole('button', { name: 'Revoke' })
.click();
await expectTextToBeVisible(page, 'Session revoked');
// Expect (1 sessions + 1 header) rows length
await expect(page.getByRole('row')).toHaveCount(2);
// Revoke own session.
await page
.getByRole('row')
.filter({ hasText: 'Current' })
.first()
.getByRole('button', { name: 'Revoke' })
.click();
await expect(page).toHaveURL('/signin');
});
+23 -8
View File
@@ -17,9 +17,14 @@ test('[USER] can reset password via forgot password', async ({ page }: { page: P
await page.goto('http://localhost:3000/signin');
await page.getByRole('link', { name: 'Forgot your password?' }).click();
await page.getByRole('textbox', { name: 'Email' }).click();
await page.getByRole('textbox', { name: 'Email' }).fill(user.email);
await expect(page.getByRole('button', { name: 'Reset Password' })).toBeEnabled();
await page.getByRole('button', { name: 'Reset Password' }).click();
await expect(page.locator('body')).toContainText('Reset email sent');
await expect(page.locator('body')).toContainText('Reset email sent', { timeout: 10000 });
const foundToken = await prisma.passwordResetToken.findFirstOrThrow({
where: {
@@ -33,16 +38,26 @@ test('[USER] can reset password via forgot password', async ({ page }: { page: P
await page.goto(`http://localhost:3000/reset-password/${foundToken.token}`);
// Assert that password cannot be same as old password.
await page.getByRole('textbox', { name: 'Password', exact: true }).fill(oldPassword);
await page.getByRole('textbox', { name: 'Repeat Password' }).fill(oldPassword);
await page.getByLabel('Password', { exact: true }).fill(oldPassword);
await page.getByLabel('Repeat Password').fill(oldPassword);
// Ensure both fields are filled before clicking
await expect(page.getByLabel('Password', { exact: true })).toHaveValue(oldPassword);
await expect(page.getByLabel('Repeat Password')).toHaveValue(oldPassword);
await page.getByRole('button', { name: 'Reset Password' }).click();
await expect(page.locator('body')).toContainText(
'Your new password cannot be the same as your old password.',
);
// Assert password reset.
await page.getByRole('textbox', { name: 'Password', exact: true }).fill(newPassword);
await page.getByRole('textbox', { name: 'Repeat Password' }).fill(newPassword);
await page.getByLabel('Password', { exact: true }).fill(newPassword);
await page.getByLabel('Repeat Password').fill(newPassword);
// Ensure both fields are filled before clicking
await expect(page.getByLabel('Password', { exact: true })).toHaveValue(newPassword);
await expect(page.getByLabel('Repeat Password')).toHaveValue(newPassword);
await page.getByRole('button', { name: 'Reset Password' }).click();
await expect(page.locator('body')).toContainText('Your password has been updated successfully.');
@@ -73,9 +88,9 @@ test('[USER] can reset password via user settings', async ({ page }: { page: Pag
redirectPath: '/settings/security',
});
await page.getByRole('textbox', { name: 'Current password' }).fill(oldPassword);
await page.getByRole('textbox', { name: 'New password' }).fill(newPassword);
await page.getByRole('textbox', { name: 'Repeat password' }).fill(newPassword);
await page.getByLabel('Current password').fill(oldPassword);
await page.getByLabel('New password').fill(newPassword);
await page.getByLabel('Repeat password').fill(newPassword);
await page.getByRole('button', { name: 'Update password' }).click();
await expect(page.locator('body')).toContainText('Password updated');
+7 -3
View File
@@ -24,19 +24,23 @@ export default defineConfig({
/* Retry on CI only */
retries: process.env.CI ? 4 : 1,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html',
reporter: [['html'], ['list']],
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: 'http://localhost:3000',
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
trace: 'on',
video: 'retain-on-failure',
/* Add explicit timeouts for actions */
actionTimeout: 15_000,
navigationTimeout: 30_000,
},
timeout: 30_000,
timeout: 60_000,
/* Configure projects for major browsers */
projects: [
+31
View File
@@ -7,6 +7,7 @@ import { AppError } from '@documenso/lib/errors/app-error';
import type { AuthAppType } from '../server';
import type { SessionValidationResult } from '../server/lib/session/session';
import type { ActiveSession } from '../server/lib/utils/get-session';
import { handleSignInRedirect } from '../server/lib/utils/redirect';
import type {
TDisableTwoFactorRequestSchema,
@@ -47,6 +48,26 @@ export class AuthClient {
window.location.href = redirectPath ?? this.signOutredirectPath;
}
public async signOutAllSessions() {
await this.client['signout-all'].$post();
}
public async signOutSession({
sessionId,
redirectPath,
}: {
sessionId: string;
redirectPath?: string;
}) {
await this.client['signout-session'].$post({
json: { sessionId },
});
if (redirectPath) {
window.location.href = redirectPath;
}
}
public async getSession() {
const response = await this.client['session-json'].$get();
@@ -57,6 +78,16 @@ export class AuthClient {
return superjson.deserialize<SessionValidationResult>(result);
}
public async getSessions() {
const response = await this.client['sessions'].$get();
await this.handleError(response);
const result = await response.json();
return superjson.deserialize<{ sessions: ActiveSession[] }>(result);
}
private async handleError<T>(response: ClientResponse<T>): Promise<void> {
if (!response.ok) {
const error = await response.json();
-1
View File
@@ -1,2 +1 @@
export * from './server/lib/errors/errors';
export * from './server/lib/errors/error-codes';
+41 -12
View File
@@ -2,6 +2,7 @@ import { sha256 } from '@oslojs/crypto/sha2';
import { encodeBase32LowerCaseNoPadding, encodeHexLowerCase } from '@oslojs/encoding';
import { type Session, type User, UserSecurityAuditLogType } from '@prisma/client';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import type { RequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
import { prisma } from '@documenso/prisma';
@@ -129,18 +130,46 @@ export const validateSessionToken = async (token: string): Promise<SessionValida
return { session, user, isAuthenticated: true };
};
export const invalidateSession = async (
sessionId: string,
metadata: RequestMetadata,
): Promise<void> => {
const session = await prisma.session.delete({ where: { id: sessionId } });
type InvalidateSessionsOptions = {
userId: number;
sessionIds: string[];
metadata: RequestMetadata;
isRevoke?: boolean;
};
await prisma.userSecurityAuditLog.create({
data: {
userId: session.userId,
ipAddress: metadata.ipAddress,
userAgent: metadata.userAgent,
type: UserSecurityAuditLogType.SIGN_OUT,
},
export const invalidateSessions = async ({
userId,
sessionIds,
metadata,
isRevoke,
}: InvalidateSessionsOptions): Promise<void> => {
if (sessionIds.length === 0) {
return;
}
await prisma.$transaction(async (tx) => {
const { count } = await tx.session.deleteMany({
where: {
userId,
id: { in: sessionIds },
},
});
if (count !== sessionIds.length) {
throw new AppError(AppErrorCode.INVALID_REQUEST, {
message: 'One or more sessions are not valid.',
});
}
await tx.userSecurityAuditLog.createMany({
data: sessionIds.map(() => ({
userId,
ipAddress: metadata.ipAddress,
userAgent: metadata.userAgent,
type: isRevoke
? UserSecurityAuditLogType.SESSION_REVOKED
: UserSecurityAuditLogType.SIGN_OUT,
})),
});
});
};
@@ -1,6 +1,8 @@
import type { Session } from '@prisma/client';
import type { Context } from 'hono';
import { AppError } from '@documenso/lib/errors/app-error';
import { prisma } from '@documenso/prisma';
import { AuthenticationErrorCode } from '../errors/error-codes';
import type { SessionValidationResult } from '../session/session';
@@ -37,6 +39,33 @@ export const getOptionalSession = async (
return await validateSessionToken(sessionId);
};
export type ActiveSession = Omit<Session, 'sessionToken'>;
export const getActiveSessions = async (c: Context | Request): Promise<ActiveSession[]> => {
const { user } = await getSession(c);
return await prisma.session.findMany({
where: {
userId: user.id,
expiresAt: {
gt: new Date(),
},
},
orderBy: {
updatedAt: 'desc',
},
select: {
id: true,
userId: true,
expiresAt: true,
updatedAt: true,
createdAt: true,
ipAddress: true,
userAgent: true,
},
});
};
/**
* Todo: (RR7) Rethink, this is pretty sketchy.
*/
+6 -1
View File
@@ -2,7 +2,7 @@ import { Hono } from 'hono';
import superjson from 'superjson';
import type { SessionValidationResult } from '../lib/session/session';
import { getOptionalSession } from '../lib/utils/get-session';
import { getActiveSessions, getOptionalSession } from '../lib/utils/get-session';
export const sessionRoute = new Hono()
.get('/session', async (c) => {
@@ -10,6 +10,11 @@ export const sessionRoute = new Hono()
return c.json(session);
})
.get('/sessions', async (c) => {
const sessions = await getActiveSessions(c);
return c.json(superjson.serialize({ sessions }));
})
.get('/session-json', async (c) => {
const session: SessionValidationResult = await getOptionalSession(c);
+108 -21
View File
@@ -1,27 +1,114 @@
import { sValidator } from '@hono/standard-validator';
import { Hono } from 'hono';
import { z } from 'zod';
import { invalidateSession, validateSessionToken } from '../lib/session/session';
import { prisma } from '@documenso/prisma';
import { invalidateSessions, validateSessionToken } from '../lib/session/session';
import { deleteSessionCookie, getSessionCookie } from '../lib/session/session-cookies';
import type { HonoAuthContext } from '../types/context';
export const signOutRoute = new Hono<HonoAuthContext>().post('/signout', async (c) => {
const metadata = c.get('requestMetadata');
const sessionId = await getSessionCookie(c);
if (!sessionId) {
return new Response('No session found', { status: 401 });
}
const { session } = await validateSessionToken(sessionId);
if (!session) {
return new Response('No session found', { status: 401 });
}
await invalidateSession(session.id, metadata);
deleteSessionCookie(c);
return c.status(200);
const ZSignoutSessionSchema = z.object({
sessionId: z.string().trim().min(1),
});
export const signOutRoute = new Hono<HonoAuthContext>()
.post('/signout', async (c) => {
const metadata = c.get('requestMetadata');
const sessionToken = await getSessionCookie(c);
if (!sessionToken) {
return new Response('No session found', { status: 401 });
}
const { session } = await validateSessionToken(sessionToken);
if (!session) {
deleteSessionCookie(c);
return new Response('No session found', { status: 401 });
}
await invalidateSessions({
userId: session.userId,
sessionIds: [session.id],
metadata,
isRevoke: false,
});
deleteSessionCookie(c);
return c.status(200);
})
.post('/signout-all', async (c) => {
const metadata = c.get('requestMetadata');
const sessionToken = await getSessionCookie(c);
if (!sessionToken) {
return new Response('No session found', { status: 401 });
}
const { session } = await validateSessionToken(sessionToken);
if (!session) {
deleteSessionCookie(c);
return new Response('No session found', { status: 401 });
}
const userId = session.userId;
const userSessionIds = await prisma.session
.findMany({
where: {
userId,
id: {
not: session.id,
},
},
select: {
id: true,
},
})
.then((sessions) => sessions.map((session) => session.id));
await invalidateSessions({
userId,
sessionIds: userSessionIds,
metadata,
isRevoke: true,
});
return c.status(200);
})
.post('/signout-session', sValidator('json', ZSignoutSessionSchema), async (c) => {
const metadata = c.get('requestMetadata');
const { sessionId: sessionIdToRevoke } = c.req.valid('json');
const sessionToken = await getSessionCookie(c);
if (!sessionToken) {
return new Response('No session found', { status: 401 });
}
const { session } = await validateSessionToken(sessionToken);
if (!session) {
deleteSessionCookie(c);
return new Response('No session found', { status: 401 });
}
await invalidateSessions({
userId: session.userId,
sessionIds: [sessionIdToRevoke],
metadata,
isRevoke: true,
});
if (session.id === sessionIdToRevoke) {
deleteSessionCookie(c);
}
return c.status(200);
});
+31 -37
View File
@@ -66,7 +66,7 @@ export const getServerLimits = async ({
};
}
// If plan expired.
// Early return for users with an expired subscription.
if (subscription && subscription.status !== SubscriptionStatus.ACTIVE) {
return {
quota: INACTIVE_PLAN_LIMITS,
@@ -74,52 +74,46 @@ export const getServerLimits = async ({
};
}
if (subscription && organisation.organisationClaim.flags.unlimitedDocuments) {
// Allow unlimited documents for users with an unlimited documents claim.
// This also allows "free" claim users without subscriptions if they have this flag.
if (organisation.organisationClaim.flags.unlimitedDocuments) {
return {
quota: PAID_PLAN_LIMITS,
remaining: PAID_PLAN_LIMITS,
};
}
// If free tier or plan does not have unlimited documents.
if (!subscription || !organisation.organisationClaim.flags.unlimitedDocuments) {
const [documents, directTemplates] = await Promise.all([
prisma.document.count({
where: {
team: {
organisationId: organisation.id,
},
createdAt: {
gte: DateTime.utc().startOf('month').toJSDate(),
},
source: {
not: DocumentSource.TEMPLATE_DIRECT_LINK,
},
const [documents, directTemplates] = await Promise.all([
prisma.document.count({
where: {
team: {
organisationId: organisation.id,
},
}),
prisma.template.count({
where: {
team: {
organisationId: organisation.id,
},
directLink: {
isNot: null,
},
createdAt: {
gte: DateTime.utc().startOf('month').toJSDate(),
},
}),
]);
source: {
not: DocumentSource.TEMPLATE_DIRECT_LINK,
},
},
}),
prisma.template.count({
where: {
team: {
organisationId: organisation.id,
},
directLink: {
isNot: null,
},
},
}),
]);
remaining.documents = Math.max(remaining.documents - documents, 0);
remaining.directTemplates = Math.max(remaining.directTemplates - directTemplates, 0);
return {
quota,
remaining,
};
}
remaining.documents = Math.max(remaining.documents - documents, 0);
remaining.directTemplates = Math.max(remaining.directTemplates - directTemplates, 0);
return {
quota: PAID_PLAN_LIMITS,
remaining: PAID_PLAN_LIMITS,
quota,
remaining,
};
};
+1
View File
@@ -31,6 +31,7 @@ export const USER_SECURITY_AUDIT_LOG_MAP: Record<string, string> = {
PASSKEY_UPDATED: 'Passkey updated',
PASSWORD_RESET: 'Password reset',
PASSWORD_UPDATE: 'Password updated',
SESSION_REVOKED: 'Session revoked',
SIGN_OUT: 'Signed Out',
SIGN_IN: 'Signed In',
SIGN_IN_FAIL: 'Sign in attempt failed',
@@ -1,5 +1,5 @@
import type { Prisma } from '@prisma/client';
import { TeamMemberRole } from '@prisma/client';
import { DocumentStatus, TeamMemberRole } from '@prisma/client';
import { match } from 'ts-pattern';
import { prisma } from '@documenso/prisma';
@@ -83,9 +83,46 @@ export const getDocumentWhereInput = async ({
}: GetDocumentWhereInputOptions) => {
const team = await getTeamById({ teamId, userId });
const user = await prisma.user.findFirstOrThrow({
where: {
id: userId,
},
});
const teamVisibilityFilters = match(team.currentTeamRole)
.with(TeamMemberRole.ADMIN, () => [
DocumentVisibility.EVERYONE,
DocumentVisibility.MANAGER_AND_ABOVE,
DocumentVisibility.ADMIN,
])
.with(TeamMemberRole.MANAGER, () => [
DocumentVisibility.EVERYONE,
DocumentVisibility.MANAGER_AND_ABOVE,
])
.otherwise(() => [DocumentVisibility.EVERYONE]);
const documentOrInput: Prisma.DocumentWhereInput[] = [
// Allow access if they own the document.
{
teamId: team.id,
userId,
},
// Or, if they belong to the team that the document is associated with.
{
visibility: {
in: teamVisibilityFilters,
},
teamId,
},
// Or, if they are a recipient of the document.
{
status: {
not: DocumentStatus.DRAFT,
},
recipients: {
some: {
email: user.email,
},
},
},
];
@@ -112,45 +149,8 @@ export const getDocumentWhereInput = async ({
OR: documentOrInput,
};
const user = await prisma.user.findFirstOrThrow({
where: {
id: userId,
},
});
const visibilityFilters = [
...match(team.currentTeamRole)
.with(TeamMemberRole.ADMIN, () => [
{ visibility: DocumentVisibility.EVERYONE },
{ visibility: DocumentVisibility.MANAGER_AND_ABOVE },
{ visibility: DocumentVisibility.ADMIN },
])
.with(TeamMemberRole.MANAGER, () => [
{ visibility: DocumentVisibility.EVERYONE },
{ visibility: DocumentVisibility.MANAGER_AND_ABOVE },
])
.otherwise(() => [{ visibility: DocumentVisibility.EVERYONE }]),
{
OR: [
{
recipients: {
some: {
email: user.email,
},
},
},
{
userId: user.id,
},
],
},
];
return {
documentWhereInput: {
...documentWhereInput,
OR: [...visibilityFilters],
},
documentWhereInput,
team,
};
};
@@ -1,15 +0,0 @@
import { prisma } from '@documenso/prisma';
export type GetApiTokenByIdOptions = {
id: number;
userId: number;
};
export const getApiTokenById = async ({ id, userId }: GetApiTokenByIdOptions) => {
return await prisma.apiToken.findFirstOrThrow({
where: {
id,
userId,
},
});
};
@@ -11,7 +11,6 @@ export type GetApiTokensOptions = {
export const getApiTokens = async ({ userId, teamId }: GetApiTokensOptions) => {
return await prisma.apiToken.findMany({
where: {
userId,
team: buildTeamWhereQuery({
teamId,
userId,
+75 -15
View File
@@ -752,6 +752,11 @@ msgstr "Aktionen"
msgid "Active"
msgstr "Aktiv"
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
#: apps/remix/app/routes/_authenticated+/settings+/security._index.tsx
msgid "Active sessions"
msgstr ""
#: apps/remix/app/routes/_authenticated+/admin+/stats.tsx
msgid "Active Subscriptions"
msgstr "Aktive Abonnements"
@@ -817,13 +822,13 @@ msgstr "Felder hinzufügen"
msgid "Add group roles"
msgstr "Gruppenrollen hinzufügen"
#: apps/remix/app/components/dialogs/team-group-create-dialog.tsx
#: apps/remix/app/components/dialogs/team-group-create-dialog.tsx
msgid "Add groups"
msgstr "Gruppen hinzufügen"
#: apps/remix/app/components/dialogs/team-member-create-dialog.tsx
#: apps/remix/app/components/dialogs/team-member-create-dialog.tsx
#: apps/remix/app/components/dialogs/team-group-create-dialog.tsx
msgid "Add members"
msgstr "Mitglieder hinzufügen"
@@ -1614,6 +1619,7 @@ msgstr "Kann vorbereiten"
#: apps/remix/app/components/dialogs/team-delete-dialog.tsx
#: apps/remix/app/components/dialogs/team-create-dialog.tsx
#: apps/remix/app/components/dialogs/team-create-dialog.tsx
#: apps/remix/app/components/dialogs/session-logout-all-dialog.tsx
#: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx
#: apps/remix/app/components/dialogs/passkey-create-dialog.tsx
#: apps/remix/app/components/dialogs/organisation-member-update-dialog.tsx
@@ -2221,6 +2227,7 @@ msgstr "Erstellen Sie Ihr Konto und beginnen Sie mit dem modernen Dokumentensign
msgid "Create your account and start using state-of-the-art document signing. Open and beautiful signing is within your grasp."
msgstr "Erstellen Sie Ihr Konto und beginnen Sie mit dem modernen Dokumentensignieren. Offenes und schönes Signieren liegt in Ihrer Reichweite."
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
#: apps/remix/app/routes/_authenticated+/admin+/documents._index.tsx
#: apps/remix/app/components/tables/templates-table.tsx
#: apps/remix/app/components/tables/settings-security-passkey-table.tsx
@@ -2260,6 +2267,10 @@ msgstr "Erstellt am {0}"
msgid "CSV Structure"
msgstr "CSV-Struktur"
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
msgid "Current"
msgstr ""
#: apps/remix/app/components/forms/password.tsx
msgid "Current Password"
msgstr "Aktuelles Passwort"
@@ -2453,6 +2464,7 @@ msgid "Details"
msgstr "Einzelheiten"
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
#: apps/remix/app/components/tables/settings-security-activity-table.tsx
msgid "Device"
msgstr "Gerät"
@@ -3179,6 +3191,7 @@ msgstr "Unternehmen"
#: apps/remix/app/routes/embed+/v1+/authoring+/template.edit.$id.tsx
#: apps/remix/app/routes/embed+/v1+/authoring+/document.edit.$id.tsx
#: apps/remix/app/routes/embed+/v1+/authoring+/document.edit.$id.tsx
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
#: apps/remix/app/routes/_authenticated+/admin+/users.$id.tsx
#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx
#: apps/remix/app/routes/_authenticated+/admin+/documents.$id.tsx
@@ -3221,6 +3234,7 @@ msgstr "Unternehmen"
#: apps/remix/app/components/dialogs/template-move-to-folder-dialog.tsx
#: apps/remix/app/components/dialogs/template-move-to-folder-dialog.tsx
#: apps/remix/app/components/dialogs/template-duplicate-dialog.tsx
#: apps/remix/app/components/dialogs/session-logout-all-dialog.tsx
#: apps/remix/app/components/dialogs/document-move-to-folder-dialog.tsx
#: apps/remix/app/components/dialogs/document-move-to-folder-dialog.tsx
#: apps/remix/app/components/dialogs/admin-user-enable-dialog.tsx
@@ -3286,10 +3300,18 @@ msgstr "Fehler beim Laden des Dokuments"
msgid "Failed to reseal document"
msgstr "Dokument konnte nicht erneut versiegelt werden"
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
msgid "Failed to revoke session"
msgstr ""
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx
msgid "Failed to save settings."
msgstr "Einstellungen konnten nicht gespeichert werden."
#: apps/remix/app/components/dialogs/session-logout-all-dialog.tsx
msgid "Failed to sign out all sessions"
msgstr ""
#: apps/remix/app/routes/embed+/v1+/authoring+/document.edit.$id.tsx
msgid "Failed to update document"
msgstr "Dokument konnte nicht aktualisiert werden"
@@ -3747,7 +3769,6 @@ msgstr "Ungültiger Code. Bitte versuchen Sie es erneut."
msgid "Invalid email"
msgstr "Ungültige E-Mail"
#: apps/remix/app/routes/_unauthenticated+/team.verify.transfer.$token.tsx
#: apps/remix/app/routes/_unauthenticated+/team.verify.email.$token.tsx
msgid "Invalid link"
msgstr "Ungültiger Link"
@@ -3811,6 +3832,7 @@ msgid "Invoice"
msgstr "Rechnung"
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
#: apps/remix/app/components/tables/internal-audit-log-table.tsx
msgid "IP Address"
msgstr "IP-Adresse"
@@ -3881,6 +3903,10 @@ msgstr "Die letzten 30 Tage"
msgid "Last 7 days"
msgstr "Die letzten 7 Tage"
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
msgid "Last Active"
msgstr ""
#: apps/remix/app/components/general/template/template-page-view-information.tsx
#: apps/remix/app/components/general/document/document-page-view-information.tsx
msgid "Last modified"
@@ -4030,6 +4056,10 @@ msgstr "Passkeys verwalten"
msgid "Manage permissions and access controls"
msgstr "Verwalten Sie Berechtigungen und Zugangskontrollen"
#: apps/remix/app/routes/_authenticated+/settings+/security._index.tsx
msgid "Manage sessions"
msgstr ""
#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx
#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx
msgid "Manage subscription"
@@ -5305,7 +5335,6 @@ msgstr "Aufbewahrung von Dokumenten"
msgid "Retry"
msgstr "Wiederholen"
#: apps/remix/app/routes/_unauthenticated+/team.verify.transfer.$token.tsx
#: apps/remix/app/routes/_unauthenticated+/team.verify.email.$token.tsx
#: apps/remix/app/routes/_unauthenticated+/organisation.invite.$token.tsx
#: apps/remix/app/routes/_unauthenticated+/organisation.decline.$token.tsx
@@ -5321,6 +5350,7 @@ msgstr "Zurück zur Startseite"
msgid "Return to sign in"
msgstr "Zurück zur Anmeldung"
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
#: apps/remix/app/components/general/teams/team-email-usage.tsx
msgid "Revoke"
msgstr "Zugriff widerrufen"
@@ -5329,6 +5359,12 @@ msgstr "Zugriff widerrufen"
msgid "Revoke access"
msgstr "Zugriff widerrufen"
#: apps/remix/app/components/dialogs/session-logout-all-dialog.tsx
#: apps/remix/app/components/dialogs/session-logout-all-dialog.tsx
#: apps/remix/app/components/dialogs/session-logout-all-dialog.tsx
msgid "Revoke all sessions"
msgstr ""
#: apps/remix/app/components/tables/user-organisations-table.tsx
#: apps/remix/app/components/tables/team-members-table.tsx
#: apps/remix/app/components/tables/team-groups-table.tsx
@@ -5481,6 +5517,10 @@ msgstr "Standardoption auswählen"
msgid "Select groups"
msgstr "Gruppen auswählen"
#: apps/remix/app/components/dialogs/team-group-create-dialog.tsx
msgid "Select groups of members to add to the team."
msgstr ""
#: apps/remix/app/components/dialogs/team-group-create-dialog.tsx
msgid "Select groups to add to this team"
msgstr "Wählen Sie Gruppen aus, die diesem Team hinzugefügt werden sollen"
@@ -5492,7 +5532,6 @@ msgid "Select members"
msgstr "Mitglieder auswählen"
#: apps/remix/app/components/dialogs/team-member-create-dialog.tsx
#: apps/remix/app/components/dialogs/team-group-create-dialog.tsx
msgid "Select members or groups of members to add to the team."
msgstr "Wählen Sie Mitglieder oder Gruppen von Mitgliedern, die dem Team hinzugefügt werden sollen."
@@ -5602,6 +5641,14 @@ msgstr "Senden..."
msgid "Sent"
msgstr "Gesendet"
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
msgid "Session revoked"
msgstr ""
#: apps/remix/app/components/dialogs/session-logout-all-dialog.tsx
msgid "Sessions have been revoked"
msgstr ""
#: apps/remix/app/components/general/claim-account.tsx
msgid "Set a password"
msgstr "Ein Passwort festlegen"
@@ -6476,7 +6523,8 @@ msgid "The following team has been deleted. You will no longer be able to access
msgstr "Das folgende Team wurde gelöscht. Sie können nicht mehr auf dieses Team und seine Dokumente zugreifen."
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.groups.$id.tsx
msgid "The organisation group you are looking for may have been removed, renamed or may have never\n"
msgid ""
"The organisation group you are looking for may have been removed, renamed or may have never\n"
" existed."
msgstr "Die Organisationsgruppe, nach der Sie suchen, wurde möglicherweise entfernt, umbenannt oder hat möglicherweise nie existiert."
@@ -6485,12 +6533,14 @@ msgid "The organisation role that will be applied to all members in this group."
msgstr "Die Organisationsrolle, die auf alle Mitglieder in dieser Gruppe angewendet wird."
#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx
msgid "The organisation you are looking for may have been removed, renamed or may have never\n"
msgid ""
"The organisation you are looking for may have been removed, renamed or may have never\n"
" existed."
msgstr "Die Organisation, nach der Sie suchen, wurde möglicherweise entfernt, umbenannt oder hat möglicherweise nie existiert."
#: apps/remix/app/routes/_authenticated+/_layout.tsx
msgid "The organisation you are looking for may have been removed, renamed or may have never\n"
msgid ""
"The organisation you are looking for may have been removed, renamed or may have never\n"
" existed."
msgstr "Die Organisation, nach der Sie suchen, wurde möglicherweise entfernt, umbenannt oder hat möglicherweise nie existiert."
@@ -6574,12 +6624,14 @@ msgid "The team email <0>{teamEmail}</0> has been removed from the following tea
msgstr "Die Team-E-Mail <0>{teamEmail}</0> wurde aus dem folgenden Team entfernt"
#: apps/remix/app/routes/_authenticated+/_layout.tsx
msgid "The team you are looking for may have been removed, renamed or may have never\n"
msgid ""
"The team you are looking for may have been removed, renamed or may have never\n"
" existed."
msgstr "Das Team, das Sie suchen, wurde möglicherweise entfernt, umbenannt oder hat möglicherweise nie existiert."
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/_layout.tsx
msgid "The team you are looking for may have been removed, renamed or may have never\n"
msgid ""
"The team you are looking for may have been removed, renamed or may have never\n"
" existed."
msgstr "Das Team, das Sie suchen, könnte entfernt, umbenannt oder nie existiert haben."
@@ -6617,7 +6669,8 @@ msgid "The URL for Documenso to send webhook events to."
msgstr "Die URL für Documenso, um Webhook-Ereignisse zu senden."
#: apps/remix/app/routes/_authenticated+/admin+/users.$id.tsx
msgid "The user you are looking for may have been removed, renamed or may have never\n"
msgid ""
"The user you are looking for may have been removed, renamed or may have never\n"
" existed."
msgstr "Der Benutzer, nach dem Sie suchen, wurde möglicherweise entfernt, umbenannt oder hat möglicherweise nie existiert."
@@ -6784,10 +6837,6 @@ msgstr "So wird das Dokument die Empfänger erreichen, sobald es zum Unterschrei
msgid "This is the claim that this organisation was initially created with. Any feature flag changes to this claim will be backported into this organisation."
msgstr "Dies ist der Anspruch, mit dem diese Organisation ursprünglich erstellt wurde. Alle Änderungen an Funktionsflags für diesen Anspruch werden in diese Organisation übernommen."
#: apps/remix/app/routes/_unauthenticated+/team.verify.transfer.$token.tsx
msgid "This link is invalid or has expired."
msgstr "Dieser Link ist ungültig oder abgelaufen."
#: apps/remix/app/routes/_unauthenticated+/team.verify.email.$token.tsx
msgid "This link is invalid or has expired. Please contact your team to resend a verification."
msgstr "Dieser Link ist ungültig oder abgelaufen. Bitte kontaktieren Sie Ihr Team, um eine neue Bestätigungsanfrage zu senden."
@@ -6858,6 +6907,10 @@ msgstr "Dies wird an den Dokumenteneigentümer gesendet, sobald das Dokument vol
msgid "This will ONLY backport feature flags which are set to true, anything disabled in the initial claim will not be backported"
msgstr "Diese werden NUR Funktionsflags zurückspielen, die auf wahr gesetzt sind; alles, was im ursprünglichen Anspruch deaktiviert ist, wird nicht zurückportiert."
#: apps/remix/app/components/dialogs/session-logout-all-dialog.tsx
msgid "This will sign you out of all other devices. You will need to sign in again on those devices to continue using your account."
msgstr ""
#: apps/remix/app/components/tables/internal-audit-log-table.tsx
#: apps/remix/app/components/tables/document-logs-table.tsx
msgid "Time"
@@ -7144,6 +7197,9 @@ msgstr "Unvollendet"
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
msgid "Unknown"
msgstr "Unbekannt"
@@ -7497,6 +7553,11 @@ msgstr "Alle verwandten Dokumente anzeigen"
msgid "View all security activity related to your account."
msgstr "Sehen Sie sich alle Sicherheitsaktivitäten in Ihrem Konto an."
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
#: apps/remix/app/routes/_authenticated+/settings+/security._index.tsx
msgid "View and manage all active sessions for your account."
msgstr ""
#: apps/remix/app/components/forms/2fa/view-recovery-codes-dialog.tsx
msgid "View Codes"
msgstr "Codes ansehen"
@@ -8563,4 +8624,3 @@ msgstr "Ihr Token wurde erfolgreich erstellt! Stellen Sie sicher, dass Sie es ko
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.tokens.tsx
msgid "Your tokens will be shown here once you create them."
msgstr "Ihre Tokens werden hier angezeigt, sobald Sie sie erstellt haben."
+63 -8
View File
@@ -747,6 +747,11 @@ msgstr "Actions"
msgid "Active"
msgstr "Active"
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
#: apps/remix/app/routes/_authenticated+/settings+/security._index.tsx
msgid "Active sessions"
msgstr "Active sessions"
#: apps/remix/app/routes/_authenticated+/admin+/stats.tsx
msgid "Active Subscriptions"
msgstr "Active Subscriptions"
@@ -812,13 +817,13 @@ msgstr "Add Fields"
msgid "Add group roles"
msgstr "Add group roles"
#: apps/remix/app/components/dialogs/team-group-create-dialog.tsx
#: apps/remix/app/components/dialogs/team-group-create-dialog.tsx
msgid "Add groups"
msgstr "Add groups"
#: apps/remix/app/components/dialogs/team-member-create-dialog.tsx
#: apps/remix/app/components/dialogs/team-member-create-dialog.tsx
#: apps/remix/app/components/dialogs/team-group-create-dialog.tsx
msgid "Add members"
msgstr "Add members"
@@ -1609,6 +1614,7 @@ msgstr "Can prepare"
#: apps/remix/app/components/dialogs/team-delete-dialog.tsx
#: apps/remix/app/components/dialogs/team-create-dialog.tsx
#: apps/remix/app/components/dialogs/team-create-dialog.tsx
#: apps/remix/app/components/dialogs/session-logout-all-dialog.tsx
#: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx
#: apps/remix/app/components/dialogs/passkey-create-dialog.tsx
#: apps/remix/app/components/dialogs/organisation-member-update-dialog.tsx
@@ -2216,6 +2222,7 @@ msgstr "Create your account and start using state-of-the-art document signing."
msgid "Create your account and start using state-of-the-art document signing. Open and beautiful signing is within your grasp."
msgstr "Create your account and start using state-of-the-art document signing. Open and beautiful signing is within your grasp."
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
#: apps/remix/app/routes/_authenticated+/admin+/documents._index.tsx
#: apps/remix/app/components/tables/templates-table.tsx
#: apps/remix/app/components/tables/settings-security-passkey-table.tsx
@@ -2255,6 +2262,10 @@ msgstr "Created on {0}"
msgid "CSV Structure"
msgstr "CSV Structure"
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
msgid "Current"
msgstr "Current"
#: apps/remix/app/components/forms/password.tsx
msgid "Current Password"
msgstr "Current Password"
@@ -2448,6 +2459,7 @@ msgid "Details"
msgstr "Details"
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
#: apps/remix/app/components/tables/settings-security-activity-table.tsx
msgid "Device"
msgstr "Device"
@@ -3174,6 +3186,7 @@ msgstr "Enterprise"
#: apps/remix/app/routes/embed+/v1+/authoring+/template.edit.$id.tsx
#: apps/remix/app/routes/embed+/v1+/authoring+/document.edit.$id.tsx
#: apps/remix/app/routes/embed+/v1+/authoring+/document.edit.$id.tsx
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
#: apps/remix/app/routes/_authenticated+/admin+/users.$id.tsx
#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx
#: apps/remix/app/routes/_authenticated+/admin+/documents.$id.tsx
@@ -3216,6 +3229,7 @@ msgstr "Enterprise"
#: apps/remix/app/components/dialogs/template-move-to-folder-dialog.tsx
#: apps/remix/app/components/dialogs/template-move-to-folder-dialog.tsx
#: apps/remix/app/components/dialogs/template-duplicate-dialog.tsx
#: apps/remix/app/components/dialogs/session-logout-all-dialog.tsx
#: apps/remix/app/components/dialogs/document-move-to-folder-dialog.tsx
#: apps/remix/app/components/dialogs/document-move-to-folder-dialog.tsx
#: apps/remix/app/components/dialogs/admin-user-enable-dialog.tsx
@@ -3281,10 +3295,18 @@ msgstr "Failed to load document"
msgid "Failed to reseal document"
msgstr "Failed to reseal document"
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
msgid "Failed to revoke session"
msgstr "Failed to revoke session"
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx
msgid "Failed to save settings."
msgstr "Failed to save settings."
#: apps/remix/app/components/dialogs/session-logout-all-dialog.tsx
msgid "Failed to sign out all sessions"
msgstr "Failed to sign out all sessions"
#: apps/remix/app/routes/embed+/v1+/authoring+/document.edit.$id.tsx
msgid "Failed to update document"
msgstr "Failed to update document"
@@ -3742,7 +3764,6 @@ msgstr "Invalid code. Please try again."
msgid "Invalid email"
msgstr "Invalid email"
#: apps/remix/app/routes/_unauthenticated+/team.verify.transfer.$token.tsx
#: apps/remix/app/routes/_unauthenticated+/team.verify.email.$token.tsx
msgid "Invalid link"
msgstr "Invalid link"
@@ -3806,6 +3827,7 @@ msgid "Invoice"
msgstr "Invoice"
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
#: apps/remix/app/components/tables/internal-audit-log-table.tsx
msgid "IP Address"
msgstr "IP Address"
@@ -3876,6 +3898,10 @@ msgstr "Last 30 days"
msgid "Last 7 days"
msgstr "Last 7 days"
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
msgid "Last Active"
msgstr "Last Active"
#: apps/remix/app/components/general/template/template-page-view-information.tsx
#: apps/remix/app/components/general/document/document-page-view-information.tsx
msgid "Last modified"
@@ -4025,6 +4051,10 @@ msgstr "Manage passkeys"
msgid "Manage permissions and access controls"
msgstr "Manage permissions and access controls"
#: apps/remix/app/routes/_authenticated+/settings+/security._index.tsx
msgid "Manage sessions"
msgstr "Manage sessions"
#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx
#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx
msgid "Manage subscription"
@@ -5300,7 +5330,6 @@ msgstr "Retention of Documents"
msgid "Retry"
msgstr "Retry"
#: apps/remix/app/routes/_unauthenticated+/team.verify.transfer.$token.tsx
#: apps/remix/app/routes/_unauthenticated+/team.verify.email.$token.tsx
#: apps/remix/app/routes/_unauthenticated+/organisation.invite.$token.tsx
#: apps/remix/app/routes/_unauthenticated+/organisation.decline.$token.tsx
@@ -5316,6 +5345,7 @@ msgstr "Return to Home"
msgid "Return to sign in"
msgstr "Return to sign in"
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
#: apps/remix/app/components/general/teams/team-email-usage.tsx
msgid "Revoke"
msgstr "Revoke"
@@ -5324,6 +5354,12 @@ msgstr "Revoke"
msgid "Revoke access"
msgstr "Revoke access"
#: apps/remix/app/components/dialogs/session-logout-all-dialog.tsx
#: apps/remix/app/components/dialogs/session-logout-all-dialog.tsx
#: apps/remix/app/components/dialogs/session-logout-all-dialog.tsx
msgid "Revoke all sessions"
msgstr "Revoke all sessions"
#: apps/remix/app/components/tables/user-organisations-table.tsx
#: apps/remix/app/components/tables/team-members-table.tsx
#: apps/remix/app/components/tables/team-groups-table.tsx
@@ -5476,6 +5512,10 @@ msgstr "Select default option"
msgid "Select groups"
msgstr "Select groups"
#: apps/remix/app/components/dialogs/team-group-create-dialog.tsx
msgid "Select groups of members to add to the team."
msgstr "Select groups of members to add to the team."
#: apps/remix/app/components/dialogs/team-group-create-dialog.tsx
msgid "Select groups to add to this team"
msgstr "Select groups to add to this team"
@@ -5487,7 +5527,6 @@ msgid "Select members"
msgstr "Select members"
#: apps/remix/app/components/dialogs/team-member-create-dialog.tsx
#: apps/remix/app/components/dialogs/team-group-create-dialog.tsx
msgid "Select members or groups of members to add to the team."
msgstr "Select members or groups of members to add to the team."
@@ -5597,6 +5636,14 @@ msgstr "Sending..."
msgid "Sent"
msgstr "Sent"
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
msgid "Session revoked"
msgstr "Session revoked"
#: apps/remix/app/components/dialogs/session-logout-all-dialog.tsx
msgid "Sessions have been revoked"
msgstr "Sessions have been revoked"
#: apps/remix/app/components/general/claim-account.tsx
msgid "Set a password"
msgstr "Set a password"
@@ -6797,10 +6844,6 @@ msgstr "This is how the document will reach the recipients once the document is
msgid "This is the claim that this organisation was initially created with. Any feature flag changes to this claim will be backported into this organisation."
msgstr "This is the claim that this organisation was initially created with. Any feature flag changes to this claim will be backported into this organisation."
#: apps/remix/app/routes/_unauthenticated+/team.verify.transfer.$token.tsx
msgid "This link is invalid or has expired."
msgstr "This link is invalid or has expired."
#: apps/remix/app/routes/_unauthenticated+/team.verify.email.$token.tsx
msgid "This link is invalid or has expired. Please contact your team to resend a verification."
msgstr "This link is invalid or has expired. Please contact your team to resend a verification."
@@ -6871,6 +6914,10 @@ msgstr "This will be sent to the document owner once the document has been fully
msgid "This will ONLY backport feature flags which are set to true, anything disabled in the initial claim will not be backported"
msgstr "This will ONLY backport feature flags which are set to true, anything disabled in the initial claim will not be backported"
#: apps/remix/app/components/dialogs/session-logout-all-dialog.tsx
msgid "This will sign you out of all other devices. You will need to sign in again on those devices to continue using your account."
msgstr "This will sign you out of all other devices. You will need to sign in again on those devices to continue using your account."
#: apps/remix/app/components/tables/internal-audit-log-table.tsx
#: apps/remix/app/components/tables/document-logs-table.tsx
msgid "Time"
@@ -7157,6 +7204,9 @@ msgstr "Uncompleted"
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
msgid "Unknown"
msgstr "Unknown"
@@ -7510,6 +7560,11 @@ msgstr "View all related documents"
msgid "View all security activity related to your account."
msgstr "View all security activity related to your account."
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
#: apps/remix/app/routes/_authenticated+/settings+/security._index.tsx
msgid "View and manage all active sessions for your account."
msgstr "View and manage all active sessions for your account."
#: apps/remix/app/components/forms/2fa/view-recovery-codes-dialog.tsx
msgid "View Codes"
msgstr "View Codes"
+77 -16
View File
@@ -752,6 +752,11 @@ msgstr "Acciones"
msgid "Active"
msgstr "Activo"
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
#: apps/remix/app/routes/_authenticated+/settings+/security._index.tsx
msgid "Active sessions"
msgstr ""
#: apps/remix/app/routes/_authenticated+/admin+/stats.tsx
msgid "Active Subscriptions"
msgstr "Suscripciones Activas"
@@ -817,13 +822,13 @@ msgstr "Agregar Campos"
msgid "Add group roles"
msgstr "Agregar roles de grupo"
#: apps/remix/app/components/dialogs/team-group-create-dialog.tsx
#: apps/remix/app/components/dialogs/team-group-create-dialog.tsx
msgid "Add groups"
msgstr "Agregar grupos"
#: apps/remix/app/components/dialogs/team-member-create-dialog.tsx
#: apps/remix/app/components/dialogs/team-member-create-dialog.tsx
#: apps/remix/app/components/dialogs/team-group-create-dialog.tsx
msgid "Add members"
msgstr "Agregar miembros"
@@ -1614,6 +1619,7 @@ msgstr "Puede preparar"
#: apps/remix/app/components/dialogs/team-delete-dialog.tsx
#: apps/remix/app/components/dialogs/team-create-dialog.tsx
#: apps/remix/app/components/dialogs/team-create-dialog.tsx
#: apps/remix/app/components/dialogs/session-logout-all-dialog.tsx
#: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx
#: apps/remix/app/components/dialogs/passkey-create-dialog.tsx
#: apps/remix/app/components/dialogs/organisation-member-update-dialog.tsx
@@ -2221,6 +2227,7 @@ msgstr "Crea tu cuenta y comienza a utilizar la firma de documentos de última g
msgid "Create your account and start using state-of-the-art document signing. Open and beautiful signing is within your grasp."
msgstr "Crea tu cuenta y comienza a utilizar la firma de documentos de última generación. La firma abierta y hermosa está al alcance de tu mano."
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
#: apps/remix/app/routes/_authenticated+/admin+/documents._index.tsx
#: apps/remix/app/components/tables/templates-table.tsx
#: apps/remix/app/components/tables/settings-security-passkey-table.tsx
@@ -2260,6 +2267,10 @@ msgstr "Creado el {0}"
msgid "CSV Structure"
msgstr "Estructura CSV"
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
msgid "Current"
msgstr ""
#: apps/remix/app/components/forms/password.tsx
msgid "Current Password"
msgstr "Contraseña actual"
@@ -2453,6 +2464,7 @@ msgid "Details"
msgstr "Detalles"
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
#: apps/remix/app/components/tables/settings-security-activity-table.tsx
msgid "Device"
msgstr "Dispositivo"
@@ -3179,6 +3191,7 @@ msgstr "Enterprise"
#: apps/remix/app/routes/embed+/v1+/authoring+/template.edit.$id.tsx
#: apps/remix/app/routes/embed+/v1+/authoring+/document.edit.$id.tsx
#: apps/remix/app/routes/embed+/v1+/authoring+/document.edit.$id.tsx
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
#: apps/remix/app/routes/_authenticated+/admin+/users.$id.tsx
#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx
#: apps/remix/app/routes/_authenticated+/admin+/documents.$id.tsx
@@ -3221,6 +3234,7 @@ msgstr "Enterprise"
#: apps/remix/app/components/dialogs/template-move-to-folder-dialog.tsx
#: apps/remix/app/components/dialogs/template-move-to-folder-dialog.tsx
#: apps/remix/app/components/dialogs/template-duplicate-dialog.tsx
#: apps/remix/app/components/dialogs/session-logout-all-dialog.tsx
#: apps/remix/app/components/dialogs/document-move-to-folder-dialog.tsx
#: apps/remix/app/components/dialogs/document-move-to-folder-dialog.tsx
#: apps/remix/app/components/dialogs/admin-user-enable-dialog.tsx
@@ -3286,10 +3300,18 @@ msgstr "Error al cargar el documento"
msgid "Failed to reseal document"
msgstr "Falló al volver a sellar el documento"
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
msgid "Failed to revoke session"
msgstr ""
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx
msgid "Failed to save settings."
msgstr "Fallo al guardar configuraciones."
#: apps/remix/app/components/dialogs/session-logout-all-dialog.tsx
msgid "Failed to sign out all sessions"
msgstr ""
#: apps/remix/app/routes/embed+/v1+/authoring+/document.edit.$id.tsx
msgid "Failed to update document"
msgstr "No se pudo actualizar el documento"
@@ -3747,7 +3769,6 @@ msgstr "Código inválido. Por favor, intenta nuevamente."
msgid "Invalid email"
msgstr "Email inválido"
#: apps/remix/app/routes/_unauthenticated+/team.verify.transfer.$token.tsx
#: apps/remix/app/routes/_unauthenticated+/team.verify.email.$token.tsx
msgid "Invalid link"
msgstr "Enlace inválido"
@@ -3811,6 +3832,7 @@ msgid "Invoice"
msgstr "Factura"
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
#: apps/remix/app/components/tables/internal-audit-log-table.tsx
msgid "IP Address"
msgstr "Dirección IP"
@@ -3881,6 +3903,10 @@ msgstr "Últimos 30 días"
msgid "Last 7 days"
msgstr "Últimos 7 días"
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
msgid "Last Active"
msgstr ""
#: apps/remix/app/components/general/template/template-page-view-information.tsx
#: apps/remix/app/components/general/document/document-page-view-information.tsx
msgid "Last modified"
@@ -4030,6 +4056,10 @@ msgstr "Gestionar claves de acceso"
msgid "Manage permissions and access controls"
msgstr "Gestiona permisos y controles de acceso"
#: apps/remix/app/routes/_authenticated+/settings+/security._index.tsx
msgid "Manage sessions"
msgstr ""
#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx
#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx
msgid "Manage subscription"
@@ -5305,7 +5335,6 @@ msgstr "Retención de Documentos"
msgid "Retry"
msgstr "Reintentar"
#: apps/remix/app/routes/_unauthenticated+/team.verify.transfer.$token.tsx
#: apps/remix/app/routes/_unauthenticated+/team.verify.email.$token.tsx
#: apps/remix/app/routes/_unauthenticated+/organisation.invite.$token.tsx
#: apps/remix/app/routes/_unauthenticated+/organisation.decline.$token.tsx
@@ -5321,6 +5350,7 @@ msgstr "Regresar a la página de inicio"
msgid "Return to sign in"
msgstr "Regresar para iniciar sesión"
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
#: apps/remix/app/components/general/teams/team-email-usage.tsx
msgid "Revoke"
msgstr "Revocar"
@@ -5329,6 +5359,12 @@ msgstr "Revocar"
msgid "Revoke access"
msgstr "Revocar acceso"
#: apps/remix/app/components/dialogs/session-logout-all-dialog.tsx
#: apps/remix/app/components/dialogs/session-logout-all-dialog.tsx
#: apps/remix/app/components/dialogs/session-logout-all-dialog.tsx
msgid "Revoke all sessions"
msgstr ""
#: apps/remix/app/components/tables/user-organisations-table.tsx
#: apps/remix/app/components/tables/team-members-table.tsx
#: apps/remix/app/components/tables/team-groups-table.tsx
@@ -5481,6 +5517,10 @@ msgstr "Seleccionar opción predeterminada"
msgid "Select groups"
msgstr "Seleccionar grupos"
#: apps/remix/app/components/dialogs/team-group-create-dialog.tsx
msgid "Select groups of members to add to the team."
msgstr ""
#: apps/remix/app/components/dialogs/team-group-create-dialog.tsx
msgid "Select groups to add to this team"
msgstr "Seleccionar grupos para añadir a este equipo"
@@ -5492,7 +5532,6 @@ msgid "Select members"
msgstr "Seleccione miembros"
#: apps/remix/app/components/dialogs/team-member-create-dialog.tsx
#: apps/remix/app/components/dialogs/team-group-create-dialog.tsx
msgid "Select members or groups of members to add to the team."
msgstr "Seleccione miembros o grupos de miembros para agregar al equipo."
@@ -5602,6 +5641,14 @@ msgstr "Enviando..."
msgid "Sent"
msgstr "Enviado"
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
msgid "Session revoked"
msgstr ""
#: apps/remix/app/components/dialogs/session-logout-all-dialog.tsx
msgid "Sessions have been revoked"
msgstr ""
#: apps/remix/app/components/general/claim-account.tsx
msgid "Set a password"
msgstr "Establecer una contraseña"
@@ -6476,7 +6523,8 @@ msgid "The following team has been deleted. You will no longer be able to access
msgstr "El siguiente equipo ha sido eliminado. Ya no podrá acceder a este equipo y sus documentos"
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.groups.$id.tsx
msgid "The organisation group you are looking for may have been removed, renamed or may have never\n"
msgid ""
"The organisation group you are looking for may have been removed, renamed or may have never\n"
" existed."
msgstr "El grupo de organización que está buscando puede haber sido eliminado, renombrado o puede que nunca haya existido."
@@ -6485,12 +6533,14 @@ msgid "The organisation role that will be applied to all members in this group."
msgstr "El rol de organización que se aplicará a todos los miembros de este grupo."
#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx
msgid "The organisation you are looking for may have been removed, renamed or may have never\n"
msgid ""
"The organisation you are looking for may have been removed, renamed or may have never\n"
" existed."
msgstr "La organización que está buscando puede haber sido eliminada, renombrada o puede que nunca haya existido."
#: apps/remix/app/routes/_authenticated+/_layout.tsx
msgid "The organisation you are looking for may have been removed, renamed or may have never\n"
msgid ""
"The organisation you are looking for may have been removed, renamed or may have never\n"
" existed."
msgstr "La organización que está buscando puede haber sido eliminada, renombrada o puede que nunca haya existido."
@@ -6574,14 +6624,17 @@ msgid "The team email <0>{teamEmail}</0> has been removed from the following tea
msgstr "El correo electrónico del equipo <0>{teamEmail}</0> ha sido eliminado del siguiente equipo"
#: apps/remix/app/routes/_authenticated+/_layout.tsx
msgid "The team you are looking for may have been removed, renamed or may have never\n"
msgid ""
"The team you are looking for may have been removed, renamed or may have never\n"
" existed."
msgstr "El equipo que está buscando puede haber sido eliminado, renombrado o puede que nunca haya existido."
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/_layout.tsx
msgid "The team you are looking for may have been removed, renamed or may have never\n"
msgid ""
"The team you are looking for may have been removed, renamed or may have never\n"
" existed."
msgstr "El equipo que buscas puede haber sido eliminado, renombrado o quizás nunca\n"
msgstr ""
"El equipo que buscas puede haber sido eliminado, renombrado o quizás nunca\n"
" existió."
#: apps/remix/app/components/dialogs/template-move-to-folder-dialog.tsx
@@ -6618,7 +6671,8 @@ msgid "The URL for Documenso to send webhook events to."
msgstr "La URL para Documenso para enviar eventos de webhook."
#: apps/remix/app/routes/_authenticated+/admin+/users.$id.tsx
msgid "The user you are looking for may have been removed, renamed or may have never\n"
msgid ""
"The user you are looking for may have been removed, renamed or may have never\n"
" existed."
msgstr "El usuario que está buscando puede haber sido eliminado, renombrado o puede que nunca haya existido."
@@ -6785,10 +6839,6 @@ msgstr "Así es como el documento llegará a los destinatarios una vez que esté
msgid "This is the claim that this organisation was initially created with. Any feature flag changes to this claim will be backported into this organisation."
msgstr "Esta es la reclamo con la que se creó inicialmente esta organización. Cualquier cambio en la bandera de características de esta reclamo se retroalimentará en esta organización."
#: apps/remix/app/routes/_unauthenticated+/team.verify.transfer.$token.tsx
msgid "This link is invalid or has expired."
msgstr "Este enlace no es válido o ha expirado."
#: apps/remix/app/routes/_unauthenticated+/team.verify.email.$token.tsx
msgid "This link is invalid or has expired. Please contact your team to resend a verification."
msgstr "Este enlace es inválido o ha expirado. Por favor, contacta a tu equipo para reenviar una verificación."
@@ -6859,6 +6909,10 @@ msgstr "Esto se enviará al propietario del documento una vez que el documento s
msgid "This will ONLY backport feature flags which are set to true, anything disabled in the initial claim will not be backported"
msgstr "Esto solo retroalimentará las banderas de características que estén configuradas como verdaderas, cualquier cosa desactivada en la reclamo inicial no será retroalimentada"
#: apps/remix/app/components/dialogs/session-logout-all-dialog.tsx
msgid "This will sign you out of all other devices. You will need to sign in again on those devices to continue using your account."
msgstr ""
#: apps/remix/app/components/tables/internal-audit-log-table.tsx
#: apps/remix/app/components/tables/document-logs-table.tsx
msgid "Time"
@@ -7145,6 +7199,9 @@ msgstr "Incompleto"
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
msgid "Unknown"
msgstr "Desconocido"
@@ -7498,6 +7555,11 @@ msgstr "Ver todos los documentos relacionados"
msgid "View all security activity related to your account."
msgstr "Ver toda la actividad de seguridad relacionada con tu cuenta."
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
#: apps/remix/app/routes/_authenticated+/settings+/security._index.tsx
msgid "View and manage all active sessions for your account."
msgstr ""
#: apps/remix/app/components/forms/2fa/view-recovery-codes-dialog.tsx
msgid "View Codes"
msgstr "Ver Códigos"
@@ -8564,4 +8626,3 @@ msgstr "¡Tu token se creó con éxito! ¡Asegúrate de copiarlo porque no podr
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.tokens.tsx
msgid "Your tokens will be shown here once you create them."
msgstr "Tus tokens se mostrarán aquí una vez que los crees."
+75 -15
View File
@@ -752,6 +752,11 @@ msgstr "Actions"
msgid "Active"
msgstr "Actif"
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
#: apps/remix/app/routes/_authenticated+/settings+/security._index.tsx
msgid "Active sessions"
msgstr ""
#: apps/remix/app/routes/_authenticated+/admin+/stats.tsx
msgid "Active Subscriptions"
msgstr "Abonnements actifs"
@@ -817,13 +822,13 @@ msgstr "Ajouter des champs"
msgid "Add group roles"
msgstr "Ajouter des rôles de groupe"
#: apps/remix/app/components/dialogs/team-group-create-dialog.tsx
#: apps/remix/app/components/dialogs/team-group-create-dialog.tsx
msgid "Add groups"
msgstr "Ajouter des groupes"
#: apps/remix/app/components/dialogs/team-member-create-dialog.tsx
#: apps/remix/app/components/dialogs/team-member-create-dialog.tsx
#: apps/remix/app/components/dialogs/team-group-create-dialog.tsx
msgid "Add members"
msgstr "Ajouter des membres"
@@ -1614,6 +1619,7 @@ msgstr "Peut préparer"
#: apps/remix/app/components/dialogs/team-delete-dialog.tsx
#: apps/remix/app/components/dialogs/team-create-dialog.tsx
#: apps/remix/app/components/dialogs/team-create-dialog.tsx
#: apps/remix/app/components/dialogs/session-logout-all-dialog.tsx
#: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx
#: apps/remix/app/components/dialogs/passkey-create-dialog.tsx
#: apps/remix/app/components/dialogs/organisation-member-update-dialog.tsx
@@ -2221,6 +2227,7 @@ msgstr "Créez votre compte et commencez à utiliser la signature de documents
msgid "Create your account and start using state-of-the-art document signing. Open and beautiful signing is within your grasp."
msgstr "Créez votre compte et commencez à utiliser la signature de documents à la pointe de la technologie. Une signature ouverte et magnifique est à votre portée."
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
#: apps/remix/app/routes/_authenticated+/admin+/documents._index.tsx
#: apps/remix/app/components/tables/templates-table.tsx
#: apps/remix/app/components/tables/settings-security-passkey-table.tsx
@@ -2260,6 +2267,10 @@ msgstr "Créé le {0}"
msgid "CSV Structure"
msgstr "Structure CSV"
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
msgid "Current"
msgstr ""
#: apps/remix/app/components/forms/password.tsx
msgid "Current Password"
msgstr "Mot de passe actuel"
@@ -2453,6 +2464,7 @@ msgid "Details"
msgstr "Détails"
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
#: apps/remix/app/components/tables/settings-security-activity-table.tsx
msgid "Device"
msgstr "Appareil"
@@ -3179,6 +3191,7 @@ msgstr "Entreprise"
#: apps/remix/app/routes/embed+/v1+/authoring+/template.edit.$id.tsx
#: apps/remix/app/routes/embed+/v1+/authoring+/document.edit.$id.tsx
#: apps/remix/app/routes/embed+/v1+/authoring+/document.edit.$id.tsx
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
#: apps/remix/app/routes/_authenticated+/admin+/users.$id.tsx
#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx
#: apps/remix/app/routes/_authenticated+/admin+/documents.$id.tsx
@@ -3221,6 +3234,7 @@ msgstr "Entreprise"
#: apps/remix/app/components/dialogs/template-move-to-folder-dialog.tsx
#: apps/remix/app/components/dialogs/template-move-to-folder-dialog.tsx
#: apps/remix/app/components/dialogs/template-duplicate-dialog.tsx
#: apps/remix/app/components/dialogs/session-logout-all-dialog.tsx
#: apps/remix/app/components/dialogs/document-move-to-folder-dialog.tsx
#: apps/remix/app/components/dialogs/document-move-to-folder-dialog.tsx
#: apps/remix/app/components/dialogs/admin-user-enable-dialog.tsx
@@ -3286,10 +3300,18 @@ msgstr "Échec du chargement du document"
msgid "Failed to reseal document"
msgstr "Échec du reseal du document"
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
msgid "Failed to revoke session"
msgstr ""
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx
msgid "Failed to save settings."
msgstr "Échec de l'enregistrement des paramètres."
#: apps/remix/app/components/dialogs/session-logout-all-dialog.tsx
msgid "Failed to sign out all sessions"
msgstr ""
#: apps/remix/app/routes/embed+/v1+/authoring+/document.edit.$id.tsx
msgid "Failed to update document"
msgstr "Échec de la mise à jour du document"
@@ -3747,7 +3769,6 @@ msgstr "Code invalide. Veuillez réessayer."
msgid "Invalid email"
msgstr "Email invalide"
#: apps/remix/app/routes/_unauthenticated+/team.verify.transfer.$token.tsx
#: apps/remix/app/routes/_unauthenticated+/team.verify.email.$token.tsx
msgid "Invalid link"
msgstr "Lien invalide"
@@ -3811,6 +3832,7 @@ msgid "Invoice"
msgstr "Facture"
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
#: apps/remix/app/components/tables/internal-audit-log-table.tsx
msgid "IP Address"
msgstr "Adresse IP"
@@ -3881,6 +3903,10 @@ msgstr "30 derniers jours"
msgid "Last 7 days"
msgstr "7 derniers jours"
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
msgid "Last Active"
msgstr ""
#: apps/remix/app/components/general/template/template-page-view-information.tsx
#: apps/remix/app/components/general/document/document-page-view-information.tsx
msgid "Last modified"
@@ -4030,6 +4056,10 @@ msgstr "Gérer les clés d'accès"
msgid "Manage permissions and access controls"
msgstr "Gérez les autorisations et les contrôles d'accès"
#: apps/remix/app/routes/_authenticated+/settings+/security._index.tsx
msgid "Manage sessions"
msgstr ""
#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx
#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx
msgid "Manage subscription"
@@ -5305,7 +5335,6 @@ msgstr "Conservation des documents"
msgid "Retry"
msgstr "Réessayer"
#: apps/remix/app/routes/_unauthenticated+/team.verify.transfer.$token.tsx
#: apps/remix/app/routes/_unauthenticated+/team.verify.email.$token.tsx
#: apps/remix/app/routes/_unauthenticated+/organisation.invite.$token.tsx
#: apps/remix/app/routes/_unauthenticated+/organisation.decline.$token.tsx
@@ -5321,6 +5350,7 @@ msgstr "Retour à l'accueil"
msgid "Return to sign in"
msgstr "Retour à la connexion"
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
#: apps/remix/app/components/general/teams/team-email-usage.tsx
msgid "Revoke"
msgstr "Révoquer"
@@ -5329,6 +5359,12 @@ msgstr "Révoquer"
msgid "Revoke access"
msgstr "Révoquer l'accès"
#: apps/remix/app/components/dialogs/session-logout-all-dialog.tsx
#: apps/remix/app/components/dialogs/session-logout-all-dialog.tsx
#: apps/remix/app/components/dialogs/session-logout-all-dialog.tsx
msgid "Revoke all sessions"
msgstr ""
#: apps/remix/app/components/tables/user-organisations-table.tsx
#: apps/remix/app/components/tables/team-members-table.tsx
#: apps/remix/app/components/tables/team-groups-table.tsx
@@ -5481,6 +5517,10 @@ msgstr "Sélectionner l'option par défaut"
msgid "Select groups"
msgstr "Sélectionnez des groupes"
#: apps/remix/app/components/dialogs/team-group-create-dialog.tsx
msgid "Select groups of members to add to the team."
msgstr ""
#: apps/remix/app/components/dialogs/team-group-create-dialog.tsx
msgid "Select groups to add to this team"
msgstr "Sélectionnez des groupes à ajouter à cette équipe"
@@ -5492,7 +5532,6 @@ msgid "Select members"
msgstr "Sélectionnez des membres"
#: apps/remix/app/components/dialogs/team-member-create-dialog.tsx
#: apps/remix/app/components/dialogs/team-group-create-dialog.tsx
msgid "Select members or groups of members to add to the team."
msgstr "Sélectionnez des membres ou groupes de membres à ajouter à l'équipe."
@@ -5602,6 +5641,14 @@ msgstr "Envoi..."
msgid "Sent"
msgstr "Envoyé"
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
msgid "Session revoked"
msgstr ""
#: apps/remix/app/components/dialogs/session-logout-all-dialog.tsx
msgid "Sessions have been revoked"
msgstr ""
#: apps/remix/app/components/general/claim-account.tsx
msgid "Set a password"
msgstr "Définir un mot de passe"
@@ -6476,7 +6523,8 @@ msgid "The following team has been deleted. You will no longer be able to access
msgstr "L'équipe suivante a été supprimée. Vous ne pourrez plus accéder à cette équipe et à ses documents"
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.groups.$id.tsx
msgid "The organisation group you are looking for may have been removed, renamed or may have never\n"
msgid ""
"The organisation group you are looking for may have been removed, renamed or may have never\n"
" existed."
msgstr "Le groupe d'organisation que vous cherchez peut avoir été supprimé, renommé ou n'a peut-être jamais existé."
@@ -6485,12 +6533,14 @@ msgid "The organisation role that will be applied to all members in this group."
msgstr "Le rôle d'organisation qui sera appliqué à tous les membres de ce groupe."
#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx
msgid "The organisation you are looking for may have been removed, renamed or may have never\n"
msgid ""
"The organisation you are looking for may have been removed, renamed or may have never\n"
" existed."
msgstr "L'organisation que vous cherchez peut avoir été supprimée, renommée ou n'a peut-être jamais existé."
#: apps/remix/app/routes/_authenticated+/_layout.tsx
msgid "The organisation you are looking for may have been removed, renamed or may have never\n"
msgid ""
"The organisation you are looking for may have been removed, renamed or may have never\n"
" existed."
msgstr "L'organisation que vous cherchez peut avoir été supprimée, renommée ou n'a peut-être jamais existé."
@@ -6574,12 +6624,14 @@ msgid "The team email <0>{teamEmail}</0> has been removed from the following tea
msgstr "L'email d'équipe <0>{teamEmail}</0> a été supprimé de l'équipe suivante"
#: apps/remix/app/routes/_authenticated+/_layout.tsx
msgid "The team you are looking for may have been removed, renamed or may have never\n"
msgid ""
"The team you are looking for may have been removed, renamed or may have never\n"
" existed."
msgstr "L'équipe que vous cherchez peut avoir été supprimée, renommée ou n'a peut-être jamais existé."
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/_layout.tsx
msgid "The team you are looking for may have been removed, renamed or may have never\n"
msgid ""
"The team you are looking for may have been removed, renamed or may have never\n"
" existed."
msgstr "L'équipe que vous cherchez a peut-être été supprimée, renommée ou n'a peut-être jamais existé."
@@ -6617,7 +6669,8 @@ msgid "The URL for Documenso to send webhook events to."
msgstr "L'URL pour Documenso pour envoyer des événements webhook."
#: apps/remix/app/routes/_authenticated+/admin+/users.$id.tsx
msgid "The user you are looking for may have been removed, renamed or may have never\n"
msgid ""
"The user you are looking for may have been removed, renamed or may have never\n"
" existed."
msgstr "L'utilisateur que vous cherchez peut avoir été supprimé, renommé ou n'a peut-être jamais existé."
@@ -6784,10 +6837,6 @@ msgstr "Voici comment le document atteindra les destinataires une fois qu'il ser
msgid "This is the claim that this organisation was initially created with. Any feature flag changes to this claim will be backported into this organisation."
msgstr "Il s'agit de la réclamation avec laquelle cette organisation a initialement été créée. Tout changement de drapeau de fonctionnalité à cette revendication sera rétroporté dans cette organisation."
#: apps/remix/app/routes/_unauthenticated+/team.verify.transfer.$token.tsx
msgid "This link is invalid or has expired."
msgstr "Ce lien est invalide ou a expiré."
#: apps/remix/app/routes/_unauthenticated+/team.verify.email.$token.tsx
msgid "This link is invalid or has expired. Please contact your team to resend a verification."
msgstr "Ce lien est invalide ou a expiré. Veuillez contacter votre équipe pour renvoyer une vérification."
@@ -6858,6 +6907,10 @@ msgstr "Cela sera envoyé au propriétaire du document une fois que le document
msgid "This will ONLY backport feature flags which are set to true, anything disabled in the initial claim will not be backported"
msgstr "Cela ne fera que rétroporter les drapeaux de fonctionnalité qui sont activés, tout ce qui est désactivé dans la réclamation initiale ne sera pas rétroporté"
#: apps/remix/app/components/dialogs/session-logout-all-dialog.tsx
msgid "This will sign you out of all other devices. You will need to sign in again on those devices to continue using your account."
msgstr ""
#: apps/remix/app/components/tables/internal-audit-log-table.tsx
#: apps/remix/app/components/tables/document-logs-table.tsx
msgid "Time"
@@ -7144,6 +7197,9 @@ msgstr "Non complet"
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
msgid "Unknown"
msgstr "Inconnu"
@@ -7497,6 +7553,11 @@ msgstr "Voir tous les documents associés"
msgid "View all security activity related to your account."
msgstr "Voir toute l'activité de sécurité liée à votre compte."
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
#: apps/remix/app/routes/_authenticated+/settings+/security._index.tsx
msgid "View and manage all active sessions for your account."
msgstr ""
#: apps/remix/app/components/forms/2fa/view-recovery-codes-dialog.tsx
msgid "View Codes"
msgstr "Voir les codes"
@@ -8563,4 +8624,3 @@ msgstr "Votre token a été créé avec succès ! Assurez-vous de le copier car
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.tokens.tsx
msgid "Your tokens will be shown here once you create them."
msgstr "Vos tokens seront affichés ici une fois que vous les aurez créés."
+87 -21
View File
@@ -752,6 +752,11 @@ msgstr "Azioni"
msgid "Active"
msgstr "Attivo"
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
#: apps/remix/app/routes/_authenticated+/settings+/security._index.tsx
msgid "Active sessions"
msgstr ""
#: apps/remix/app/routes/_authenticated+/admin+/stats.tsx
msgid "Active Subscriptions"
msgstr "Abbonamenti attivi"
@@ -817,13 +822,13 @@ msgstr "Aggiungi campi"
msgid "Add group roles"
msgstr "Aggiungi ruoli di gruppo"
#: apps/remix/app/components/dialogs/team-group-create-dialog.tsx
#: apps/remix/app/components/dialogs/team-group-create-dialog.tsx
msgid "Add groups"
msgstr "Aggiungi gruppi"
#: apps/remix/app/components/dialogs/team-member-create-dialog.tsx
#: apps/remix/app/components/dialogs/team-member-create-dialog.tsx
#: apps/remix/app/components/dialogs/team-group-create-dialog.tsx
msgid "Add members"
msgstr "Aggiungi membri"
@@ -1614,6 +1619,7 @@ msgstr "Può preparare"
#: apps/remix/app/components/dialogs/team-delete-dialog.tsx
#: apps/remix/app/components/dialogs/team-create-dialog.tsx
#: apps/remix/app/components/dialogs/team-create-dialog.tsx
#: apps/remix/app/components/dialogs/session-logout-all-dialog.tsx
#: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx
#: apps/remix/app/components/dialogs/passkey-create-dialog.tsx
#: apps/remix/app/components/dialogs/organisation-member-update-dialog.tsx
@@ -2221,6 +2227,7 @@ msgstr "Crea il tuo account e inizia a utilizzare firme digitali all'avanguardia
msgid "Create your account and start using state-of-the-art document signing. Open and beautiful signing is within your grasp."
msgstr "Crea il tuo account e inizia a utilizzare firme digitali all'avanguardia. Una firma aperta e bella è a tua portata."
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
#: apps/remix/app/routes/_authenticated+/admin+/documents._index.tsx
#: apps/remix/app/components/tables/templates-table.tsx
#: apps/remix/app/components/tables/settings-security-passkey-table.tsx
@@ -2260,6 +2267,10 @@ msgstr "Creato il {0}"
msgid "CSV Structure"
msgstr "Struttura CSV"
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
msgid "Current"
msgstr ""
#: apps/remix/app/components/forms/password.tsx
msgid "Current Password"
msgstr "Password attuale"
@@ -2453,6 +2464,7 @@ msgid "Details"
msgstr "Dettagli"
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
#: apps/remix/app/components/tables/settings-security-activity-table.tsx
msgid "Device"
msgstr "Dispositivo"
@@ -3179,6 +3191,7 @@ msgstr "Impresa"
#: apps/remix/app/routes/embed+/v1+/authoring+/template.edit.$id.tsx
#: apps/remix/app/routes/embed+/v1+/authoring+/document.edit.$id.tsx
#: apps/remix/app/routes/embed+/v1+/authoring+/document.edit.$id.tsx
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
#: apps/remix/app/routes/_authenticated+/admin+/users.$id.tsx
#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx
#: apps/remix/app/routes/_authenticated+/admin+/documents.$id.tsx
@@ -3221,6 +3234,7 @@ msgstr "Impresa"
#: apps/remix/app/components/dialogs/template-move-to-folder-dialog.tsx
#: apps/remix/app/components/dialogs/template-move-to-folder-dialog.tsx
#: apps/remix/app/components/dialogs/template-duplicate-dialog.tsx
#: apps/remix/app/components/dialogs/session-logout-all-dialog.tsx
#: apps/remix/app/components/dialogs/document-move-to-folder-dialog.tsx
#: apps/remix/app/components/dialogs/document-move-to-folder-dialog.tsx
#: apps/remix/app/components/dialogs/admin-user-enable-dialog.tsx
@@ -3286,10 +3300,18 @@ msgstr "Caricamento documento fallito."
msgid "Failed to reseal document"
msgstr "Fallito il risigillo del documento"
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
msgid "Failed to revoke session"
msgstr ""
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx
msgid "Failed to save settings."
msgstr "Impossibile salvare le impostazioni."
#: apps/remix/app/components/dialogs/session-logout-all-dialog.tsx
msgid "Failed to sign out all sessions"
msgstr ""
#: apps/remix/app/routes/embed+/v1+/authoring+/document.edit.$id.tsx
msgid "Failed to update document"
msgstr "Aggiornamento documento fallito"
@@ -3747,7 +3769,6 @@ msgstr "Codice non valido. Riprova."
msgid "Invalid email"
msgstr "Email non valida"
#: apps/remix/app/routes/_unauthenticated+/team.verify.transfer.$token.tsx
#: apps/remix/app/routes/_unauthenticated+/team.verify.email.$token.tsx
msgid "Invalid link"
msgstr "Link non valido"
@@ -3811,6 +3832,7 @@ msgid "Invoice"
msgstr "Fattura"
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
#: apps/remix/app/components/tables/internal-audit-log-table.tsx
msgid "IP Address"
msgstr "Indirizzo IP"
@@ -3881,6 +3903,10 @@ msgstr "Ultimi 30 giorni"
msgid "Last 7 days"
msgstr "Ultimi 7 giorni"
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
msgid "Last Active"
msgstr ""
#: apps/remix/app/components/general/template/template-page-view-information.tsx
#: apps/remix/app/components/general/document/document-page-view-information.tsx
msgid "Last modified"
@@ -4030,6 +4056,10 @@ msgstr "Gestisci chiavi di accesso"
msgid "Manage permissions and access controls"
msgstr "Gestisci le autorizzazioni e i controlli di accesso"
#: apps/remix/app/routes/_authenticated+/settings+/security._index.tsx
msgid "Manage sessions"
msgstr ""
#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx
#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx
msgid "Manage subscription"
@@ -5305,7 +5335,6 @@ msgstr "Conservazione dei documenti"
msgid "Retry"
msgstr "Riprova"
#: apps/remix/app/routes/_unauthenticated+/team.verify.transfer.$token.tsx
#: apps/remix/app/routes/_unauthenticated+/team.verify.email.$token.tsx
#: apps/remix/app/routes/_unauthenticated+/organisation.invite.$token.tsx
#: apps/remix/app/routes/_unauthenticated+/organisation.decline.$token.tsx
@@ -5321,6 +5350,7 @@ msgstr "Torna alla home"
msgid "Return to sign in"
msgstr "Torna a accedere"
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
#: apps/remix/app/components/general/teams/team-email-usage.tsx
msgid "Revoke"
msgstr "Revoca"
@@ -5329,6 +5359,12 @@ msgstr "Revoca"
msgid "Revoke access"
msgstr "Revoca l'accesso"
#: apps/remix/app/components/dialogs/session-logout-all-dialog.tsx
#: apps/remix/app/components/dialogs/session-logout-all-dialog.tsx
#: apps/remix/app/components/dialogs/session-logout-all-dialog.tsx
msgid "Revoke all sessions"
msgstr ""
#: apps/remix/app/components/tables/user-organisations-table.tsx
#: apps/remix/app/components/tables/team-members-table.tsx
#: apps/remix/app/components/tables/team-groups-table.tsx
@@ -5481,6 +5517,10 @@ msgstr "Seleziona opzione predefinita"
msgid "Select groups"
msgstr "Seleziona gruppi"
#: apps/remix/app/components/dialogs/team-group-create-dialog.tsx
msgid "Select groups of members to add to the team."
msgstr ""
#: apps/remix/app/components/dialogs/team-group-create-dialog.tsx
msgid "Select groups to add to this team"
msgstr "Seleziona i gruppi da aggiungere a questo team"
@@ -5492,7 +5532,6 @@ msgid "Select members"
msgstr "Seleziona membri"
#: apps/remix/app/components/dialogs/team-member-create-dialog.tsx
#: apps/remix/app/components/dialogs/team-group-create-dialog.tsx
msgid "Select members or groups of members to add to the team."
msgstr "Seleziona membri o gruppi di membri da aggiungere al team."
@@ -5602,6 +5641,14 @@ msgstr "Invio..."
msgid "Sent"
msgstr "Inviato"
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
msgid "Session revoked"
msgstr ""
#: apps/remix/app/components/dialogs/session-logout-all-dialog.tsx
msgid "Sessions have been revoked"
msgstr ""
#: apps/remix/app/components/general/claim-account.tsx
msgid "Set a password"
msgstr "Imposta una password"
@@ -6476,9 +6523,11 @@ msgid "The following team has been deleted. You will no longer be able to access
msgstr "Il seguente team è stato eliminato. Non potrai più accedere a questo team e ai suoi documenti"
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.groups.$id.tsx
msgid "The organisation group you are looking for may have been removed, renamed or may have never\n"
msgid ""
"The organisation group you are looking for may have been removed, renamed or may have never\n"
" existed."
msgstr "Il gruppo organizzativo che cerchi potrebbe essere stato rimosso, rinominato o potrebbe non essere mai\n"
msgstr ""
"Il gruppo organizzativo che cerchi potrebbe essere stato rimosso, rinominato o potrebbe non essere mai\n"
" esistito."
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.groups.$id.tsx
@@ -6486,15 +6535,19 @@ msgid "The organisation role that will be applied to all members in this group."
msgstr "Il ruolo organizzativo che verrà applicato a tutti i membri in questo gruppo."
#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx
msgid "The organisation you are looking for may have been removed, renamed or may have never\n"
msgid ""
"The organisation you are looking for may have been removed, renamed or may have never\n"
" existed."
msgstr "L'organizzazione che cerchi potrebbe essere stata rimossa, rinominata o potrebbe non essere mai\n"
msgstr ""
"L'organizzazione che cerchi potrebbe essere stata rimossa, rinominata o potrebbe non essere mai\n"
" esistita."
#: apps/remix/app/routes/_authenticated+/_layout.tsx
msgid "The organisation you are looking for may have been removed, renamed or may have never\n"
msgid ""
"The organisation you are looking for may have been removed, renamed or may have never\n"
" existed."
msgstr "L'organizzazione che cerchi potrebbe essere stata rimossa, rinominata o potrebbe non essere mai\n"
msgstr ""
"L'organizzazione che cerchi potrebbe essere stata rimossa, rinominata o potrebbe non essere mai\n"
" esistita."
#: apps/remix/app/components/general/generic-error-layout.tsx
@@ -6577,15 +6630,19 @@ msgid "The team email <0>{teamEmail}</0> has been removed from the following tea
msgstr "L'email del team <0>{teamEmail}</0> è stata rimossa dal seguente team"
#: apps/remix/app/routes/_authenticated+/_layout.tsx
msgid "The team you are looking for may have been removed, renamed or may have never\n"
msgid ""
"The team you are looking for may have been removed, renamed or may have never\n"
" existed."
msgstr "Il team che cerchi potrebbe essere stato rimosso, rinominato o potrebbe non essere mai\n"
msgstr ""
"Il team che cerchi potrebbe essere stato rimosso, rinominato o potrebbe non essere mai\n"
" esistito."
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/_layout.tsx
msgid "The team you are looking for may have been removed, renamed or may have never\n"
msgid ""
"The team you are looking for may have been removed, renamed or may have never\n"
" existed."
msgstr "La squadra che stai cercando potrebbe essere stata rimossa, rinominata o potrebbe non essere mai\n"
msgstr ""
"La squadra che stai cercando potrebbe essere stata rimossa, rinominata o potrebbe non essere mai\n"
" esistita."
#: apps/remix/app/components/dialogs/template-move-to-folder-dialog.tsx
@@ -6622,9 +6679,11 @@ msgid "The URL for Documenso to send webhook events to."
msgstr "L'URL per Documenso per inviare eventi webhook."
#: apps/remix/app/routes/_authenticated+/admin+/users.$id.tsx
msgid "The user you are looking for may have been removed, renamed or may have never\n"
msgid ""
"The user you are looking for may have been removed, renamed or may have never\n"
" existed."
msgstr "L'utente che cerchi potrebbe essere stato rimosso, rinominato o potrebbe non essere mai\n"
msgstr ""
"L'utente che cerchi potrebbe essere stato rimosso, rinominato o potrebbe non essere mai\n"
" esistito."
#: apps/remix/app/components/dialogs/webhook-delete-dialog.tsx
@@ -6790,10 +6849,6 @@ msgstr "È così che il documento raggiungerà i destinatari una volta pronto pe
msgid "This is the claim that this organisation was initially created with. Any feature flag changes to this claim will be backported into this organisation."
msgstr "Questo è il reclamo con cui questa organizzazione è stata inizialmente creata. Qualunque cambiamento agli indicatori delle funzionalità di questo reclamo sarà retroportato in questa organizzazione."
#: apps/remix/app/routes/_unauthenticated+/team.verify.transfer.$token.tsx
msgid "This link is invalid or has expired."
msgstr "Questo link non è valido o è scaduto."
#: apps/remix/app/routes/_unauthenticated+/team.verify.email.$token.tsx
msgid "This link is invalid or has expired. Please contact your team to resend a verification."
msgstr "Questo link è invalido o è scaduto. Si prega di contattare il tuo team per inviare nuovamente una verifica."
@@ -6864,6 +6919,10 @@ msgstr "Questo sarà inviato al proprietario del documento una volta che il docu
msgid "This will ONLY backport feature flags which are set to true, anything disabled in the initial claim will not be backported"
msgstr "Questo farà SOLO il retroporting degli indicatori delle funzionalità impostati su vero, qualsiasi cosa disabilitata nel reclamo iniziale non sarà retroportata"
#: apps/remix/app/components/dialogs/session-logout-all-dialog.tsx
msgid "This will sign you out of all other devices. You will need to sign in again on those devices to continue using your account."
msgstr ""
#: apps/remix/app/components/tables/internal-audit-log-table.tsx
#: apps/remix/app/components/tables/document-logs-table.tsx
msgid "Time"
@@ -7150,6 +7209,9 @@ msgstr "Incompleto"
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
msgid "Unknown"
msgstr "Sconosciuto"
@@ -7503,6 +7565,11 @@ msgstr "Visualizza tutti i documenti correlati"
msgid "View all security activity related to your account."
msgstr "Visualizza tutte le attività di sicurezza relative al tuo account."
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
#: apps/remix/app/routes/_authenticated+/settings+/security._index.tsx
msgid "View and manage all active sessions for your account."
msgstr ""
#: apps/remix/app/components/forms/2fa/view-recovery-codes-dialog.tsx
msgid "View Codes"
msgstr "Visualizza Codici"
@@ -8569,4 +8636,3 @@ msgstr "Il tuo token è stato creato con successo! Assicurati di copiarlo perch
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.tokens.tsx
msgid "Your tokens will be shown here once you create them."
msgstr "I tuoi token verranno mostrati qui una volta creati."
+77 -17
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: pl\n"
"Project-Id-Version: documenso-app\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2025-06-10 02:27\n"
"PO-Revision-Date: 2025-06-10 12:05\n"
"Last-Translator: \n"
"Language-Team: Polish\n"
"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n"
@@ -752,6 +752,11 @@ msgstr "Akcje"
msgid "Active"
msgstr "Aktywne"
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
#: apps/remix/app/routes/_authenticated+/settings+/security._index.tsx
msgid "Active sessions"
msgstr ""
#: apps/remix/app/routes/_authenticated+/admin+/stats.tsx
msgid "Active Subscriptions"
msgstr "Aktywne subskrypcje"
@@ -817,13 +822,13 @@ msgstr "Dodaj pola"
msgid "Add group roles"
msgstr "Dodaj role grupowe"
#: apps/remix/app/components/dialogs/team-group-create-dialog.tsx
#: apps/remix/app/components/dialogs/team-group-create-dialog.tsx
msgid "Add groups"
msgstr "Dodaj grupy"
#: apps/remix/app/components/dialogs/team-member-create-dialog.tsx
#: apps/remix/app/components/dialogs/team-member-create-dialog.tsx
#: apps/remix/app/components/dialogs/team-group-create-dialog.tsx
msgid "Add members"
msgstr "Dodaj członków"
@@ -1614,6 +1619,7 @@ msgstr "Może przygotować"
#: apps/remix/app/components/dialogs/team-delete-dialog.tsx
#: apps/remix/app/components/dialogs/team-create-dialog.tsx
#: apps/remix/app/components/dialogs/team-create-dialog.tsx
#: apps/remix/app/components/dialogs/session-logout-all-dialog.tsx
#: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx
#: apps/remix/app/components/dialogs/passkey-create-dialog.tsx
#: apps/remix/app/components/dialogs/organisation-member-update-dialog.tsx
@@ -1795,7 +1801,7 @@ msgstr "Porównaj szczegóły wszystkich planów i funkcji"
#: apps/remix/app/components/embed/embed-direct-template-client-page.tsx
#: apps/remix/app/components/embed/multisign/multi-sign-document-signing-view.tsx
msgid "Complete"
msgstr "Zakończono"
msgstr "Zakończ"
#: apps/remix/app/components/general/document-signing/document-signing-complete-dialog.tsx
msgid "Complete Approval"
@@ -2221,6 +2227,7 @@ msgstr "Utwórz swoje konto i zacznij korzystać z nowoczesnego podpisywania dok
msgid "Create your account and start using state-of-the-art document signing. Open and beautiful signing is within your grasp."
msgstr "Utwórz swoje konto i zacznij korzystać z nowoczesnego podpisywania dokumentów. Otwarty i piękny podpis jest w zasięgu ręki."
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
#: apps/remix/app/routes/_authenticated+/admin+/documents._index.tsx
#: apps/remix/app/components/tables/templates-table.tsx
#: apps/remix/app/components/tables/settings-security-passkey-table.tsx
@@ -2260,6 +2267,10 @@ msgstr "Utworzono {0}"
msgid "CSV Structure"
msgstr "Struktura CSV"
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
msgid "Current"
msgstr ""
#: apps/remix/app/components/forms/password.tsx
msgid "Current Password"
msgstr "Obecne hasło"
@@ -2453,6 +2464,7 @@ msgid "Details"
msgstr "Szczegóły"
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
#: apps/remix/app/components/tables/settings-security-activity-table.tsx
msgid "Device"
msgstr "Urządzenie"
@@ -3179,6 +3191,7 @@ msgstr "Enterprise"
#: apps/remix/app/routes/embed+/v1+/authoring+/template.edit.$id.tsx
#: apps/remix/app/routes/embed+/v1+/authoring+/document.edit.$id.tsx
#: apps/remix/app/routes/embed+/v1+/authoring+/document.edit.$id.tsx
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
#: apps/remix/app/routes/_authenticated+/admin+/users.$id.tsx
#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx
#: apps/remix/app/routes/_authenticated+/admin+/documents.$id.tsx
@@ -3221,6 +3234,7 @@ msgstr "Enterprise"
#: apps/remix/app/components/dialogs/template-move-to-folder-dialog.tsx
#: apps/remix/app/components/dialogs/template-move-to-folder-dialog.tsx
#: apps/remix/app/components/dialogs/template-duplicate-dialog.tsx
#: apps/remix/app/components/dialogs/session-logout-all-dialog.tsx
#: apps/remix/app/components/dialogs/document-move-to-folder-dialog.tsx
#: apps/remix/app/components/dialogs/document-move-to-folder-dialog.tsx
#: apps/remix/app/components/dialogs/admin-user-enable-dialog.tsx
@@ -3286,10 +3300,18 @@ msgstr "Nie udało się załadować dokumentu"
msgid "Failed to reseal document"
msgstr "Nie udało się ponownie zaplombować dokumentu"
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
msgid "Failed to revoke session"
msgstr ""
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx
msgid "Failed to save settings."
msgstr "Nie udało się zapisać ustawień."
#: apps/remix/app/components/dialogs/session-logout-all-dialog.tsx
msgid "Failed to sign out all sessions"
msgstr ""
#: apps/remix/app/routes/embed+/v1+/authoring+/document.edit.$id.tsx
msgid "Failed to update document"
msgstr "Nie udało się aktualizować dokumentu"
@@ -3747,7 +3769,6 @@ msgstr "Nieprawidłowy kod. Proszę spróbuj ponownie."
msgid "Invalid email"
msgstr "Nieprawidłowy email"
#: apps/remix/app/routes/_unauthenticated+/team.verify.transfer.$token.tsx
#: apps/remix/app/routes/_unauthenticated+/team.verify.email.$token.tsx
msgid "Invalid link"
msgstr "Nieprawidłowy link"
@@ -3811,6 +3832,7 @@ msgid "Invoice"
msgstr "Faktura"
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
#: apps/remix/app/components/tables/internal-audit-log-table.tsx
msgid "IP Address"
msgstr "Adres IP"
@@ -3881,6 +3903,10 @@ msgstr "Ostatnie 30 dni"
msgid "Last 7 days"
msgstr "Ostatnie 7 dni"
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
msgid "Last Active"
msgstr ""
#: apps/remix/app/components/general/template/template-page-view-information.tsx
#: apps/remix/app/components/general/document/document-page-view-information.tsx
msgid "Last modified"
@@ -4030,6 +4056,10 @@ msgstr "Zarządzaj kluczami dostępu"
msgid "Manage permissions and access controls"
msgstr "Zarządzaj uprawnieniami i kontrolą dostępu"
#: apps/remix/app/routes/_authenticated+/settings+/security._index.tsx
msgid "Manage sessions"
msgstr ""
#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx
#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx
msgid "Manage subscription"
@@ -5305,7 +5335,6 @@ msgstr "Przechowywanie dokumentów"
msgid "Retry"
msgstr "Spróbuj ponownie"
#: apps/remix/app/routes/_unauthenticated+/team.verify.transfer.$token.tsx
#: apps/remix/app/routes/_unauthenticated+/team.verify.email.$token.tsx
#: apps/remix/app/routes/_unauthenticated+/organisation.invite.$token.tsx
#: apps/remix/app/routes/_unauthenticated+/organisation.decline.$token.tsx
@@ -5321,6 +5350,7 @@ msgstr "Powrót do strony głównej"
msgid "Return to sign in"
msgstr "Powrót do logowania"
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
#: apps/remix/app/components/general/teams/team-email-usage.tsx
msgid "Revoke"
msgstr "Cofnij"
@@ -5329,6 +5359,12 @@ msgstr "Cofnij"
msgid "Revoke access"
msgstr "Cofnij dostęp"
#: apps/remix/app/components/dialogs/session-logout-all-dialog.tsx
#: apps/remix/app/components/dialogs/session-logout-all-dialog.tsx
#: apps/remix/app/components/dialogs/session-logout-all-dialog.tsx
msgid "Revoke all sessions"
msgstr ""
#: apps/remix/app/components/tables/user-organisations-table.tsx
#: apps/remix/app/components/tables/team-members-table.tsx
#: apps/remix/app/components/tables/team-groups-table.tsx
@@ -5481,6 +5517,10 @@ msgstr "Wybierz domyślną opcję"
msgid "Select groups"
msgstr "Wybierz grupy"
#: apps/remix/app/components/dialogs/team-group-create-dialog.tsx
msgid "Select groups of members to add to the team."
msgstr ""
#: apps/remix/app/components/dialogs/team-group-create-dialog.tsx
msgid "Select groups to add to this team"
msgstr "Wybierz grupy, które chcesz dodać do tego zespołu"
@@ -5492,7 +5532,6 @@ msgid "Select members"
msgstr "Wybierz członków"
#: apps/remix/app/components/dialogs/team-member-create-dialog.tsx
#: apps/remix/app/components/dialogs/team-group-create-dialog.tsx
msgid "Select members or groups of members to add to the team."
msgstr "Wybierz członków lub grupy członków, aby dodać do zespołu."
@@ -5602,6 +5641,14 @@ msgstr "Wysyłanie..."
msgid "Sent"
msgstr "Wysłano"
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
msgid "Session revoked"
msgstr ""
#: apps/remix/app/components/dialogs/session-logout-all-dialog.tsx
msgid "Sessions have been revoked"
msgstr ""
#: apps/remix/app/components/general/claim-account.tsx
msgid "Set a password"
msgstr "Ustaw hasło"
@@ -6476,7 +6523,8 @@ msgid "The following team has been deleted. You will no longer be able to access
msgstr "Poniższy zespół został usunięty. Nie będziesz mógł już uzyskać dostępu do tego zespołu i jego dokumentów."
#: apps/remix/app/routes/_authenticated+/o.$orgUrl.settings.groups.$id.tsx
msgid "The organisation group you are looking for may have been removed, renamed or may have never\n"
msgid ""
"The organisation group you are looking for may have been removed, renamed or may have never\n"
" existed."
msgstr "Grupa organizacji, której szukasz, mogła zostać usunięta, zmieniona nazwa lub mogła nigdy nie istnieć."
@@ -6485,12 +6533,14 @@ msgid "The organisation role that will be applied to all members in this group."
msgstr "Rola organizacji, która zostanie zastosowana do wszystkich członków tej grupy."
#: apps/remix/app/routes/_authenticated+/admin+/organisations.$id.tsx
msgid "The organisation you are looking for may have been removed, renamed or may have never\n"
msgid ""
"The organisation you are looking for may have been removed, renamed or may have never\n"
" existed."
msgstr "Organizacja, której szukasz, mogła zostać usunięta, zmieniona nazwa lub mogła nigdy nie istnieć."
#: apps/remix/app/routes/_authenticated+/_layout.tsx
msgid "The organisation you are looking for may have been removed, renamed or may have never\n"
msgid ""
"The organisation you are looking for may have been removed, renamed or may have never\n"
" existed."
msgstr "Organizacja, której szukasz, mogła zostać usunięta, zmieniona nazwa lub mogła nigdy nie istnieć."
@@ -6574,12 +6624,14 @@ msgid "The team email <0>{teamEmail}</0> has been removed from the following tea
msgstr "Email zespołowy <0>{teamEmail}</0> został usunięty z następującego zespołu"
#: apps/remix/app/routes/_authenticated+/_layout.tsx
msgid "The team you are looking for may have been removed, renamed or may have never\n"
msgid ""
"The team you are looking for may have been removed, renamed or may have never\n"
" existed."
msgstr "Zespół, którego szukasz, mógł zostać usunięty, zmienić nazwę lub mógł nigdy nie istnieć."
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/_layout.tsx
msgid "The team you are looking for may have been removed, renamed or may have never\n"
msgid ""
"The team you are looking for may have been removed, renamed or may have never\n"
" existed."
msgstr "Zespół, którego szukasz, mógł zostać usunięty, zmieniony na, albo nigdy nie istniał."
@@ -6617,7 +6669,8 @@ msgid "The URL for Documenso to send webhook events to."
msgstr "URL dla Documenso do wysyłania zdarzeń webhook."
#: apps/remix/app/routes/_authenticated+/admin+/users.$id.tsx
msgid "The user you are looking for may have been removed, renamed or may have never\n"
msgid ""
"The user you are looking for may have been removed, renamed or may have never\n"
" existed."
msgstr "Użytkownik, którego szukasz, mógł zostać usunięty, zmieniony nazwę lub mógł nigdy nie istnieć."
@@ -6784,10 +6837,6 @@ msgstr "W ten sposób dokument dotrze do odbiorców, gdy tylko dokument będzie
msgid "This is the claim that this organisation was initially created with. Any feature flag changes to this claim will be backported into this organisation."
msgstr "To jest roszczenie, z którym pierwotnie została utworzona ta organizacja. Wszelkie zmiany flag funkcji w tym roszczeniu zostaną przeniesione do tej organizacji."
#: apps/remix/app/routes/_unauthenticated+/team.verify.transfer.$token.tsx
msgid "This link is invalid or has expired."
msgstr "Ten link jest nieprawidłowy lub wygasł."
#: apps/remix/app/routes/_unauthenticated+/team.verify.email.$token.tsx
msgid "This link is invalid or has expired. Please contact your team to resend a verification."
msgstr "Ten link jest nieprawidłowy lub wygasł. Proszę skontaktować się ze swoim zespołem, aby ponownie wysłać weryfikację."
@@ -6858,6 +6907,10 @@ msgstr "To zostanie wysłane do właściciela dokumentu, gdy dokument zostanie w
msgid "This will ONLY backport feature flags which are set to true, anything disabled in the initial claim will not be backported"
msgstr "To będzie TYLKO przenoś funkcje flag, które są ustawione na true, wszystko, co wyłączone w początkowym roszczeniu, nie zostanie przeniesione"
#: apps/remix/app/components/dialogs/session-logout-all-dialog.tsx
msgid "This will sign you out of all other devices. You will need to sign in again on those devices to continue using your account."
msgstr ""
#: apps/remix/app/components/tables/internal-audit-log-table.tsx
#: apps/remix/app/components/tables/document-logs-table.tsx
msgid "Time"
@@ -7144,6 +7197,9 @@ msgstr "Niezakończony"
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
#: apps/remix/app/routes/_internal+/[__htmltopdf]+/certificate.tsx
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
msgid "Unknown"
msgstr "Nieznany"
@@ -7497,6 +7553,11 @@ msgstr "Zobacz wszystkie powiązane dokumenty"
msgid "View all security activity related to your account."
msgstr "Wyświetl wszystkie aktywności związane z bezpieczeństwem twojego konta."
#: apps/remix/app/routes/_authenticated+/settings+/security.sessions.tsx
#: apps/remix/app/routes/_authenticated+/settings+/security._index.tsx
msgid "View and manage all active sessions for your account."
msgstr ""
#: apps/remix/app/components/forms/2fa/view-recovery-codes-dialog.tsx
msgid "View Codes"
msgstr "Wyświetl kody"
@@ -8563,4 +8624,3 @@ msgstr "Twój token został pomyślnie utworzony! Upewnij się, że go skopiujes
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/settings.tokens.tsx
msgid "Your tokens will be shown here once you create them."
msgstr "Twoje tokeny będą tutaj wyświetlane po ich utworzeniu."
@@ -0,0 +1,2 @@
-- AlterEnum
ALTER TYPE "UserSecurityAuditLogType" ADD VALUE 'SESSION_REVOKED';
+1
View File
@@ -97,6 +97,7 @@ enum UserSecurityAuditLogType {
PASSKEY_UPDATED
PASSWORD_RESET
PASSWORD_UPDATE
SESSION_REVOKED
SIGN_OUT
SIGN_IN
SIGN_IN_FAIL
@@ -1,31 +1,15 @@
import { createApiToken } from '@documenso/lib/server-only/public-api/create-api-token';
import { deleteTokenById } from '@documenso/lib/server-only/public-api/delete-api-token-by-id';
import { getApiTokenById } from '@documenso/lib/server-only/public-api/get-api-token-by-id';
import { getApiTokens } from '@documenso/lib/server-only/public-api/get-api-tokens';
import { authenticatedProcedure, router } from '../trpc';
import {
ZCreateTokenMutationSchema,
ZDeleteTokenByIdMutationSchema,
ZGetApiTokenByIdQuerySchema,
} from './schema';
import { ZCreateTokenMutationSchema, ZDeleteTokenByIdMutationSchema } from './schema';
export const apiTokenRouter = router({
getTokens: authenticatedProcedure.query(async ({ ctx }) => {
return await getApiTokens({ userId: ctx.user.id, teamId: ctx.teamId });
}),
getTokenById: authenticatedProcedure
.input(ZGetApiTokenByIdQuerySchema)
.query(async ({ input, ctx }) => {
const { id } = input;
return await getApiTokenById({
id,
userId: ctx.user.id,
});
}),
createToken: authenticatedProcedure
.input(ZCreateTokenMutationSchema)
.mutation(async ({ input, ctx }) => {
@@ -1,11 +1,5 @@
import { z } from 'zod';
export const ZGetApiTokenByIdQuerySchema = z.object({
id: z.number().min(1),
});
export type TGetApiTokenByIdQuerySchema = z.infer<typeof ZGetApiTokenByIdQuerySchema>;
export const ZCreateTokenMutationSchema = z.object({
teamId: z.number(),
tokenName: z.string().min(3, { message: 'The token name should be 3 characters or longer' }),
@@ -166,7 +166,6 @@ export const AddFieldsFormPartial = ({
const [selectedField, setSelectedField] = useState<FieldType | null>(null);
const [selectedSigner, setSelectedSigner] = useState<Recipient | null>(null);
const [showRecipientsSelector, setShowRecipientsSelector] = useState(false);
const [lastActiveField, setLastActiveField] = useState<TAddFieldsFormSchema['fields'][0] | null>(
null,
);
@@ -465,6 +464,7 @@ export const AddFieldsFormPartial = ({
append({
...copiedField,
nativeId: undefined,
formId: nanoid(12),
signerEmail: selectedSigner?.email ?? copiedField.signerEmail,
pageX: copiedField.pageX + 3,
@@ -661,6 +661,8 @@ export const AddFieldsFormPartial = ({
passive={isFieldWithinBounds && !!selectedField}
onFocus={() => setLastActiveField(field)}
onBlur={() => setLastActiveField(null)}
onMouseEnter={() => setLastActiveField(field)}
onMouseLeave={() => setLastActiveField(null)}
onResize={(options) => onFieldResize(options, index)}
onMove={(options) => onFieldMove(options, index)}
onRemove={() => remove(index)}
@@ -6,6 +6,7 @@ import { FieldType } from '@prisma/client';
import { CopyPlus, Settings2, SquareStack, Trash } from 'lucide-react';
import { createPortal } from 'react-dom';
import { Rnd } from 'react-rnd';
import { useSearchParams } from 'react-router';
import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
import type { TFieldMetaSchema } from '@documenso/lib/types/field-meta';
@@ -35,6 +36,8 @@ export type FieldItemProps = {
onAdvancedSettings?: () => void;
onFocus?: () => void;
onBlur?: () => void;
onMouseEnter?: () => void;
onMouseLeave?: () => void;
recipientIndex?: number;
hasErrors?: boolean;
active?: boolean;
@@ -69,6 +72,7 @@ export const FieldItem = ({
onFieldDeactivate,
}: FieldItemProps) => {
const { _ } = useLingui();
const [searchParams] = useSearchParams();
const [coords, setCoords] = useState({
pageX: 0,
@@ -81,6 +85,8 @@ export const FieldItem = ({
const signerStyles = useRecipientColors(recipientIndex);
const isDevMode = searchParams.get('devmode') === 'true';
const advancedField = [
'NUMBER',
'RADIO',
@@ -233,6 +239,8 @@ export const FieldItem = ({
bounds={`${PDF_VIEWER_PAGE_SELECTOR}[data-page-number="${field.pageNumber}"]`}
onDragStart={() => onFieldActivate?.()}
onResizeStart={() => onFieldActivate?.()}
onMouseEnter={() => onFocus?.()}
onMouseLeave={() => onBlur?.()}
enableResizing={!fixedSize}
resizeHandleStyles={{
bottom: { bottom: -8, cursor: 'ns-resize' },
@@ -303,6 +311,12 @@ export const FieldItem = ({
(field.signerEmail?.charAt(1)?.toUpperCase() ?? '')}
</div>
</div>
{isDevMode && (
<div className="text-muted-foreground absolute -top-6 left-0 right-0 text-center text-[10px]">
{`x: ${field.pageX.toFixed(2)}, y: ${field.pageY.toFixed(2)}`}
</div>
)}
</div>
{!disabled && settingsActive && (
@@ -209,6 +209,7 @@ export const AddTemplateFieldsFormPartial = ({
append({
...copiedField,
formId: nanoid(12),
nativeId: undefined,
signerEmail: selectedSigner?.email ?? copiedField.signerEmail,
signerId: selectedSigner?.id ?? copiedField.signerId,
signerToken: selectedSigner?.token ?? copiedField.signerToken,