mirror of
https://github.com/documenso/documenso.git
synced 2026-06-22 04:12:06 +10:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1bc090cb83 | |||
| fd94a403af | |||
| 158b36a9b7 | |||
| fabd69bd62 | |||
| c976e747e3 | |||
| 34f512bd55 | |||
| db913e95b6 | |||
| bb3e9583e4 | |||
| 5bc73a7471 | |||
| 06d7849146 | |||
| cef7987a72 | |||
| cf6f6bcea0 | |||
| 2f27304750 | |||
| 912530ca17 | |||
| a995961c4e |
@@ -0,0 +1,186 @@
|
||||
---
|
||||
date: 2026-01-14
|
||||
title: Simplewebauthn V13 Upgrade
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
Upgrade SimpleWebAuthn packages from v9.x to v13.x to address the deprecation of `@simplewebauthn/types` and take advantage of new features and improvements.
|
||||
|
||||
## Current State
|
||||
|
||||
The codebase currently uses:
|
||||
- `@simplewebauthn/browser@9.x`
|
||||
- `@simplewebauthn/server@9.x`
|
||||
- `@simplewebauthn/types@9.x`
|
||||
|
||||
## Breaking Changes Summary (v9 → v13)
|
||||
|
||||
### v10.0.0 Breaking Changes
|
||||
1. **Minimum Node version raised to Node v20**
|
||||
2. **`generateRegistrationOptions()` now expects `Base64URLString` for `excludeCredentials` IDs** (no more `type: 'public-key'` needed)
|
||||
3. **`generateAuthenticationOptions()` now expects `Base64URLString` for `allowCredentials` IDs**
|
||||
4. **`credentialID` returned from verification methods is now `Base64URLString`** instead of `Uint8Array`
|
||||
5. **`AuthenticatorDevice.credentialID` is now `Base64URLString`**
|
||||
6. **`rpID` is now required when calling `generateAuthenticationOptions()`**
|
||||
7. **`generateRegistrationOptions()` will generate random user IDs** if not provided
|
||||
8. **`user.id` is treated as base64url string in `startRegistration()`**
|
||||
9. **`userHandle` is treated as base64url string in `startAuthentication()`**
|
||||
|
||||
### v11.0.0 Breaking Changes
|
||||
1. **Positional arguments in `startRegistration()` and `startAuthentication()` replaced by object**
|
||||
- Before: `startRegistration(options)`
|
||||
- After: `startRegistration({ optionsJSON: options })`
|
||||
- Before: `startAuthentication(options)`
|
||||
- After: `startAuthentication({ optionsJSON: options })`
|
||||
2. **`AuthenticatorDevice` type renamed to `WebAuthnCredential`**
|
||||
- `credentialID` → `credential.id`
|
||||
- `credentialPublicKey` → `credential.publicKey`
|
||||
3. **`verifyRegistrationResponse()` returns `registrationInfo.credential` instead of individual properties**
|
||||
- `credentialID` → `credential.id`
|
||||
- `credentialPublicKey` → `credential.publicKey`
|
||||
- `counter` → `credential.counter`
|
||||
- `transports` are now in `credential.transports`
|
||||
4. **`verifyAuthenticationResponse()` uses `credential` argument instead of `authenticator`**
|
||||
|
||||
### v13.0.0 Breaking Changes
|
||||
1. **`@simplewebauthn/types` package is retired**
|
||||
- Types are now exported from `@simplewebauthn/browser` and `@simplewebauthn/server`
|
||||
- Import types from `@simplewebauthn/server` instead
|
||||
|
||||
## Files to Update
|
||||
|
||||
### Package Changes
|
||||
1. Remove `@simplewebauthn/types` dependency
|
||||
2. Update `@simplewebauthn/browser` to `^13.2.2`
|
||||
3. Update `@simplewebauthn/server` to `^13.2.2`
|
||||
|
||||
### Server-side Files
|
||||
|
||||
#### 1. `packages/lib/server-only/auth/create-passkey-registration-options.ts`
|
||||
- Change import from `@simplewebauthn/types` to `@simplewebauthn/server`
|
||||
- Remove `type: 'public-key'` from `excludeCredentials` items
|
||||
- Update `userID` to use `isoUint8Array.fromUTF8String()` for proper encoding
|
||||
|
||||
#### 2. `packages/lib/server-only/auth/create-passkey-authentication-options.ts`
|
||||
- Change import from `@simplewebauthn/types` to `@simplewebauthn/server`
|
||||
- Remove `type: 'public-key'` from `allowCredentials` items
|
||||
|
||||
#### 3. `packages/lib/server-only/auth/create-passkey-signin-options.ts`
|
||||
- No changes needed (already using correct options)
|
||||
|
||||
#### 4. `packages/lib/server-only/auth/create-passkey.ts`
|
||||
- Change import from `@simplewebauthn/types` to `@simplewebauthn/server`
|
||||
- Update to use new `registrationInfo.credential` structure:
|
||||
- `credentialID` → `credential.id`
|
||||
- `credentialPublicKey` → `credential.publicKey`
|
||||
- `counter` → `credential.counter`
|
||||
- Note: `credential.id` is now a `Base64URLString`, so `Buffer.from(credentialID)` needs updating
|
||||
|
||||
#### 5. `packages/lib/server-only/document/is-recipient-authorized.ts`
|
||||
- Update `verifyAuthenticationResponse()` to use `credential` instead of `authenticator`:
|
||||
- Change `authenticator: { credentialID, credentialPublicKey, counter }` to `credential: { id, publicKey, counter }`
|
||||
- Since `credential.id` is now base64url string, convert stored `credentialId` buffer to base64url
|
||||
|
||||
#### 6. `packages/auth/server/routes/passkey.ts`
|
||||
- Update `verifyAuthenticationResponse()` to use `credential` instead of `authenticator`
|
||||
- Same changes as `is-recipient-authorized.ts`
|
||||
|
||||
#### 7. `packages/trpc/server/auth-router/create-passkey.ts`
|
||||
- Change import from `@simplewebauthn/types` to `@simplewebauthn/server`
|
||||
|
||||
### Browser-side Files
|
||||
|
||||
#### 8. `apps/remix/app/components/dialogs/passkey-create-dialog.tsx`
|
||||
- Update `startRegistration()` call:
|
||||
- Before: `startRegistration(passkeyRegistrationOptions)`
|
||||
- After: `startRegistration({ optionsJSON: passkeyRegistrationOptions })`
|
||||
|
||||
#### 9. `apps/remix/app/components/forms/signin.tsx`
|
||||
- Update `startAuthentication()` call:
|
||||
- Before: `startAuthentication(options)`
|
||||
- After: `startAuthentication({ optionsJSON: options })`
|
||||
|
||||
#### 10. `apps/remix/app/components/general/document-signing/document-signing-auth-passkey.tsx`
|
||||
- Update `startAuthentication()` call:
|
||||
- Before: `startAuthentication(options)`
|
||||
- After: `startAuthentication({ optionsJSON: options })`
|
||||
|
||||
### Database/Schema Considerations
|
||||
|
||||
The database stores `credentialId` as `Bytes`. The new API returns `credential.id` as `Base64URLString`. We need to:
|
||||
1. When **storing** a new passkey: Convert from `Base64URLString` to `Buffer`
|
||||
2. When **passing to verification**: Convert from `Buffer` to `Base64URLString`
|
||||
|
||||
Use `isoBase64URL` helper from `@simplewebauthn/server/helpers` for these conversions.
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
### Step 1: Update package.json dependencies
|
||||
```bash
|
||||
npm uninstall @simplewebauthn/types
|
||||
npm install @simplewebauthn/browser@^13.2.2 @simplewebauthn/server@^13.2.2
|
||||
```
|
||||
|
||||
### Step 2: Update type imports
|
||||
Replace all `@simplewebauthn/types` imports with `@simplewebauthn/server`
|
||||
|
||||
### Step 3: Update browser-side API calls
|
||||
- `startRegistration(options)` → `startRegistration({ optionsJSON: options })`
|
||||
- `startAuthentication(options)` → `startAuthentication({ optionsJSON: options })`
|
||||
|
||||
### Step 4: Update server-side registration
|
||||
- Update `excludeCredentials` format (remove `type: 'public-key'`)
|
||||
- Update `userID` encoding if needed
|
||||
- Update `verifyRegistrationResponse()` result handling for new `credential` structure
|
||||
|
||||
### Step 5: Update server-side authentication
|
||||
- Update `allowCredentials` format (remove `type: 'public-key'`)
|
||||
- Update `verifyAuthenticationResponse()` to use `credential` instead of `authenticator`
|
||||
- Handle `Base64URLString` for `credential.id`
|
||||
|
||||
### Step 6: Update credential storage/retrieval
|
||||
- When storing: Convert `Base64URLString` to `Buffer`
|
||||
- When reading: Convert `Buffer` to `Base64URLString`
|
||||
|
||||
### Step 7: Test passkey flows
|
||||
1. Test passkey creation
|
||||
2. Test passkey sign-in
|
||||
3. Test passkey authentication for document signing
|
||||
4. Test passkey deletion
|
||||
|
||||
## Code Examples
|
||||
|
||||
### Converting stored Buffer to Base64URLString for verification
|
||||
```typescript
|
||||
import { isoBase64URL } from '@simplewebauthn/server/helpers';
|
||||
|
||||
// When reading from database (Buffer) and passing to verification
|
||||
const credential = {
|
||||
id: isoBase64URL.fromBuffer(passkey.credentialId),
|
||||
publicKey: new Uint8Array(passkey.credentialPublicKey),
|
||||
counter: Number(passkey.counter),
|
||||
transports: passkey.transports,
|
||||
};
|
||||
```
|
||||
|
||||
### Converting Base64URLString to Buffer for storage
|
||||
```typescript
|
||||
import { isoBase64URL } from '@simplewebauthn/server/helpers';
|
||||
|
||||
// When storing from registration response
|
||||
const credentialIdBuffer = Buffer.from(
|
||||
isoBase64URL.toBuffer(registrationInfo.credential.id)
|
||||
);
|
||||
```
|
||||
|
||||
## Risks and Mitigations
|
||||
|
||||
1. **Database compatibility**: The `credentialId` is stored as `Bytes` in the database. The new API uses `Base64URLString`. We need proper conversion functions.
|
||||
- **Mitigation**: Use `isoBase64URL.fromBuffer()` and `isoBase64URL.toBuffer()` for conversions
|
||||
|
||||
2. **Existing passkeys**: Existing passkeys should continue to work as long as conversion is done correctly.
|
||||
- **Mitigation**: Test with existing passkeys after upgrade
|
||||
|
||||
3. **Browser compatibility**: v10+ requires newer browser APIs.
|
||||
- **Mitigation**: `browserSupportsWebAuthn()` already handles this check
|
||||
@@ -0,0 +1,103 @@
|
||||
# V1 API Legacy Deprecation Specification
|
||||
|
||||
## Overview
|
||||
|
||||
Mark all V1 API endpoints as legacy/deprecated in the OpenAPI documentation to signal that users should migrate to the V2 API.
|
||||
|
||||
## Decision Summary
|
||||
|
||||
| Decision | Choice |
|
||||
|----------|--------|
|
||||
| Deprecation Scope | All V1 endpoints |
|
||||
| Message Style | Generic for all endpoints |
|
||||
| Sunset Timeline | No specific date |
|
||||
| V2 API Status | V2 exists now |
|
||||
| OpenAPI Info | Update to include deprecation notice |
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### Files to Modify
|
||||
|
||||
1. `packages/api/v1/contract.ts` - Add `deprecated: true` and `description` to all endpoints
|
||||
2. `packages/api/v1/openapi.ts` - Update the OpenAPI info description
|
||||
|
||||
### Documentation Files to Modify
|
||||
|
||||
1. `apps/documentation/pages/developers/public-api/index.mdx` - Add deprecation `<Callout type="warning">` under "API V1 - Deprecated" section:
|
||||
```
|
||||
<Callout type="warning">
|
||||
V1 API is deprecated and will be removed in a future release. Please migrate to V2.
|
||||
</Callout>
|
||||
```
|
||||
|
||||
2. `apps/documentation/pages/developers/public-api/versioning.mdx` - Add callout after "current version is v2" stating V1 is deprecated:
|
||||
```
|
||||
<Callout type="warning">
|
||||
V1 API is deprecated. Please migrate to V2.
|
||||
</Callout>
|
||||
```
|
||||
|
||||
### Deprecation Message
|
||||
|
||||
**Per-endpoint message:**
|
||||
```
|
||||
Deprecated. Please migrate to the V2 API.
|
||||
```
|
||||
|
||||
**OpenAPI info description:**
|
||||
```
|
||||
[DEPRECATED] The Documenso API for retrieving, creating, updating and deleting documents. Please migrate to V2.
|
||||
```
|
||||
|
||||
### Endpoints to Update (19 total)
|
||||
|
||||
| Endpoint | Method | Path | Action |
|
||||
|----------|--------|------|--------|
|
||||
| getDocuments | GET | /api/v1/documents | Add deprecation |
|
||||
| getDocument | GET | /api/v1/documents/:id | Add deprecation |
|
||||
| downloadSignedDocument | GET | /api/v1/documents/:id/download | Add deprecation |
|
||||
| createDocument | POST | /api/v1/documents | Add deprecation |
|
||||
| createTemplate | POST | /api/v1/templates | Add deprecation |
|
||||
| deleteTemplate | DELETE | /api/v1/templates/:id | Add deprecation |
|
||||
| getTemplate | GET | /api/v1/templates/:id | Add deprecation |
|
||||
| getTemplates | GET | /api/v1/templates | Add deprecation |
|
||||
| createDocumentFromTemplate | POST | /api/v1/templates/:templateId/create-document | Update existing deprecation message |
|
||||
| generateDocumentFromTemplate | POST | /api/v1/templates/:templateId/generate-document | Add deprecation |
|
||||
| sendDocument | POST | /api/v1/documents/:id/send | Add deprecation |
|
||||
| resendDocument | POST | /api/v1/documents/:id/resend | Add deprecation |
|
||||
| deleteDocument | DELETE | /api/v1/documents/:id | Add deprecation |
|
||||
| createRecipient | POST | /api/v1/documents/:id/recipients | Add deprecation |
|
||||
| updateRecipient | PATCH | /api/v1/documents/:id/recipients/:recipientId | Add deprecation |
|
||||
| deleteRecipient | DELETE | /api/v1/documents/:id/recipients/:recipientId | Add deprecation |
|
||||
| createField | POST | /api/v1/documents/:id/fields | Add deprecation |
|
||||
| updateField | PATCH | /api/v1/documents/:id/fields/:fieldId | Add deprecation |
|
||||
| deleteField | DELETE | /api/v1/documents/:id/fields/:fieldId | Add deprecation |
|
||||
|
||||
## Changes Required
|
||||
|
||||
### contract.ts Changes
|
||||
|
||||
For each endpoint, add:
|
||||
```typescript
|
||||
deprecated: true,
|
||||
description: 'Deprecated. Please migrate to the V2 API.',
|
||||
```
|
||||
|
||||
For endpoints that already have a description, prepend the deprecation notice.
|
||||
|
||||
### openapi.ts Changes
|
||||
|
||||
Update the info block description from:
|
||||
```typescript
|
||||
description: 'The Documenso API for retrieving, creating, updating and deleting documents.',
|
||||
```
|
||||
to:
|
||||
```typescript
|
||||
description: '[DEPRECATED] The Documenso API for retrieving, creating, updating and deleting documents. Please migrate to V2.',
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- The `createDocumentFromTemplate` endpoint's existing deprecation message (pointing to `generateDocumentFromTemplate`) will be replaced with the generic V2 migration message
|
||||
- No sunset date is included - endpoints are marked deprecated but without a removal timeline
|
||||
- The V2 API is available and ready for users to migrate to
|
||||
@@ -0,0 +1,80 @@
|
||||
---
|
||||
description: Add and commit changes using conventional commits
|
||||
allowed-tools: Bash, Read, Glob, Grep
|
||||
---
|
||||
|
||||
Create a git commit for the current changes using the Conventional Commits standard.
|
||||
|
||||
## Process
|
||||
|
||||
1. **Analyze the changes** by running:
|
||||
- `git status` to see all modified/untracked files
|
||||
- `git diff` to see unstaged changes
|
||||
- `git diff --staged` to see already-staged changes
|
||||
- `git log --oneline -5` to see recent commit style
|
||||
|
||||
2. **Stage appropriate files**:
|
||||
- Stage all related changes with `git add`
|
||||
- Do NOT stage files that appear to contain secrets (.env, credentials, API keys, tokens)
|
||||
- If you detect potential secrets, warn the user and skip those files
|
||||
|
||||
3. **Determine the commit type** based on the changes:
|
||||
- `feat`: New feature or capability
|
||||
- `fix`: Bug fix
|
||||
- `docs`: Documentation only
|
||||
- `style`: Formatting, whitespace (not CSS)
|
||||
- `refactor`: Code restructuring without behavior change
|
||||
- `perf`: Performance improvement
|
||||
- `test`: Adding or updating tests
|
||||
- `build`: Build system or dependencies
|
||||
- `ci`: CI/CD configuration
|
||||
- `chore`: Maintenance tasks, tooling, config
|
||||
|
||||
NOTE: Do not use a scope for commits
|
||||
|
||||
4. **Write the commit message**:
|
||||
- **Subject line**: `<type>: <description>`
|
||||
- Use imperative mood ("add" not "added")
|
||||
- Lowercase, no period at end
|
||||
- Max 50 characters if possible, 72 hard limit
|
||||
- **Body** (if needed): Explain _why_, not _what_
|
||||
- Wrap at 72 characters
|
||||
- Separate from subject with blank line
|
||||
|
||||
## Commit Format
|
||||
|
||||
```
|
||||
<type>[scope]: <subject>
|
||||
|
||||
[optional body explaining WHY this change was made]
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
Simple change:
|
||||
|
||||
```
|
||||
fix: handle empty input in parser without throwing
|
||||
```
|
||||
|
||||
With body:
|
||||
|
||||
```
|
||||
feat: add streaming response support
|
||||
|
||||
Large responses were causing memory issues in production.
|
||||
Streaming allows processing chunks incrementally.
|
||||
```
|
||||
|
||||
## Rules
|
||||
|
||||
- NEVER commit files that may contain secrets
|
||||
- NEVER use `git commit --amend` unless the user explicitly requests it
|
||||
- NEVER use `--no-verify` to skip hooks
|
||||
- If the pre-commit hook fails, fix the issues and create a NEW commit
|
||||
- If there are no changes to commit, inform the user and stop
|
||||
- Use a HEREDOC to pass the commit message to ensure proper formatting
|
||||
|
||||
## Execute
|
||||
|
||||
Run the git commands to analyze, stage, and commit the changes now.
|
||||
@@ -0,0 +1,112 @@
|
||||
---
|
||||
description: Continue implementing a spec from a previous session
|
||||
argument-hint: <spec-file-path>
|
||||
---
|
||||
|
||||
You are continuing implementation of a specification that was started in a previous session. Work autonomously until the feature is complete and tests pass.
|
||||
|
||||
## Your Task
|
||||
|
||||
1. **Read the spec** at `$ARGUMENTS`
|
||||
2. **Read CODE_STYLE.md** for formatting conventions
|
||||
3. **Assess current state**:
|
||||
- Check git status for uncommitted changes
|
||||
- Run tests to see what's passing/failing (if E2E tests exist)
|
||||
- Review any existing implementation
|
||||
4. **Determine what remains** by comparing the spec to the current state
|
||||
5. **Plan remaining work** using TodoWrite
|
||||
6. **Continue implementing** until complete
|
||||
|
||||
## Assessing Current State
|
||||
|
||||
Run these commands to understand where the previous session left off:
|
||||
|
||||
```bash
|
||||
git status # See uncommitted changes
|
||||
git log --oneline -10 # See recent commits
|
||||
npm run typecheck -w @documenso/remix # Check for type errors
|
||||
npm run lint:fix # Check for linting issues
|
||||
```
|
||||
|
||||
Review the code that's already been written to understand:
|
||||
|
||||
- What's already implemented
|
||||
- What's partially done
|
||||
- What's not started yet
|
||||
|
||||
## Implementation Guidelines
|
||||
|
||||
### During Implementation
|
||||
|
||||
- Follow CODE_STYLE.md strictly (2-space indent, double quotes, braces always, etc.)
|
||||
- Follow workspace rules for TypeScript, React, TRPC patterns, and Remix conventions
|
||||
- Mark todos complete as you finish each task
|
||||
- Commit logical chunks of work
|
||||
|
||||
### Code Quality
|
||||
|
||||
- No stubbed implementations
|
||||
- Handle edge cases and error conditions
|
||||
- Include descriptive error messages with context
|
||||
- Use async/await for all I/O operations
|
||||
- Use AppError class when throwing errors
|
||||
- Use Zod for validation and react-hook-form for forms
|
||||
|
||||
### Testing
|
||||
|
||||
**Important**: E2E tests are time-consuming. Only write tests for non-trivial functionality.
|
||||
|
||||
- Write E2E tests in `packages/app-tests/e2e/` using Playwright
|
||||
- Test critical user flows and edge cases
|
||||
- Follow existing E2E test patterns in the codebase
|
||||
- Use descriptive test names that explain what is being tested
|
||||
- Skip tests for trivial changes (simple UI tweaks, minor refactors, etc.)
|
||||
|
||||
## Autonomous Workflow
|
||||
|
||||
Work continuously through these steps:
|
||||
|
||||
1. **Implement** - Write the code for the current task
|
||||
2. **Typecheck** - Run `npm run typecheck -w @documenso/remix` to verify types
|
||||
3. **Lint** - Run `npm run lint:fix` to fix linting issues
|
||||
4. **Test** - If non-trivial, run E2E tests: `npm run test:dev -w @documenso/app-tests`
|
||||
5. **Fix** - If tests fail, fix and re-run
|
||||
6. **Repeat** - Move to next task
|
||||
|
||||
## Stopping Conditions
|
||||
|
||||
**Stop and report success when:**
|
||||
|
||||
- All spec requirements are implemented
|
||||
- Typecheck passes
|
||||
- Lint passes
|
||||
- E2E tests pass (if written for non-trivial functionality)
|
||||
|
||||
**Stop and ask for help when:**
|
||||
|
||||
- The spec is ambiguous and you need clarification
|
||||
- You encounter a blocking issue you cannot resolve
|
||||
- You need to make a decision that significantly deviates from the spec
|
||||
- External dependencies are missing
|
||||
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
# Type checking
|
||||
npm run typecheck -w @documenso/remix
|
||||
|
||||
# Linting
|
||||
npm run lint:fix
|
||||
|
||||
# E2E Tests (only for non-trivial work)
|
||||
npm run test:dev -w @documenso/app-tests # Run E2E tests in dev mode
|
||||
npm run test-ui:dev -w @documenso/app-tests # Run E2E tests with UI
|
||||
npm run test:e2e # Run full E2E test suite
|
||||
|
||||
# Development
|
||||
npm run dev # Start dev server
|
||||
```
|
||||
|
||||
## Begin
|
||||
|
||||
Read the spec file and CODE_STYLE.md, assess the current implementation state, then continue where the previous session left off. Use TodoWrite to track your progress throughout.
|
||||
@@ -0,0 +1,75 @@
|
||||
---
|
||||
description: Create a new justification file in .agents/justifications/
|
||||
argument-hint: <justification-slug> [content]
|
||||
---
|
||||
|
||||
You are creating a new justification file in the `.agents/justifications/` directory.
|
||||
|
||||
## Your Task
|
||||
|
||||
1. **Determine the slug** - Use `$ARGUMENTS` as the file slug (kebab-case recommended)
|
||||
2. **Gather content** - Collect or generate the justification content
|
||||
3. **Create the file** - Use the create-justification script to generate the file
|
||||
|
||||
## Usage
|
||||
|
||||
The script will automatically:
|
||||
- Generate a unique three-word ID (e.g., `swift-emerald-river`)
|
||||
- Create frontmatter with current date and formatted title
|
||||
- Save the file as `{id}-{slug}.md` in `.agents/justifications/`
|
||||
|
||||
## Creating the File
|
||||
|
||||
### Option 1: Direct Content
|
||||
|
||||
If you have the content ready, run:
|
||||
|
||||
```bash
|
||||
npx tsx scripts/create-justification.ts "$ARGUMENTS" "Your justification content here"
|
||||
```
|
||||
|
||||
### Option 2: Multi-line Content (Heredoc)
|
||||
|
||||
For multi-line content, use heredoc:
|
||||
|
||||
```bash
|
||||
npx tsx scripts/create-justification.ts "$ARGUMENTS" << HEREDOC
|
||||
Your multi-line
|
||||
justification content
|
||||
goes here
|
||||
HEREDOC
|
||||
```
|
||||
|
||||
### Option 3: Pipe Content
|
||||
|
||||
You can also pipe content:
|
||||
|
||||
```bash
|
||||
echo "Your content" | npx tsx scripts/create-justification.ts "$ARGUMENTS"
|
||||
```
|
||||
|
||||
## File Format
|
||||
|
||||
The created file will have:
|
||||
|
||||
```markdown
|
||||
---
|
||||
date: 2026-01-13
|
||||
title: Justification Title
|
||||
---
|
||||
|
||||
Your content here
|
||||
```
|
||||
|
||||
The title is automatically formatted from the slug (e.g., `architecture-decision` → `Architecture Decision`).
|
||||
|
||||
## Guidelines
|
||||
|
||||
- Use descriptive slugs in kebab-case (e.g., `tech-stack-choice`, `api-design-rationale`)
|
||||
- Include clear reasoning and context for the decision
|
||||
- The unique ID ensures no filename conflicts
|
||||
- Files are automatically dated for organization
|
||||
|
||||
## Begin
|
||||
|
||||
Create a justification file using the slug from `$ARGUMENTS` and appropriate content documenting the reasoning or justification.
|
||||
@@ -0,0 +1,76 @@
|
||||
---
|
||||
description: Create a new plan file in .agents/plans/
|
||||
argument-hint: <plan-slug> [content]
|
||||
---
|
||||
|
||||
You are creating a new plan file in the `.agents/plans/` directory.
|
||||
|
||||
## Your Task
|
||||
|
||||
1. **Determine the slug** - Use `$ARGUMENTS` as the file slug (kebab-case recommended)
|
||||
2. **Gather content** - Collect or generate the plan content
|
||||
3. **Create the file** - Use the create-plan script to generate the file
|
||||
|
||||
## Usage
|
||||
|
||||
The script will automatically:
|
||||
|
||||
- Generate a unique three-word ID (e.g., `happy-blue-moon`)
|
||||
- Create frontmatter with current date and formatted title
|
||||
- Save the file as `{id}-{slug}.md` in `.agents/plans/`
|
||||
|
||||
## Creating the File
|
||||
|
||||
### Option 1: Direct Content
|
||||
|
||||
If you have the content ready, run:
|
||||
|
||||
```bash
|
||||
npx tsx scripts/create-plan.ts "$ARGUMENTS" "Your plan content here"
|
||||
```
|
||||
|
||||
### Option 2: Multi-line Content (Heredoc)
|
||||
|
||||
For multi-line content, use heredoc:
|
||||
|
||||
```bash
|
||||
npx tsx scripts/create-plan.ts "$ARGUMENTS" << HEREDOC
|
||||
Your multi-line
|
||||
plan content
|
||||
goes here
|
||||
HEREDOC
|
||||
```
|
||||
|
||||
### Option 3: Pipe Content
|
||||
|
||||
You can also pipe content:
|
||||
|
||||
```bash
|
||||
echo "Your content" | npx tsx scripts/create-plan.ts "$ARGUMENTS"
|
||||
```
|
||||
|
||||
## File Format
|
||||
|
||||
The created file will have:
|
||||
|
||||
```markdown
|
||||
---
|
||||
date: 2026-01-13
|
||||
title: Plan Title
|
||||
---
|
||||
|
||||
Your content here
|
||||
```
|
||||
|
||||
The title is automatically formatted from the slug (e.g., `my-feature` → `My Feature`).
|
||||
|
||||
## Guidelines
|
||||
|
||||
- Use descriptive slugs in kebab-case (e.g., `user-authentication`, `api-integration`)
|
||||
- Include clear, actionable plan content
|
||||
- The unique ID ensures no filename conflicts
|
||||
- Files are automatically dated for organization
|
||||
|
||||
## Begin
|
||||
|
||||
Create a plan file using the slug from `$ARGUMENTS` and appropriate content for the planning task.
|
||||
@@ -0,0 +1,75 @@
|
||||
---
|
||||
description: Create a new scratch file in .agents/scratches/
|
||||
argument-hint: <scratch-slug> [content]
|
||||
---
|
||||
|
||||
You are creating a new scratch file in the `.agents/scratches/` directory.
|
||||
|
||||
## Your Task
|
||||
|
||||
1. **Determine the slug** - Use `$ARGUMENTS` as the file slug (kebab-case recommended)
|
||||
2. **Gather content** - Collect or generate the scratch content
|
||||
3. **Create the file** - Use the create-scratch script to generate the file
|
||||
|
||||
## Usage
|
||||
|
||||
The script will automatically:
|
||||
- Generate a unique three-word ID (e.g., `calm-teal-cloud`)
|
||||
- Create frontmatter with current date and formatted title
|
||||
- Save the file as `{id}-{slug}.md` in `.agents/scratches/`
|
||||
|
||||
## Creating the File
|
||||
|
||||
### Option 1: Direct Content
|
||||
|
||||
If you have the content ready, run:
|
||||
|
||||
```bash
|
||||
npx tsx scripts/create-scratch.ts "$ARGUMENTS" "Your scratch content here"
|
||||
```
|
||||
|
||||
### Option 2: Multi-line Content (Heredoc)
|
||||
|
||||
For multi-line content, use heredoc:
|
||||
|
||||
```bash
|
||||
npx tsx scripts/create-scratch.ts "$ARGUMENTS" << HEREDOC
|
||||
Your multi-line
|
||||
scratch content
|
||||
goes here
|
||||
HEREDOC
|
||||
```
|
||||
|
||||
### Option 3: Pipe Content
|
||||
|
||||
You can also pipe content:
|
||||
|
||||
```bash
|
||||
echo "Your content" | npx tsx scripts/create-scratch.ts "$ARGUMENTS"
|
||||
```
|
||||
|
||||
## File Format
|
||||
|
||||
The created file will have:
|
||||
|
||||
```markdown
|
||||
---
|
||||
date: 2026-01-13
|
||||
title: Scratch Title
|
||||
---
|
||||
|
||||
Your content here
|
||||
```
|
||||
|
||||
The title is automatically formatted from the slug (e.g., `quick-notes` → `Quick Notes`).
|
||||
|
||||
## Guidelines
|
||||
|
||||
- Use descriptive slugs in kebab-case (e.g., `exploration-ideas`, `temporary-notes`)
|
||||
- Scratch files are for temporary notes, explorations, or ideas
|
||||
- The unique ID ensures no filename conflicts
|
||||
- Files are automatically dated for organization
|
||||
|
||||
## Begin
|
||||
|
||||
Create a scratch file using the slug from `$ARGUMENTS` and appropriate content for notes or exploration.
|
||||
@@ -0,0 +1,201 @@
|
||||
---
|
||||
description: Generate MDX documentation for a module or feature
|
||||
argument-hint: <module-path-or-feature>
|
||||
---
|
||||
|
||||
You are creating proper MDX documentation for a module or feature in Documenso using Nextra.
|
||||
|
||||
## Your Task
|
||||
|
||||
1. **Identify the scope** - What does `$ARGUMENTS` refer to? (file, directory, or feature name)
|
||||
2. **Read the source code** - Understand the public API, types, and behavior
|
||||
3. **Read existing docs** - Check if there's documentation to update or reference
|
||||
4. **Write comprehensive documentation** - Create or update MDX docs in the appropriate location
|
||||
5. **Update navigation** - Add entry to `_meta.js` if creating a new page
|
||||
|
||||
## Documentation Structure
|
||||
|
||||
Create documentation in the appropriate location:
|
||||
|
||||
- **Developer docs**: `apps/documentation/pages/developers/`
|
||||
- **User docs**: `apps/documentation/pages/users/`
|
||||
|
||||
### File Format
|
||||
|
||||
All documentation files must be `.mdx` files with frontmatter:
|
||||
|
||||
```mdx
|
||||
---
|
||||
title: Page Title
|
||||
description: Brief description for SEO and meta tags
|
||||
---
|
||||
|
||||
# Page Title
|
||||
|
||||
Content starts here...
|
||||
```
|
||||
|
||||
### Navigation
|
||||
|
||||
Each directory should have a `_meta.js` file that defines the navigation structure:
|
||||
|
||||
```javascript
|
||||
export default {
|
||||
index: 'Introduction',
|
||||
'feature-name': 'Feature Name',
|
||||
'another-feature': 'Another Feature',
|
||||
};
|
||||
```
|
||||
|
||||
If creating a new page, add it to the appropriate `_meta.js` file.
|
||||
|
||||
### Documentation Format
|
||||
|
||||
````mdx
|
||||
---
|
||||
title: <Module|Feature Name>
|
||||
description: Brief description of what this does and when to use it
|
||||
---
|
||||
|
||||
# <Module|Feature Name>
|
||||
|
||||
Brief description of what this module/feature does and when to use it.
|
||||
|
||||
## Installation
|
||||
|
||||
If there are specific packages or imports needed:
|
||||
|
||||
```bash
|
||||
npm install @documenso/package-name
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
```jsx
|
||||
// Minimal working example
|
||||
import { Component } from '@documenso/package';
|
||||
|
||||
const Example = () => {
|
||||
return <Component />;
|
||||
};
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### Component/Function Name
|
||||
|
||||
Description of what it does.
|
||||
|
||||
#### Props/Parameters
|
||||
|
||||
| Prop/Param | Type | Description |
|
||||
| ---------- | -------------------- | ------------------------- |
|
||||
| prop | `string` | Description of the prop |
|
||||
| optional | `boolean` (optional) | Optional prop description |
|
||||
|
||||
#### Example
|
||||
|
||||
```jsx
|
||||
import { Component } from '@documenso/package';
|
||||
|
||||
<Component prop="value" optional={true} />;
|
||||
```
|
||||
|
||||
### Types
|
||||
|
||||
#### `TypeName`
|
||||
|
||||
```typescript
|
||||
type TypeName = {
|
||||
property: string;
|
||||
optional?: boolean;
|
||||
};
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Common Use Case
|
||||
|
||||
```jsx
|
||||
// Full working example
|
||||
```
|
||||
|
||||
### Advanced Usage
|
||||
|
||||
```jsx
|
||||
// More complex example
|
||||
```
|
||||
|
||||
## Related
|
||||
|
||||
- [Link to related documentation](/developers/path)
|
||||
- [Another related page](/users/path)
|
||||
````
|
||||
|
||||
## Guidelines
|
||||
|
||||
### Content Quality
|
||||
|
||||
- **Be accurate** - Verify behavior by reading the code
|
||||
- **Be complete** - Document all public API surface
|
||||
- **Be practical** - Include real, working examples
|
||||
- **Be concise** - Don't over-explain obvious things
|
||||
- **Be user-focused** - Write for the target audience (developers or users)
|
||||
|
||||
### Code Examples
|
||||
|
||||
- Use appropriate language tags: `jsx`, `tsx`, `typescript`, `bash`, `json`
|
||||
- Show imports when not obvious
|
||||
- Include expected output in comments where helpful
|
||||
- Progress from simple to complex
|
||||
- Use real examples from the codebase when possible
|
||||
|
||||
### Formatting
|
||||
|
||||
- Always include frontmatter with `title` and `description`
|
||||
- Use proper markdown headers (h1 for title, h2 for sections)
|
||||
- Use tables for props/parameters documentation (matching existing style)
|
||||
- Use code fences with appropriate language tags
|
||||
- Use Nextra components when appropriate:
|
||||
- `<Callout type="info">` for notes
|
||||
- `<Steps>` for step-by-step instructions
|
||||
- Use relative links for internal documentation (e.g., `/developers/embedding/react`)
|
||||
|
||||
### Nextra Components
|
||||
|
||||
You can import and use Nextra components:
|
||||
|
||||
```jsx
|
||||
import { Callout, Steps } from 'nextra/components';
|
||||
|
||||
<Callout type="info">
|
||||
This is an informational note.
|
||||
</Callout>
|
||||
|
||||
<Steps>
|
||||
<Steps.Step>First step</Steps.Step>
|
||||
<Steps.Step>Second step</Steps.Step>
|
||||
</Steps>
|
||||
```
|
||||
|
||||
### Maintenance
|
||||
|
||||
- Include types inline so docs don't get stale
|
||||
- Reference source file locations for complex behavior
|
||||
- Keep examples up-to-date with the codebase
|
||||
- Update `_meta.js` when adding new pages
|
||||
|
||||
## Process
|
||||
|
||||
1. **Explore the code** - Read source files to understand the API
|
||||
2. **Identify the audience** - Is this for developers or users?
|
||||
3. **Check existing docs** - Look for similar pages to match style
|
||||
4. **Draft the structure** - Outline sections before writing
|
||||
5. **Write content** - Fill in each section with frontmatter
|
||||
6. **Add examples** - Create working code samples
|
||||
7. **Update navigation** - Add to `_meta.js` if needed
|
||||
8. **Review** - Read through for clarity and accuracy
|
||||
|
||||
## Begin
|
||||
|
||||
Analyze `$ARGUMENTS`, read the relevant source code, check existing documentation patterns, and create comprehensive MDX documentation following the Documenso documentation style.
|
||||
@@ -0,0 +1,100 @@
|
||||
---
|
||||
description: Implement a spec from the plans directory
|
||||
argument-hint: <spec-file-path>
|
||||
---
|
||||
|
||||
You are implementing a specification from the `.agents/plans/` directory. Work autonomously until the feature is complete and tests pass.
|
||||
|
||||
## Your Task
|
||||
|
||||
1. **Read the spec** at `$ARGUMENTS`
|
||||
2. **Read CODE_STYLE.md** for formatting conventions
|
||||
3. **Plan the implementation** using the TodoWrite tool to break down the work
|
||||
4. **Implement the feature** following the spec and code style
|
||||
5. **Write E2E tests** only for non-trivial functionality (E2E tests are time-consuming)
|
||||
6. **Run tests** and fix any failures
|
||||
7. **Run typecheck and lint** and fix any issues
|
||||
|
||||
## Implementation Guidelines
|
||||
|
||||
### Before Coding
|
||||
|
||||
- Understand the spec's goals and scope
|
||||
- Identify the desired API from usage examples in the spec
|
||||
- Review related existing code to understand patterns
|
||||
- Break the work into discrete tasks using TodoWrite
|
||||
|
||||
### During Implementation
|
||||
|
||||
- Follow CODE_STYLE.md strictly (2-space indent, double quotes, braces always, etc.)
|
||||
- Follow workspace rules for TypeScript, React, TRPC patterns, and Remix conventions
|
||||
- Mark todos complete as you finish each task
|
||||
- Commit logical chunks of work
|
||||
|
||||
### Code Quality
|
||||
|
||||
- No stubbed implementations
|
||||
- Handle edge cases and error conditions
|
||||
- Include descriptive error messages with context
|
||||
- Use async/await for all I/O operations
|
||||
- Use AppError class when throwing errors
|
||||
- Use Zod for validation and react-hook-form for forms
|
||||
|
||||
### Testing
|
||||
|
||||
**Important**: E2E tests are time-consuming. Only write tests for non-trivial functionality.
|
||||
|
||||
- Write E2E tests in `packages/app-tests/e2e/` using Playwright
|
||||
- Test critical user flows and edge cases
|
||||
- Follow existing E2E test patterns in the codebase
|
||||
- Use descriptive test names that explain what is being tested
|
||||
- Skip tests for trivial changes (simple UI tweaks, minor refactors, etc.)
|
||||
|
||||
## Autonomous Workflow
|
||||
|
||||
Work continuously through these steps:
|
||||
|
||||
1. **Implement** - Write the code for the current task
|
||||
2. **Typecheck** - Run `npm run typecheck -w @documenso/remix` to verify types
|
||||
3. **Lint** - Run `npm run lint:fix` to fix linting issues
|
||||
4. **Test** - If non-trivial, run E2E tests: `npm run test:dev -w @documenso/app-tests`
|
||||
5. **Fix** - If tests fail, fix and re-run
|
||||
6. **Repeat** - Move to next task
|
||||
|
||||
## Stopping Conditions
|
||||
|
||||
**Stop and report success when:**
|
||||
|
||||
- All spec requirements are implemented
|
||||
- Typecheck passes
|
||||
- Lint passes
|
||||
- E2E tests pass (if written for non-trivial functionality)
|
||||
|
||||
**Stop and ask for help when:**
|
||||
|
||||
- The spec is ambiguous and you need clarification
|
||||
- You encounter a blocking issue you cannot resolve
|
||||
- You need to make a decision that significantly deviates from the spec
|
||||
- External dependencies are missing
|
||||
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
# Type checking
|
||||
npm run typecheck -w @documenso/remix
|
||||
|
||||
# Linting
|
||||
npm run lint:fix
|
||||
|
||||
# E2E Tests (only for non-trivial work)
|
||||
npm run test:dev -w @documenso/app-tests # Run E2E tests in dev mode
|
||||
npm run test-ui:dev -w @documenso/app-tests # Run E2E tests with UI
|
||||
npm run test:e2e # Run full E2E test suite
|
||||
|
||||
# Development
|
||||
npm run dev # Start dev server
|
||||
```
|
||||
|
||||
## Begin
|
||||
|
||||
Read the spec file and CODE_STYLE.md, then start implementing. Use TodoWrite to track your progress throughout.
|
||||
@@ -0,0 +1,57 @@
|
||||
---
|
||||
description: Deep-dive interview to flesh out a spec or design document
|
||||
agent: build
|
||||
argument-hint: <file-path>
|
||||
---
|
||||
|
||||
You are conducting a thorough interview to help flesh out and complete a specification or design document.
|
||||
|
||||
## Your Task
|
||||
|
||||
1. **Read the document** at `$ARGUMENTS`
|
||||
2. **Analyze it deeply** - identify gaps, ambiguities, unexplored edge cases, and areas needing clarification
|
||||
3. **Interview the user** by providing a question with some pre-determined options
|
||||
4. **Write the completed spec** back to the file when the interview is complete
|
||||
|
||||
## Interview Guidelines
|
||||
|
||||
### Question Quality
|
||||
- Ask **non-obvious, insightful questions** - avoid surface-level queries
|
||||
- Focus on: technical implementation details, architectural decisions, edge cases, error handling, UX implications, security considerations, performance tradeoffs, integration points, migration strategies, rollback plans
|
||||
- Each question should reveal something that would otherwise be missed
|
||||
- Challenge assumptions embedded in the document
|
||||
- Explore second and third-order consequences of design decisions
|
||||
- Use the Web Search and other tools where required to ground questions (e.g. package recommendations)
|
||||
|
||||
### Question Strategy
|
||||
- Start by identifying the 3-5 most critical unknowns or ambiguities
|
||||
- Use the AskUserQuestion tool with well-crafted options that represent real tradeoffs
|
||||
- When appropriate, offer multiple valid approaches with their pros/cons as options
|
||||
- Don't ask about things that are already clearly specified
|
||||
- Probe deeper when answers reveal new areas of uncertainty
|
||||
|
||||
### Topics to Explore (as relevant)
|
||||
- **Technical**: Data models, API contracts, state management, concurrency, caching, validation
|
||||
- **UX**: Error states, loading states, empty states, edge cases, accessibility, mobile considerations
|
||||
- **Operations**: Deployment, monitoring, alerting, debugging, logging, feature flags
|
||||
- **Security**: Auth, authz, input validation, rate limiting, audit trails
|
||||
- **Scale**: Performance bottlenecks, data growth, traffic spikes, graceful degradation
|
||||
- **Integration**: Dependencies, backwards compatibility, versioning, migration path
|
||||
- **Failure modes**: What happens when X fails? How do we recover? What's the blast radius?
|
||||
|
||||
### Interview Flow
|
||||
1. Ask 2-4 questions at a time (use multiple questions in one when they're related)
|
||||
2. After each round, incorporate answers and identify follow-up questions
|
||||
3. Continue until all critical areas are addressed
|
||||
4. Signal when you believe the interview is complete, but offer to go deeper
|
||||
|
||||
## Output
|
||||
|
||||
When the interview is complete:
|
||||
1. Synthesize all gathered information
|
||||
2. Rewrite/expand the original document with the new details
|
||||
3. Preserve the document's original structure where sensible, but reorganize if needed
|
||||
4. Add new sections for areas that weren't originally covered
|
||||
5. Write the completed spec back to `$ARGUMENTS`
|
||||
|
||||
Begin by reading the file and identifying your first set of deep questions.
|
||||
@@ -0,0 +1,56 @@
|
||||
---
|
||||
name: create-justification
|
||||
description: Create a new justification file in .agents/justifications/ with a unique three-word ID, frontmatter, and formatted title
|
||||
license: MIT
|
||||
compatibility: opencode
|
||||
metadata:
|
||||
audience: agents
|
||||
workflow: decision-making
|
||||
---
|
||||
|
||||
## What I do
|
||||
|
||||
I help you create new justification files in the `.agents/justifications/` directory. Each justification file gets:
|
||||
|
||||
- A unique three-word identifier (e.g., `swift-emerald-river`)
|
||||
- Frontmatter with the current date and formatted title
|
||||
- Content you provide
|
||||
|
||||
## How to use
|
||||
|
||||
Run the script with a slug and content:
|
||||
|
||||
```bash
|
||||
npx tsx scripts/create-justification.ts "decision-name" "Justification content here"
|
||||
```
|
||||
|
||||
Or use heredoc for multi-line content:
|
||||
|
||||
```bash
|
||||
npx tsx scripts/create-justification.ts "decision-name" << HEREDOC
|
||||
Multi-line
|
||||
justification content
|
||||
goes here
|
||||
HEREDOC
|
||||
```
|
||||
|
||||
## File format
|
||||
|
||||
Files are created as: `{three-word-id}-{slug}.md`
|
||||
|
||||
Example: `swift-emerald-river-decision-name.md`
|
||||
|
||||
The file includes frontmatter:
|
||||
|
||||
```markdown
|
||||
---
|
||||
date: 2026-01-13
|
||||
title: Decision Name
|
||||
---
|
||||
|
||||
Your content here
|
||||
```
|
||||
|
||||
## When to use me
|
||||
|
||||
Use this skill when you need to document the reasoning or justification for a decision, approach, or architectural choice. The unique ID ensures no filename conflicts, and the frontmatter provides metadata for organization.
|
||||
@@ -0,0 +1,56 @@
|
||||
---
|
||||
name: create-plan
|
||||
description: Create a new plan file in .agents/plans/ with a unique three-word ID, frontmatter, and formatted title
|
||||
license: MIT
|
||||
compatibility: opencode
|
||||
metadata:
|
||||
audience: agents
|
||||
workflow: planning
|
||||
---
|
||||
|
||||
## What I do
|
||||
|
||||
I help you create new plan files in the `.agents/plans/` directory. Each plan file gets:
|
||||
|
||||
- A unique three-word identifier (e.g., `happy-blue-moon`)
|
||||
- Frontmatter with the current date and formatted title
|
||||
- Content you provide
|
||||
|
||||
## How to use
|
||||
|
||||
Run the script with a slug and content:
|
||||
|
||||
```bash
|
||||
npx tsx scripts/create-plan.ts "feature-name" "Plan content here"
|
||||
```
|
||||
|
||||
Or use heredoc for multi-line content:
|
||||
|
||||
```bash
|
||||
npx tsx scripts/create-plan.ts "feature-name" << HEREDOC
|
||||
Multi-line
|
||||
plan content
|
||||
goes here
|
||||
HEREDOC
|
||||
```
|
||||
|
||||
## File format
|
||||
|
||||
Files are created as: `{three-word-id}-{slug}.md`
|
||||
|
||||
Example: `happy-blue-moon-feature-name.md`
|
||||
|
||||
The file includes frontmatter:
|
||||
|
||||
```markdown
|
||||
---
|
||||
date: 2026-01-13
|
||||
title: Feature Name
|
||||
---
|
||||
|
||||
Your content here
|
||||
```
|
||||
|
||||
## When to use me
|
||||
|
||||
Use this skill when you need to create a new plan document for a feature, task, or project. The unique ID ensures no filename conflicts, and the frontmatter provides metadata for organization.
|
||||
@@ -0,0 +1,56 @@
|
||||
---
|
||||
name: create-scratch
|
||||
description: Create a new scratch file in .agents/scratches/ with a unique three-word ID, frontmatter, and formatted title
|
||||
license: MIT
|
||||
compatibility: opencode
|
||||
metadata:
|
||||
audience: agents
|
||||
workflow: exploration
|
||||
---
|
||||
|
||||
## What I do
|
||||
|
||||
I help you create new scratch files in the `.agents/scratches/` directory. Each scratch file gets:
|
||||
|
||||
- A unique three-word identifier (e.g., `calm-teal-cloud`)
|
||||
- Frontmatter with the current date and formatted title
|
||||
- Content you provide
|
||||
|
||||
## How to use
|
||||
|
||||
Run the script with a slug and content:
|
||||
|
||||
```bash
|
||||
npx tsx scripts/create-scratch.ts "note-name" "Scratch content here"
|
||||
```
|
||||
|
||||
Or use heredoc for multi-line content:
|
||||
|
||||
```bash
|
||||
npx tsx scripts/create-scratch.ts "note-name" << HEREDOC
|
||||
Multi-line
|
||||
scratch content
|
||||
goes here
|
||||
HEREDOC
|
||||
```
|
||||
|
||||
## File format
|
||||
|
||||
Files are created as: `{three-word-id}-{slug}.md`
|
||||
|
||||
Example: `calm-teal-cloud-note-name.md`
|
||||
|
||||
The file includes frontmatter:
|
||||
|
||||
```markdown
|
||||
---
|
||||
date: 2026-01-13
|
||||
title: Note Name
|
||||
---
|
||||
|
||||
Your content here
|
||||
```
|
||||
|
||||
## When to use me
|
||||
|
||||
Use this skill when you need to create a temporary note, exploration document, or scratch pad for ideas. The unique ID ensures no filename conflicts, and the frontmatter provides metadata for organization.
|
||||
@@ -11,6 +11,8 @@
|
||||
- `npm run format` - Format code with Prettier
|
||||
- `npm run dev` - Start development server for Remix app
|
||||
|
||||
**Important:** Do not run `npm run build` to verify changes unless explicitly asked. Builds take a long time (~2 minutes). Use `npx tsc --noEmit` for type checking specific packages if needed.
|
||||
|
||||
## Code Style Guidelines
|
||||
|
||||
- Use TypeScript for all code; prefer `type` over `interface`
|
||||
|
||||
@@ -52,3 +52,53 @@ You can build the project with:
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
## AI-Assisted Development with OpenCode
|
||||
|
||||
We use [OpenCode](https://opencode.ai) for AI-assisted development. OpenCode provides custom commands and skills to help maintain consistency and streamline common workflows.
|
||||
|
||||
OpenCode works with most major AI providers (Anthropic, OpenAI, Google, etc.) or you can use [Zen](https://opencode.ai/zen) for optimized coding models. Configure your preferred provider in the OpenCode settings.
|
||||
|
||||
> **Important**: All AI-generated code must be thoroughly reviewed by the contributor before submitting a PR. You are responsible for understanding and validating every line of code you submit. If we detect that contributors are simply throwing AI-generated code over the wall without proper review, they will be blocked from the repository.
|
||||
|
||||
### Getting Started
|
||||
|
||||
1. Install OpenCode (see [opencode.ai](https://opencode.ai) for other install methods):
|
||||
```bash
|
||||
curl -fsSL https://opencode.ai/install | bash
|
||||
```
|
||||
2. Configure your AI provider (or use Zen for optimized models)
|
||||
3. Run `opencode` in the project root
|
||||
|
||||
### Available Commands
|
||||
|
||||
Use these commands in OpenCode by typing the command name:
|
||||
|
||||
| Command | Description |
|
||||
| ------------------------------ | -------------------------------------------------------- |
|
||||
| `/implement <spec-path>` | Implement a spec from `.agents/plans/` autonomously |
|
||||
| `/continue <spec-path>` | Continue implementing a spec from a previous session |
|
||||
| `/interview <file-path>` | Deep-dive interview to flesh out a spec or design |
|
||||
| `/document <module-path>` | Generate MDX documentation for a module or feature |
|
||||
| `/commit` | Create a conventional commit for staged changes |
|
||||
| `/create-plan <slug>` | Create a new plan file in `.agents/plans/` |
|
||||
| `/create-scratch <slug>` | Create a scratch file for notes in `.agents/scratches/` |
|
||||
| `/create-justification <slug>` | Create a justification file in `.agents/justifications/` |
|
||||
|
||||
### Typical Workflow
|
||||
|
||||
1. **Create a plan**: Use `/create-plan my-feature` to draft a spec for a new feature
|
||||
2. **Flesh out the spec**: Use `/interview .agents/plans/<file>.md` to refine requirements
|
||||
3. **Implement**: Use `/implement .agents/plans/<file>.md` to build the feature
|
||||
4. **Continue if needed**: Use `/continue .agents/plans/<file>.md` to pick up where you left off
|
||||
5. **Commit**: Use `/commit` to create a conventional commit
|
||||
|
||||
### Agent Files
|
||||
|
||||
The `.agents/` directory stores AI-generated artifacts:
|
||||
|
||||
- **`.agents/plans/`** - Feature specs and implementation plans
|
||||
- **`.agents/scratches/`** - Temporary notes and explorations
|
||||
- **`.agents/justifications/`** - Decision rationale and technical justifications
|
||||
|
||||
These files use a unique ID format (`{word}-{word}-{word}-{slug}.md`) to prevent conflicts.
|
||||
|
||||
@@ -31,6 +31,10 @@ Our new API V2 supports the following typed SDKs:
|
||||
|
||||
## API V1 - Deprecated
|
||||
|
||||
<Callout type="warning">
|
||||
v1 API is deprecated and will be removed in a future release. Please migrate to v2.
|
||||
</Callout>
|
||||
|
||||
Check out the [API V1 documentation](https://app.documenso.com/api/v1/openapi) for details about the API endpoints, request parameters, response formats, and authentication methods.
|
||||
|
||||
📖 [Documentation](https://documen.so/api-v2-docs)
|
||||
|
||||
@@ -11,6 +11,8 @@ Documenso uses API versioning to manage changes to the public API. This allows u
|
||||
|
||||
<Callout type="info">The current version of the API is `v2`.</Callout>
|
||||
|
||||
<Callout type="warning">v1 API is deprecated. Please migrate to v2.</Callout>
|
||||
|
||||
The API version is specified in the URL. For example, the base URL for the `v2` API is `https://app.documenso.com/api/v2`.
|
||||
|
||||
We may make changes to the API without incrementing the version number. We will always try to avoid breaking changes, but in some cases, it may be necessary to make changes that are not backward compatible. In these cases, we will increment the version number and provide information about the changes in the release notes.
|
||||
|
||||
@@ -73,7 +73,9 @@ export const PasskeyCreateDialog = ({ trigger, onSuccess, ...props }: PasskeyCre
|
||||
try {
|
||||
const passkeyRegistrationOptions = await createPasskeyRegistrationOptions();
|
||||
|
||||
const registrationResult = await startRegistration(passkeyRegistrationOptions);
|
||||
const registrationResult = await startRegistration({
|
||||
optionsJSON: passkeyRegistrationOptions,
|
||||
});
|
||||
|
||||
await createPasskey({
|
||||
passkeyName,
|
||||
|
||||
@@ -82,9 +82,7 @@ export const SignFieldCheckboxDialog = createCallable<
|
||||
<Dialog open={true} onOpenChange={(value) => (!value ? call.end(null) : null)}>
|
||||
<DialogContent position="center">
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
<Trans>Sign Checkbox Field</Trans>
|
||||
</DialogTitle>
|
||||
<DialogTitle>{fieldMeta.label || <Trans>Select Options</Trans>}</DialogTitle>
|
||||
|
||||
<DialogDescription
|
||||
className={cn('mt-4', {
|
||||
@@ -143,7 +141,7 @@ export const SignFieldCheckboxDialog = createCallable<
|
||||
<div className="flex items-center">
|
||||
<Checkbox
|
||||
id={`checkbox-value-${index}`}
|
||||
className="data-[state=checked]:bg-primary border-foreground/30 h-5 w-5"
|
||||
className="h-5 w-5 border-foreground/30 data-[state=checked]:bg-primary"
|
||||
checked={field.value.checked}
|
||||
onCheckedChange={(checked) => {
|
||||
field.onChange({
|
||||
@@ -154,7 +152,7 @@ export const SignFieldCheckboxDialog = createCallable<
|
||||
/>
|
||||
|
||||
<label
|
||||
className="text-muted-foreground ml-2 w-full text-sm"
|
||||
className="ml-2 w-full text-sm text-muted-foreground"
|
||||
htmlFor={`checkbox-value-${index}`}
|
||||
>
|
||||
{value.value}
|
||||
@@ -174,7 +172,7 @@ export const SignFieldCheckboxDialog = createCallable<
|
||||
</Button>
|
||||
|
||||
<Button type="submit">
|
||||
<Trans>Sign</Trans>
|
||||
<Trans>Confirm</Trans>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</fieldset>
|
||||
|
||||
@@ -50,11 +50,11 @@ export const SignFieldEmailDialog = createCallable<SignFieldEmailDialogProps, st
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
<Trans>Sign Email</Trans>
|
||||
<Trans>Enter Email</Trans>
|
||||
</DialogTitle>
|
||||
|
||||
<DialogDescription className="mt-4">
|
||||
<Trans>Sign your email into the field</Trans>
|
||||
<Trans>Please enter your email address</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
@@ -83,7 +83,7 @@ export const SignFieldEmailDialog = createCallable<SignFieldEmailDialogProps, st
|
||||
</Button>
|
||||
|
||||
<Button type="submit">
|
||||
<Trans>Sign</Trans>
|
||||
<Trans>Enter</Trans>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</fieldset>
|
||||
|
||||
@@ -48,11 +48,11 @@ export const SignFieldInitialsDialog = createCallable<SignFieldInitialsDialogPro
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
<Trans>Sign Initials</Trans>
|
||||
<Trans>Enter Initials</Trans>
|
||||
</DialogTitle>
|
||||
|
||||
<DialogDescription className="mt-4">
|
||||
<Trans>Sign your initials into the field</Trans>
|
||||
<Trans>Please enter your initials</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
@@ -84,7 +84,7 @@ export const SignFieldInitialsDialog = createCallable<SignFieldInitialsDialogPro
|
||||
</Button>
|
||||
|
||||
<Button type="submit">
|
||||
<Trans>Sign</Trans>
|
||||
<Trans>Enter</Trans>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</fieldset>
|
||||
|
||||
@@ -47,11 +47,11 @@ export const SignFieldNameDialog = createCallable<SignFieldNameDialogProps, stri
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
<Trans>Sign Name</Trans>
|
||||
<Trans>Enter Name</Trans>
|
||||
</DialogTitle>
|
||||
|
||||
<DialogDescription className="mt-4">
|
||||
<Trans>Sign your full name into the field</Trans>
|
||||
<Trans>Please enter your full name</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
@@ -80,7 +80,7 @@ export const SignFieldNameDialog = createCallable<SignFieldNameDialogProps, stri
|
||||
</Button>
|
||||
|
||||
<Button type="submit">
|
||||
<Trans>Sign</Trans>
|
||||
<Trans>Enter</Trans>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</fieldset>
|
||||
|
||||
@@ -22,7 +22,6 @@ import {
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@documenso/ui/primitives/form/form';
|
||||
import { Input } from '@documenso/ui/primitives/input';
|
||||
@@ -107,12 +106,10 @@ export const SignFieldNumberDialog = createCallable<SignFieldNumberDialogProps,
|
||||
<Dialog open={true} onOpenChange={(value) => (!value ? call.end(null) : null)}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
<Trans>Sign Number Field</Trans>
|
||||
</DialogTitle>
|
||||
<DialogTitle>{fieldMeta.label || <Trans>Enter Number</Trans>}</DialogTitle>
|
||||
|
||||
<DialogDescription className="mt-4">
|
||||
<Trans>Insert a value into the number field</Trans>
|
||||
<Trans>Please enter a number</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
@@ -127,8 +124,6 @@ export const SignFieldNumberDialog = createCallable<SignFieldNumberDialogProps,
|
||||
name="number"
|
||||
render={({ field, fieldState }) => (
|
||||
<FormItem>
|
||||
{fieldMeta.label && <FormLabel>{fieldMeta.label}</FormLabel>}
|
||||
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder={fieldMeta.placeholder ?? t`Enter your number here`}
|
||||
@@ -150,7 +145,7 @@ export const SignFieldNumberDialog = createCallable<SignFieldNumberDialogProps,
|
||||
</Button>
|
||||
|
||||
<Button type="submit">
|
||||
<Trans>Sign</Trans>
|
||||
<Trans>Enter</Trans>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</fieldset>
|
||||
|
||||
@@ -22,7 +22,6 @@ import {
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@documenso/ui/primitives/form/form';
|
||||
import { Textarea } from '@documenso/ui/primitives/textarea';
|
||||
@@ -52,12 +51,10 @@ export const SignFieldTextDialog = createCallable<SignFieldTextDialogProps, stri
|
||||
<Dialog open={true} onOpenChange={(value) => (!value ? call.end(null) : null)}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
<Trans>Sign Text Field</Trans>
|
||||
</DialogTitle>
|
||||
<DialogTitle>{fieldMeta?.label || <Trans>Enter Text</Trans>}</DialogTitle>
|
||||
|
||||
<DialogDescription className="mt-4">
|
||||
<Trans>Insert a value into the text field</Trans>
|
||||
<Trans>Please enter a value</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
@@ -72,8 +69,6 @@ export const SignFieldTextDialog = createCallable<SignFieldTextDialogProps, stri
|
||||
name="text"
|
||||
render={({ field, fieldState }) => (
|
||||
<FormItem>
|
||||
{fieldMeta?.label && <FormLabel>{fieldMeta?.label}</FormLabel>}
|
||||
|
||||
<FormControl>
|
||||
<Textarea
|
||||
id="custom-text"
|
||||
@@ -89,7 +84,7 @@ export const SignFieldTextDialog = createCallable<SignFieldTextDialogProps, stri
|
||||
{fieldMeta?.characterLimit !== undefined &&
|
||||
fieldMeta?.characterLimit > 0 &&
|
||||
!fieldState.error && (
|
||||
<div className="text-muted-foreground text-sm">
|
||||
<div className="text-sm text-muted-foreground">
|
||||
<Plural
|
||||
value={fieldMeta?.characterLimit - (field.value?.length ?? 0)}
|
||||
one="# character remaining"
|
||||
@@ -107,7 +102,7 @@ export const SignFieldTextDialog = createCallable<SignFieldTextDialogProps, stri
|
||||
</Button>
|
||||
|
||||
<Button type="submit">
|
||||
<Trans>Sign</Trans>
|
||||
<Trans>Enter</Trans>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</fieldset>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import type { TeamGlobalSettings } from '@prisma/client';
|
||||
import { DocumentVisibility, OrganisationType } from '@prisma/client';
|
||||
import { DocumentVisibility, OrganisationType, type RecipientRole } from '@prisma/client';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
@@ -17,14 +17,19 @@ import {
|
||||
isValidLanguageCode,
|
||||
} from '@documenso/lib/constants/i18n';
|
||||
import { TIME_ZONES } from '@documenso/lib/constants/time-zones';
|
||||
import type { TDefaultRecipients } from '@documenso/lib/types/default-recipients';
|
||||
import { ZDefaultRecipientsSchema } from '@documenso/lib/types/default-recipients';
|
||||
import {
|
||||
type TDocumentMetaDateFormat,
|
||||
ZDocumentMetaTimezoneSchema,
|
||||
} from '@documenso/lib/types/document-meta';
|
||||
import { isPersonalLayout } from '@documenso/lib/utils/organisations';
|
||||
import { recipientAbbreviation } from '@documenso/lib/utils/recipient-formatter';
|
||||
import { extractTeamSignatureSettings } from '@documenso/lib/utils/teams';
|
||||
import { DocumentSignatureSettingsTooltip } from '@documenso/ui/components/document/document-signature-settings-tooltip';
|
||||
import { RecipientRoleSelect } from '@documenso/ui/components/recipient/recipient-role-select';
|
||||
import { Alert } from '@documenso/ui/primitives/alert';
|
||||
import { AvatarWithText } from '@documenso/ui/primitives/avatar';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import { Combobox } from '@documenso/ui/primitives/combobox';
|
||||
import {
|
||||
@@ -45,6 +50,10 @@ import {
|
||||
SelectValue,
|
||||
} from '@documenso/ui/primitives/select';
|
||||
|
||||
import { useOptionalCurrentTeam } from '~/providers/team';
|
||||
|
||||
import { DefaultRecipientsMultiSelectCombobox } from '../general/default-recipients-multiselect-combobox';
|
||||
|
||||
/**
|
||||
* Can't infer this from the schema since we need to keep the schema inside the component to allow
|
||||
* it to be dynamic.
|
||||
@@ -58,6 +67,7 @@ export type TDocumentPreferencesFormSchema = {
|
||||
includeSigningCertificate: boolean | null;
|
||||
includeAuditLog: boolean | null;
|
||||
signatureTypes: DocumentSignatureType[];
|
||||
defaultRecipients: TDefaultRecipients | null;
|
||||
delegateDocumentOwnership: boolean | null;
|
||||
aiFeaturesEnabled: boolean | null;
|
||||
};
|
||||
@@ -74,6 +84,7 @@ type SettingsSubset = Pick<
|
||||
| 'typedSignatureEnabled'
|
||||
| 'uploadSignatureEnabled'
|
||||
| 'drawSignatureEnabled'
|
||||
| 'defaultRecipients'
|
||||
| 'delegateDocumentOwnership'
|
||||
| 'aiFeaturesEnabled'
|
||||
>;
|
||||
@@ -94,6 +105,7 @@ export const DocumentPreferencesForm = ({
|
||||
const { t } = useLingui();
|
||||
const { user, organisations } = useSession();
|
||||
const currentOrganisation = useCurrentOrganisation();
|
||||
const optionalTeam = useOptionalCurrentTeam();
|
||||
|
||||
const isPersonalLayoutMode = isPersonalLayout(organisations);
|
||||
const isPersonalOrganisation = currentOrganisation.type === OrganisationType.PERSONAL;
|
||||
@@ -111,6 +123,7 @@ export const DocumentPreferencesForm = ({
|
||||
signatureTypes: z.array(z.nativeEnum(DocumentSignatureType)).min(canInherit ? 0 : 1, {
|
||||
message: msg`At least one signature type must be enabled`.id,
|
||||
}),
|
||||
defaultRecipients: ZDefaultRecipientsSchema.nullable(),
|
||||
delegateDocumentOwnership: z.boolean().nullable(),
|
||||
aiFeaturesEnabled: z.boolean().nullable(),
|
||||
});
|
||||
@@ -128,6 +141,9 @@ export const DocumentPreferencesForm = ({
|
||||
includeSigningCertificate: settings.includeSigningCertificate,
|
||||
includeAuditLog: settings.includeAuditLog,
|
||||
signatureTypes: extractTeamSignatureSettings({ ...settings }),
|
||||
defaultRecipients: settings.defaultRecipients
|
||||
? ZDefaultRecipientsSchema.parse(settings.defaultRecipients)
|
||||
: null,
|
||||
delegateDocumentOwnership: settings.delegateDocumentOwnership,
|
||||
aiFeaturesEnabled: settings.aiFeaturesEnabled,
|
||||
},
|
||||
@@ -519,6 +535,94 @@ export const DocumentPreferencesForm = ({
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="defaultRecipients"
|
||||
render={({ field }) => {
|
||||
const recipients = field.value ?? [];
|
||||
|
||||
return (
|
||||
<FormItem className="flex-1">
|
||||
<FormLabel>
|
||||
<Trans>Default Recipients</Trans>
|
||||
</FormLabel>
|
||||
|
||||
{canInherit && (
|
||||
<Select
|
||||
value={field.value === null ? '-1' : '0'}
|
||||
onValueChange={(value) => field.onChange(value === '-1' ? null : [])}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value={'-1'}>
|
||||
<Trans>Inherit from organisation</Trans>
|
||||
</SelectItem>
|
||||
<SelectItem value={'0'}>
|
||||
<Trans>Override organisation settings</Trans>
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)}
|
||||
|
||||
{(field.value !== null || !canInherit) && (
|
||||
<div className="space-y-4">
|
||||
<DefaultRecipientsMultiSelectCombobox
|
||||
listValues={recipients}
|
||||
onChange={field.onChange}
|
||||
organisationId={!canInherit ? currentOrganisation.id : undefined}
|
||||
teamId={canInherit ? optionalTeam?.id : undefined}
|
||||
/>
|
||||
|
||||
{recipients.map((recipient, index) => {
|
||||
return (
|
||||
<div
|
||||
key={recipient.email}
|
||||
className="flex items-center justify-between gap-3 rounded-lg border p-3"
|
||||
>
|
||||
<AvatarWithText
|
||||
avatarFallback={recipientAbbreviation(recipient)}
|
||||
primaryText={
|
||||
<span className="text-sm font-medium">
|
||||
{recipient.name || recipient.email}
|
||||
</span>
|
||||
}
|
||||
secondaryText={
|
||||
recipient.name ? (
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{recipient.email}
|
||||
</span>
|
||||
) : undefined
|
||||
}
|
||||
className="flex-1"
|
||||
/>
|
||||
<div className="flex items-center gap-2">
|
||||
<RecipientRoleSelect
|
||||
value={recipient.role}
|
||||
onValueChange={(role: RecipientRole) => {
|
||||
field.onChange(
|
||||
recipients.map((recipient, idx) =>
|
||||
idx === index ? { ...recipient, role } : recipient,
|
||||
),
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<FormDescription>
|
||||
<Trans>Recipients that will be automatically added to new documents.</Trans>
|
||||
</FormDescription>
|
||||
</FormItem>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="delegateDocumentOwnership"
|
||||
|
||||
@@ -171,7 +171,7 @@ export const SignInForm = ({
|
||||
|
||||
const { options, sessionId } = await createPasskeySigninOptions();
|
||||
|
||||
const credential = await startAuthentication(options);
|
||||
const credential = await startAuthentication({ optionsJSON: options });
|
||||
|
||||
await authClient.passkey.signIn({
|
||||
credential: JSON.stringify(credential),
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { RecipientRole } from '@prisma/client';
|
||||
|
||||
import type { TDefaultRecipient } from '@documenso/lib/types/default-recipients';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { MultiSelect, type Option } from '@documenso/ui/primitives/multiselect';
|
||||
|
||||
type DefaultRecipientsMultiSelectComboboxProps = {
|
||||
listValues: TDefaultRecipient[];
|
||||
onChange: (_values: TDefaultRecipient[]) => void;
|
||||
teamId?: number;
|
||||
organisationId?: string;
|
||||
};
|
||||
|
||||
export const DefaultRecipientsMultiSelectCombobox = ({
|
||||
listValues,
|
||||
onChange,
|
||||
teamId,
|
||||
organisationId,
|
||||
}: DefaultRecipientsMultiSelectComboboxProps) => {
|
||||
const { _ } = useLingui();
|
||||
|
||||
const { data: organisationData, isLoading: isLoadingOrganisation } =
|
||||
trpc.organisation.member.find.useQuery(
|
||||
{
|
||||
organisationId: organisationId!,
|
||||
query: '',
|
||||
page: 1,
|
||||
perPage: 100,
|
||||
},
|
||||
{
|
||||
enabled: !!organisationId,
|
||||
},
|
||||
);
|
||||
|
||||
const { data: teamData, isLoading: isLoadingTeam } = trpc.team.member.find.useQuery(
|
||||
{
|
||||
teamId: teamId!,
|
||||
query: '',
|
||||
page: 1,
|
||||
perPage: 100,
|
||||
},
|
||||
{
|
||||
enabled: !!teamId,
|
||||
},
|
||||
);
|
||||
|
||||
const members = organisationId ? organisationData?.data : teamData?.data;
|
||||
const isLoading = organisationId ? isLoadingOrganisation : isLoadingTeam;
|
||||
|
||||
const options = members?.map((member) => ({
|
||||
value: member.email,
|
||||
label: member.name ? `${member.name} (${member.email})` : member.email,
|
||||
}));
|
||||
|
||||
const value = listValues.map((recipient) => ({
|
||||
value: recipient.email,
|
||||
label: recipient.name ? `${recipient.name} (${recipient.email})` : recipient.email,
|
||||
}));
|
||||
|
||||
const onSelectionChange = (selected: Option[]) => {
|
||||
const updatedRecipients = selected.map((option) => {
|
||||
const existingRecipient = listValues.find((r) => r.email === option.value);
|
||||
const member = members?.find((m) => m.email === option.value);
|
||||
|
||||
return {
|
||||
email: option.value,
|
||||
name: member?.name || option.value,
|
||||
role: existingRecipient?.role ?? RecipientRole.CC,
|
||||
};
|
||||
});
|
||||
|
||||
onChange(updatedRecipients);
|
||||
};
|
||||
|
||||
return (
|
||||
<MultiSelect
|
||||
commandProps={{ label: _(msg`Select recipients`) }}
|
||||
options={options}
|
||||
value={value}
|
||||
onChange={onSelectionChange}
|
||||
placeholder={_(msg`Select recipients`)}
|
||||
hideClearAllButton
|
||||
hidePlaceholderWhenSelected
|
||||
loadingIndicator={isLoading ? <p className="text-center text-sm">Loading...</p> : undefined}
|
||||
emptyIndicator={<p className="text-center text-sm">No members found</p>}
|
||||
/>
|
||||
);
|
||||
};
|
||||
+2
-2
@@ -90,7 +90,7 @@ export const DocumentSigningAuthPasskey = ({
|
||||
preferredPasskeyId: passkeyId,
|
||||
});
|
||||
|
||||
const authenticationResponse = await startAuthentication(options);
|
||||
const authenticationResponse = await startAuthentication({ optionsJSON: options });
|
||||
|
||||
await onReauthFormSubmit({
|
||||
type: DocumentAuth.PASSKEY,
|
||||
@@ -146,7 +146,7 @@ export const DocumentSigningAuthPasskey = ({
|
||||
if (passkeyData.isInitialLoading || (passkeyData.isError && passkeyData.passkeys.length === 0)) {
|
||||
return (
|
||||
<div className="flex h-28 items-center justify-center">
|
||||
<Loader className="text-muted-foreground h-6 w-6 animate-spin" />
|
||||
<Loader className="h-6 w-6 animate-spin text-muted-foreground" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
+3
-1
@@ -341,7 +341,9 @@ export const EnvelopeEditorSettingsDialog = ({
|
||||
{/* Sidebar. */}
|
||||
<div className="flex w-80 flex-col border-r bg-accent/20">
|
||||
<DialogHeader className="p-6 pb-4">
|
||||
<DialogTitle>Document Settings</DialogTitle>
|
||||
<DialogTitle>
|
||||
<Trans>Document Settings</Trans>
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<nav className="col-span-12 mb-8 flex flex-wrap items-center justify-start gap-x-2 gap-y-4 px-4 md:col-span-3 md:w-full md:flex-col md:items-start md:gap-y-2">
|
||||
|
||||
@@ -306,7 +306,7 @@ export const EnvelopeEditorUploadPage = () => {
|
||||
ref={provided.innerRef}
|
||||
{...provided.draggableProps}
|
||||
style={provided.draggableProps.style}
|
||||
className={`bg-accent/50 flex items-center justify-between rounded-lg p-3 transition-shadow ${
|
||||
className={`flex items-center justify-between rounded-lg bg-accent/50 p-3 transition-shadow ${
|
||||
snapshot.isDragging ? 'shadow-md' : ''
|
||||
}`}
|
||||
>
|
||||
@@ -332,7 +332,7 @@ export const EnvelopeEditorUploadPage = () => {
|
||||
<p className="text-sm font-medium">{localFile.title}</p>
|
||||
)}
|
||||
|
||||
<div className="text-muted-foreground text-xs">
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{localFile.isUploading ? (
|
||||
<Trans>Uploading</Trans>
|
||||
) : localFile.isError ? (
|
||||
@@ -345,13 +345,13 @@ export const EnvelopeEditorUploadPage = () => {
|
||||
<div className="flex items-center space-x-2">
|
||||
{localFile.isUploading && (
|
||||
<div className="flex h-6 w-10 items-center justify-center">
|
||||
<Loader2 className="text-muted-foreground h-4 w-4 animate-spin" />
|
||||
<Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{localFile.isError && (
|
||||
<div className="flex h-6 w-10 items-center justify-center">
|
||||
<FileWarningIcon className="text-destructive h-4 w-4" />
|
||||
<FileWarningIcon className="h-4 w-4 text-destructive" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -21,17 +21,17 @@ export default function DocumentEditSkeleton() {
|
||||
</div>
|
||||
|
||||
<div className="mt-4 grid h-[80vh] max-h-[60rem] w-full grid-cols-12 gap-x-8">
|
||||
<div className="dark:bg-background border-border col-span-12 rounded-xl border-2 bg-white/50 p-2 before:rounded-xl lg:col-span-6 xl:col-span-7">
|
||||
<div className="col-span-12 rounded-xl border-2 border-border bg-white/50 p-2 before:rounded-xl lg:col-span-6 xl:col-span-7 dark:bg-background">
|
||||
<div className="flex h-[80vh] max-h-[60rem] flex-col items-center justify-center">
|
||||
<Loader className="text-documenso h-12 w-12 animate-spin" />
|
||||
<Loader className="h-12 w-12 animate-spin text-documenso" />
|
||||
|
||||
<p className="text-muted-foreground mt-4">
|
||||
<p className="mt-4 text-muted-foreground">
|
||||
<Trans>Loading document...</Trans>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-background border-border col-span-12 rounded-xl border-2 before:rounded-xl lg:col-span-6 xl:col-span-5" />
|
||||
<div className="col-span-12 rounded-xl border-2 border-border bg-background before:rounded-xl lg:col-span-6 xl:col-span-5" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { DateTime } from 'luxon';
|
||||
import type { DateTimeFormatOptions } from 'luxon';
|
||||
import { useSearchParams } from 'react-router';
|
||||
import { UAParser } from 'ua-parser-js';
|
||||
|
||||
import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params';
|
||||
import type { TDocumentAuditLog } from '@documenso/lib/types/document-audit-logs';
|
||||
import { ZUrlSearchParamsSchema } from '@documenso/lib/types/search-params';
|
||||
import { formatDocumentAuditLogAction } from '@documenso/lib/utils/document-audit-logs';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { CopyTextButton } from '@documenso/ui/components/common/copy-text-button';
|
||||
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 { DataTablePagination } from '@documenso/ui/primitives/data-table-pagination';
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@documenso/ui/primitives/dialog';
|
||||
import { Skeleton } from '@documenso/ui/primitives/skeleton';
|
||||
import { TableCell } from '@documenso/ui/primitives/table';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
|
||||
export type AdminDocumentLogsTableProps = {
|
||||
envelopeId: string;
|
||||
};
|
||||
|
||||
const dateFormat: DateTimeFormatOptions = {
|
||||
...DateTime.DATETIME_SHORT,
|
||||
hourCycle: 'h12',
|
||||
};
|
||||
|
||||
export const AdminDocumentLogsTable = ({ envelopeId }: AdminDocumentLogsTableProps) => {
|
||||
const { _, i18n } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
const [searchParams] = useSearchParams();
|
||||
const updateSearchParams = useUpdateSearchParams();
|
||||
|
||||
const [selectedAuditLog, setSelectedAuditLog] = useState<TDocumentAuditLog | null>(null);
|
||||
|
||||
const parsedSearchParams = ZUrlSearchParamsSchema.parse(Object.fromEntries(searchParams ?? []));
|
||||
|
||||
const { data, isLoading, isLoadingError } = trpc.admin.document.findAuditLogs.useQuery(
|
||||
{
|
||||
envelopeId,
|
||||
page: parsedSearchParams.page,
|
||||
perPage: parsedSearchParams.perPage,
|
||||
},
|
||||
{
|
||||
placeholderData: (previousData) => previousData,
|
||||
},
|
||||
);
|
||||
|
||||
const onPaginationChange = (page: number, perPage: number) => {
|
||||
updateSearchParams({
|
||||
page,
|
||||
perPage,
|
||||
});
|
||||
};
|
||||
|
||||
const results = data ?? {
|
||||
data: [],
|
||||
perPage: 10,
|
||||
currentPage: 1,
|
||||
totalPages: 1,
|
||||
};
|
||||
|
||||
const columns = useMemo(() => {
|
||||
const parser = new UAParser();
|
||||
|
||||
return [
|
||||
{
|
||||
header: _(msg`Time`),
|
||||
accessorKey: 'createdAt',
|
||||
cell: ({ row }) => i18n.date(row.original.createdAt, dateFormat),
|
||||
},
|
||||
{
|
||||
header: _(msg`User`),
|
||||
accessorKey: 'name',
|
||||
cell: ({ row }) =>
|
||||
row.original.name || row.original.email ? (
|
||||
<div>
|
||||
{row.original.name && (
|
||||
<p className="truncate" title={row.original.name}>
|
||||
{row.original.name}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{row.original.email && (
|
||||
<p className="truncate" title={row.original.email}>
|
||||
{row.original.email}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<p>N/A</p>
|
||||
),
|
||||
},
|
||||
{
|
||||
header: _(msg`Action`),
|
||||
accessorKey: 'type',
|
||||
cell: ({ row }) => (
|
||||
<span>{formatDocumentAuditLogAction(i18n, row.original).description}</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
header: _(msg`IP Address`),
|
||||
accessorKey: 'ipAddress',
|
||||
},
|
||||
{
|
||||
header: _(msg`Browser`),
|
||||
cell: ({ row }) => {
|
||||
if (!row.original.userAgent) {
|
||||
return 'N/A';
|
||||
}
|
||||
|
||||
parser.setUA(row.original.userAgent);
|
||||
|
||||
const result = parser.getResult();
|
||||
|
||||
return result.browser.name ?? 'N/A';
|
||||
},
|
||||
},
|
||||
{
|
||||
header: '',
|
||||
id: 'actions',
|
||||
cell: ({ row }) => (
|
||||
<Button variant="link" size="sm" onClick={() => setSelectedAuditLog(row.original)}>
|
||||
<Trans>View JSON</Trans>
|
||||
</Button>
|
||||
),
|
||||
},
|
||||
] satisfies DataTableColumnDef<(typeof results)['data'][number]>[];
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<DataTable
|
||||
columns={columns}
|
||||
data={results.data}
|
||||
perPage={results.perPage}
|
||||
currentPage={results.currentPage}
|
||||
totalPages={results.totalPages}
|
||||
onPaginationChange={onPaginationChange}
|
||||
error={{
|
||||
enable: isLoadingError,
|
||||
}}
|
||||
skeleton={{
|
||||
enable: isLoading,
|
||||
rows: 3,
|
||||
component: (
|
||||
<>
|
||||
<TableCell>
|
||||
<Skeleton className="h-4 w-12 rounded-full" />
|
||||
</TableCell>
|
||||
<TableCell className="w-1/2 py-4 pr-4">
|
||||
<div className="ml-2 flex flex-grow flex-col">
|
||||
<Skeleton className="h-4 w-1/3 max-w-[8rem]" />
|
||||
<Skeleton className="mt-1 h-4 w-1/2 max-w-[12rem]" />
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Skeleton className="h-4 w-12 rounded-full" />
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Skeleton className="h-4 w-10 rounded-full" />
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Skeleton className="h-4 w-10 rounded-full" />
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Skeleton className="h-4 w-8 rounded-full" />
|
||||
</TableCell>
|
||||
</>
|
||||
),
|
||||
}}
|
||||
>
|
||||
{(table) => <DataTablePagination additionalInformation="VisibleCount" table={table} />}
|
||||
</DataTable>
|
||||
|
||||
<Dialog open={selectedAuditLog !== null} onOpenChange={() => setSelectedAuditLog(null)}>
|
||||
<DialogContent className="max-w-2xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
<Trans>Audit Log Details</Trans>
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
{selectedAuditLog && (
|
||||
<div className="group relative">
|
||||
<div className="absolute right-2 top-2 opacity-0 transition-opacity group-hover:opacity-100">
|
||||
<CopyTextButton
|
||||
value={JSON.stringify(selectedAuditLog, null, 2)}
|
||||
onCopySuccess={() => toast({ title: _(msg`Copied to clipboard`) })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<pre className="max-h-[60vh] overflow-auto whitespace-pre-wrap break-all rounded-lg border border-border bg-muted/50 p-4 font-mono text-xs leading-relaxed text-foreground">
|
||||
{JSON.stringify(selectedAuditLog, null, 2)}
|
||||
</pre>
|
||||
</div>
|
||||
)}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -6,6 +6,7 @@ import { DateTime } from 'luxon';
|
||||
import { Link, redirect } from 'react-router';
|
||||
|
||||
import { unsafeGetEntireEnvelope } from '@documenso/lib/server-only/admin/get-entire-document';
|
||||
import { mapSecondaryIdToDocumentId } from '@documenso/lib/utils/envelope';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import {
|
||||
Accordion,
|
||||
@@ -26,6 +27,7 @@ import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
import { AdminDocumentDeleteDialog } from '~/components/dialogs/admin-document-delete-dialog';
|
||||
import { DocumentStatus } from '~/components/general/document/document-status';
|
||||
import { AdminDocumentJobsTable } from '~/components/tables/admin-document-jobs-table';
|
||||
import { AdminDocumentLogsTable } from '~/components/tables/admin-document-logs-table';
|
||||
import { AdminDocumentRecipientItemTable } from '~/components/tables/admin-document-recipient-item-table';
|
||||
|
||||
import type { Route } from './+types/documents.$id';
|
||||
@@ -87,6 +89,10 @@ export default function AdminDocumentDetailsPage({ loaderData }: Route.Component
|
||||
</div>
|
||||
|
||||
<div className="mt-4 text-sm text-muted-foreground">
|
||||
<div>
|
||||
<Trans>Document ID</Trans>: {mapSecondaryIdToDocumentId(envelope.secondaryId)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Trans>Created on</Trans>: {i18n.date(envelope.createdAt, DateTime.DATETIME_MED)}
|
||||
</div>
|
||||
@@ -156,6 +162,9 @@ export default function AdminDocumentDetailsPage({ loaderData }: Route.Component
|
||||
<Badge size="small" variant="neutral">
|
||||
{recipient.email}
|
||||
</Badge>
|
||||
<Badge size="small" variant="secondary">
|
||||
{recipient.role}
|
||||
</Badge>
|
||||
</div>
|
||||
</AccordionTrigger>
|
||||
|
||||
@@ -175,6 +184,22 @@ export default function AdminDocumentDetailsPage({ loaderData }: Route.Component
|
||||
|
||||
<hr className="my-4" />
|
||||
|
||||
<Accordion type="single" collapsible className="w-full">
|
||||
<AccordionItem value="audit-logs" className="rounded-lg border">
|
||||
<AccordionTrigger className="px-4">
|
||||
<h2 className="text-lg font-semibold">
|
||||
<Trans>Audit Logs</Trans>
|
||||
</h2>
|
||||
</AccordionTrigger>
|
||||
|
||||
<AccordionContent className="border-t px-4 pt-4">
|
||||
<AdminDocumentLogsTable envelopeId={envelope.id} />
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
|
||||
<hr className="my-4" />
|
||||
|
||||
{envelope && <AdminDocumentDeleteDialog envelopeId={envelope.id} />}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -57,6 +57,7 @@ export default function OrganisationSettingsDocumentPage() {
|
||||
includeSigningCertificate,
|
||||
includeAuditLog,
|
||||
signatureTypes,
|
||||
defaultRecipients,
|
||||
delegateDocumentOwnership,
|
||||
aiFeaturesEnabled,
|
||||
} = data;
|
||||
@@ -83,6 +84,7 @@ export default function OrganisationSettingsDocumentPage() {
|
||||
includeSenderDetails,
|
||||
includeSigningCertificate,
|
||||
includeAuditLog,
|
||||
defaultRecipients,
|
||||
typedSignatureEnabled: signatureTypes.includes(DocumentSignatureType.TYPE),
|
||||
uploadSignatureEnabled: signatureTypes.includes(DocumentSignatureType.UPLOAD),
|
||||
drawSignatureEnabled: signatureTypes.includes(DocumentSignatureType.DRAW),
|
||||
|
||||
@@ -106,7 +106,7 @@ export default function DocumentEditPage() {
|
||||
/>
|
||||
|
||||
{recipients.length > 0 && (
|
||||
<div className="text-muted-foreground flex items-center">
|
||||
<div className="flex items-center text-muted-foreground">
|
||||
<Users2 className="mr-2 h-5 w-5" />
|
||||
|
||||
<StackAvatarsWithTooltip
|
||||
|
||||
@@ -50,6 +50,7 @@ export default function TeamsSettingsPage() {
|
||||
includeSigningCertificate,
|
||||
includeAuditLog,
|
||||
signatureTypes,
|
||||
defaultRecipients,
|
||||
delegateDocumentOwnership,
|
||||
aiFeaturesEnabled,
|
||||
} = data;
|
||||
@@ -64,6 +65,7 @@ export default function TeamsSettingsPage() {
|
||||
includeSenderDetails,
|
||||
includeSigningCertificate,
|
||||
includeAuditLog,
|
||||
defaultRecipients,
|
||||
aiFeaturesEnabled,
|
||||
...(signatureTypes.length === 0
|
||||
? {
|
||||
|
||||
@@ -36,16 +36,16 @@
|
||||
"@lingui/react": "^5.6.0",
|
||||
"@oslojs/crypto": "^1.0.1",
|
||||
"@oslojs/encoding": "^1.1.0",
|
||||
"@react-router/node": "^7.9.6",
|
||||
"@react-router/serve": "^7.9.6",
|
||||
"@simplewebauthn/browser": "^9.0.1",
|
||||
"@simplewebauthn/server": "^9.0.3",
|
||||
"@react-router/node": "^7.12.0",
|
||||
"@react-router/serve": "^7.12.0",
|
||||
"@simplewebauthn/browser": "^13.2.2",
|
||||
"@simplewebauthn/server": "^13.2.2",
|
||||
"@tanstack/react-query": "5.90.10",
|
||||
"autoprefixer": "^10.4.22",
|
||||
"colord": "^2.9.3",
|
||||
"content-disposition": "^1.0.1",
|
||||
"framer-motion": "^12.23.24",
|
||||
"hono": "4.10.6",
|
||||
"hono": "4.11.4",
|
||||
"hono-rate-limiter": "^0.4.2",
|
||||
"hono-react-router-adapter": "^0.6.5",
|
||||
"input-otp": "^1.4.2",
|
||||
@@ -65,7 +65,7 @@
|
||||
"react-hotkeys-hook": "^4.6.2",
|
||||
"react-icons": "^5.5.0",
|
||||
"react-rnd": "^10.5.2",
|
||||
"react-router": "^7.9.6",
|
||||
"react-router": "^7.12.0",
|
||||
"recharts": "^2.15.4",
|
||||
"remeda": "^2.32.0",
|
||||
"remix-themes": "^2.0.4",
|
||||
@@ -81,14 +81,13 @@
|
||||
"@babel/preset-typescript": "^7.28.5",
|
||||
"@lingui/babel-plugin-lingui-macro": "^5.6.0",
|
||||
"@lingui/vite-plugin": "^5.6.0",
|
||||
"@react-router/dev": "^7.9.6",
|
||||
"@react-router/remix-routes-option-adapter": "^7.9.6",
|
||||
"@react-router/dev": "^7.12.0",
|
||||
"@react-router/remix-routes-option-adapter": "^7.12.0",
|
||||
"@rollup/plugin-babel": "^6.1.0",
|
||||
"@rollup/plugin-commonjs": "^28.0.9",
|
||||
"@rollup/plugin-json": "^6.1.0",
|
||||
"@rollup/plugin-node-resolve": "^16.0.3",
|
||||
"@rollup/plugin-typescript": "^12.3.0",
|
||||
"@simplewebauthn/types": "^9.0.1",
|
||||
"@types/content-disposition": "^0.5.9",
|
||||
"@types/formidable": "^3.4.6",
|
||||
"@types/luxon": "^3.7.1",
|
||||
|
||||
Binary file not shown.
Generated
+296
-456
File diff suppressed because it is too large
Load Diff
+3
-3
@@ -64,7 +64,7 @@
|
||||
"lint-staged": "^16.2.7",
|
||||
"nanoid": "^5.1.6",
|
||||
"nodemailer": "^7.0.10",
|
||||
"pdfjs-dist": "5.4.449",
|
||||
"pdfjs-dist": "5.4.296",
|
||||
"pino": "^9.14.0",
|
||||
"pino-pretty": "^13.1.2",
|
||||
"playwright": "1.56.1",
|
||||
@@ -99,8 +99,8 @@
|
||||
"zod": "^3.25.76"
|
||||
},
|
||||
"overrides": {
|
||||
"pdfjs-dist": "5.4.449",
|
||||
"pdfjs-dist": "5.4.296",
|
||||
"typescript": "5.6.2",
|
||||
"zod": "^3.25.76"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -55,6 +55,8 @@ export const ApiContractV1 = c.router(
|
||||
404: ZUnsuccessfulResponseSchema,
|
||||
},
|
||||
summary: 'Get all documents',
|
||||
deprecated: true,
|
||||
description: 'Deprecated. Please migrate to the v2 API.',
|
||||
},
|
||||
|
||||
getDocument: {
|
||||
@@ -66,6 +68,8 @@ export const ApiContractV1 = c.router(
|
||||
404: ZUnsuccessfulResponseSchema,
|
||||
},
|
||||
summary: 'Get a single document',
|
||||
deprecated: true,
|
||||
description: 'Deprecated. Please migrate to the v2 API.',
|
||||
},
|
||||
|
||||
downloadSignedDocument: {
|
||||
@@ -78,6 +82,8 @@ export const ApiContractV1 = c.router(
|
||||
404: ZUnsuccessfulResponseSchema,
|
||||
},
|
||||
summary: 'Download a signed document when the storage transport is S3',
|
||||
deprecated: true,
|
||||
description: 'Deprecated. Please migrate to the v2 API.',
|
||||
},
|
||||
|
||||
createDocument: {
|
||||
@@ -90,6 +96,8 @@ export const ApiContractV1 = c.router(
|
||||
404: ZUnsuccessfulResponseSchema,
|
||||
},
|
||||
summary: 'Upload a new document and get a presigned URL',
|
||||
deprecated: true,
|
||||
description: 'Deprecated. Please migrate to the v2 API.',
|
||||
},
|
||||
|
||||
createTemplate: {
|
||||
@@ -102,6 +110,8 @@ export const ApiContractV1 = c.router(
|
||||
404: ZUnsuccessfulResponseSchema,
|
||||
},
|
||||
summary: 'Create a new template and get a presigned URL',
|
||||
deprecated: true,
|
||||
description: 'Deprecated. Please migrate to the v2 API.',
|
||||
},
|
||||
|
||||
deleteTemplate: {
|
||||
@@ -114,6 +124,8 @@ export const ApiContractV1 = c.router(
|
||||
404: ZUnsuccessfulResponseSchema,
|
||||
},
|
||||
summary: 'Delete a template',
|
||||
deprecated: true,
|
||||
description: 'Deprecated. Please migrate to the v2 API.',
|
||||
},
|
||||
|
||||
getTemplate: {
|
||||
@@ -125,6 +137,8 @@ export const ApiContractV1 = c.router(
|
||||
404: ZUnsuccessfulResponseSchema,
|
||||
},
|
||||
summary: 'Get a single template',
|
||||
deprecated: true,
|
||||
description: 'Deprecated. Please migrate to the v2 API.',
|
||||
},
|
||||
|
||||
getTemplates: {
|
||||
@@ -137,6 +151,8 @@ export const ApiContractV1 = c.router(
|
||||
404: ZUnsuccessfulResponseSchema,
|
||||
},
|
||||
summary: 'Get all templates',
|
||||
deprecated: true,
|
||||
description: 'Deprecated. Please migrate to the v2 API.',
|
||||
},
|
||||
|
||||
createDocumentFromTemplate: {
|
||||
@@ -150,7 +166,7 @@ export const ApiContractV1 = c.router(
|
||||
},
|
||||
summary: 'Create a new document from an existing template',
|
||||
deprecated: true,
|
||||
description: `This has been deprecated in favour of "/api/v1/templates/:templateId/generate-document". You may face unpredictable behavior using this endpoint as it is no longer maintained.`,
|
||||
description: 'Deprecated. Please migrate to the v2 API.',
|
||||
},
|
||||
|
||||
generateDocumentFromTemplate: {
|
||||
@@ -165,8 +181,9 @@ export const ApiContractV1 = c.router(
|
||||
500: ZUnsuccessfulResponseSchema,
|
||||
},
|
||||
summary: 'Create a new document from an existing template',
|
||||
deprecated: true,
|
||||
description:
|
||||
'Create a new document from an existing template. Passing in values for title and meta will override the original values defined in the template. If you do not pass in values for recipients, it will use the values defined in the template.',
|
||||
'Deprecated. Please migrate to the v2 API.\n\nCreate a new document from an existing template. Passing in values for title and meta will override the original values defined in the template. If you do not pass in values for recipients, it will use the values defined in the template.',
|
||||
},
|
||||
|
||||
sendDocument: {
|
||||
@@ -181,9 +198,9 @@ export const ApiContractV1 = c.router(
|
||||
500: ZUnsuccessfulResponseSchema,
|
||||
},
|
||||
summary: 'Send a document for signing',
|
||||
// I'm aware this should be in the variable itself, which it is, however it's difficult for users to find in our current UI.
|
||||
deprecated: true,
|
||||
description:
|
||||
'Notes\n\n`sendEmail` - Whether to send an email to the recipients asking them to action the document. If you disable this, you will need to manually distribute the document to the recipients using the generated signing links. Defaults to true',
|
||||
'Deprecated. Please migrate to the v2 API.\n\nNotes\n\n`sendEmail` - Whether to send an email to the recipients asking them to action the document. If you disable this, you will need to manually distribute the document to the recipients using the generated signing links. Defaults to true',
|
||||
},
|
||||
|
||||
resendDocument: {
|
||||
@@ -198,6 +215,8 @@ export const ApiContractV1 = c.router(
|
||||
500: ZUnsuccessfulResponseSchema,
|
||||
},
|
||||
summary: 'Re-send a document for signing',
|
||||
deprecated: true,
|
||||
description: 'Deprecated. Please migrate to the v2 API.',
|
||||
},
|
||||
|
||||
deleteDocument: {
|
||||
@@ -210,6 +229,8 @@ export const ApiContractV1 = c.router(
|
||||
404: ZUnsuccessfulResponseSchema,
|
||||
},
|
||||
summary: 'Delete a document',
|
||||
deprecated: true,
|
||||
description: 'Deprecated. Please migrate to the v2 API.',
|
||||
},
|
||||
|
||||
createRecipient: {
|
||||
@@ -224,6 +245,8 @@ export const ApiContractV1 = c.router(
|
||||
500: ZUnsuccessfulResponseSchema,
|
||||
},
|
||||
summary: 'Create a recipient for a document',
|
||||
deprecated: true,
|
||||
description: 'Deprecated. Please migrate to the v2 API.',
|
||||
},
|
||||
|
||||
updateRecipient: {
|
||||
@@ -238,6 +261,8 @@ export const ApiContractV1 = c.router(
|
||||
500: ZUnsuccessfulResponseSchema,
|
||||
},
|
||||
summary: 'Update a recipient for a document',
|
||||
deprecated: true,
|
||||
description: 'Deprecated. Please migrate to the v2 API.',
|
||||
},
|
||||
|
||||
deleteRecipient: {
|
||||
@@ -252,6 +277,8 @@ export const ApiContractV1 = c.router(
|
||||
500: ZUnsuccessfulResponseSchema,
|
||||
},
|
||||
summary: 'Delete a recipient from a document',
|
||||
deprecated: true,
|
||||
description: 'Deprecated. Please migrate to the v2 API.',
|
||||
},
|
||||
|
||||
createField: {
|
||||
@@ -266,6 +293,8 @@ export const ApiContractV1 = c.router(
|
||||
500: ZUnsuccessfulResponseSchema,
|
||||
},
|
||||
summary: 'Create a field for a document',
|
||||
deprecated: true,
|
||||
description: 'Deprecated. Please migrate to the v2 API.',
|
||||
},
|
||||
|
||||
updateField: {
|
||||
@@ -280,6 +309,8 @@ export const ApiContractV1 = c.router(
|
||||
500: ZUnsuccessfulResponseSchema,
|
||||
},
|
||||
summary: 'Update a field for a document',
|
||||
deprecated: true,
|
||||
description: 'Deprecated. Please migrate to the v2 API.',
|
||||
},
|
||||
|
||||
deleteField: {
|
||||
@@ -294,6 +325,8 @@ export const ApiContractV1 = c.router(
|
||||
500: ZUnsuccessfulResponseSchema,
|
||||
},
|
||||
summary: 'Delete a field from a document',
|
||||
deprecated: true,
|
||||
description: 'Deprecated. Please migrate to the v2 API.',
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -41,7 +41,7 @@ import {
|
||||
ZTextFieldMeta,
|
||||
} from '@documenso/lib/types/field-meta';
|
||||
import { getFileServerSide } from '@documenso/lib/universal/upload/get-file.server';
|
||||
import { putPdfFileServerSide } from '@documenso/lib/universal/upload/put-file.server';
|
||||
import { putNormalizedPdfFileServerSide } from '@documenso/lib/universal/upload/put-file.server';
|
||||
import {
|
||||
getPresignGetUrl,
|
||||
getPresignPostUrl,
|
||||
@@ -822,7 +822,7 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
|
||||
formValues: body.formValues,
|
||||
});
|
||||
|
||||
const newDocumentData = await putPdfFileServerSide({
|
||||
const newDocumentData = await putNormalizedPdfFileServerSide({
|
||||
name: fileName,
|
||||
type: 'application/pdf',
|
||||
arrayBuffer: async () => Promise.resolve(prefilled),
|
||||
@@ -911,61 +911,13 @@ export const ApiContractV1Implementation = tsr.router(ApiContractV1, {
|
||||
title: body.title,
|
||||
...body.meta,
|
||||
},
|
||||
formValues: body.formValues,
|
||||
requestMetadata: metadata,
|
||||
});
|
||||
} catch (err) {
|
||||
return AppError.toRestAPIError(err);
|
||||
}
|
||||
|
||||
if (envelope.envelopeItems.length !== 1) {
|
||||
throw new Error('API V1 does not support envelopes');
|
||||
}
|
||||
|
||||
const firstEnvelopeDocumentData = await prisma.envelopeItem.findFirstOrThrow({
|
||||
where: {
|
||||
envelopeId: envelope.id,
|
||||
},
|
||||
include: {
|
||||
documentData: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (body.formValues) {
|
||||
const fileName = envelope.title.endsWith('.pdf') ? envelope.title : `${envelope.title}.pdf`;
|
||||
|
||||
const pdf = await getFileServerSide(firstEnvelopeDocumentData.documentData);
|
||||
|
||||
const prefilled = await insertFormValuesInPdf({
|
||||
pdf: Buffer.from(pdf),
|
||||
formValues: body.formValues,
|
||||
});
|
||||
|
||||
const newDocumentData = await putPdfFileServerSide({
|
||||
name: fileName,
|
||||
type: 'application/pdf',
|
||||
arrayBuffer: async () => Promise.resolve(prefilled),
|
||||
});
|
||||
|
||||
await prisma.envelope.update({
|
||||
where: {
|
||||
id: envelope.id,
|
||||
},
|
||||
data: {
|
||||
formValues: body.formValues,
|
||||
envelopeItems: {
|
||||
update: {
|
||||
where: {
|
||||
id: firstEnvelopeDocumentData.id,
|
||||
},
|
||||
data: {
|
||||
documentDataId: newDocumentData.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (body.authOptions) {
|
||||
await prisma.envelope.update({
|
||||
where: {
|
||||
|
||||
@@ -11,7 +11,8 @@ export const OpenAPIV1 = Object.assign(
|
||||
info: {
|
||||
title: 'Documenso API',
|
||||
version: '1.0.0',
|
||||
description: 'The Documenso API for retrieving, creating, updating and deleting documents.',
|
||||
description:
|
||||
'[DEPRECATED] The Documenso API for retrieving, creating, updating and deleting documents. Please migrate to the v2 API.',
|
||||
},
|
||||
servers: [
|
||||
{
|
||||
|
||||
@@ -0,0 +1,848 @@
|
||||
import { PDFDocument } from '@cantoo/pdf-lib';
|
||||
import { expect, test } from '@playwright/test';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
||||
import { createApiToken } from '@documenso/lib/server-only/public-api/create-api-token';
|
||||
import { getFileServerSide } from '@documenso/lib/universal/upload/get-file.server';
|
||||
import { mapSecondaryIdToTemplateId } from '@documenso/lib/utils/envelope';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { EnvelopeType, RecipientRole } from '@documenso/prisma/client';
|
||||
import { seedUser } from '@documenso/prisma/seed/users';
|
||||
import type {
|
||||
TCreateEnvelopePayload,
|
||||
TCreateEnvelopeResponse,
|
||||
} from '@documenso/trpc/server/envelope-router/create-envelope.types';
|
||||
|
||||
const WEBAPP_BASE_URL = NEXT_PUBLIC_WEBAPP_URL();
|
||||
const baseUrl = `${WEBAPP_BASE_URL}/api/v2-beta`;
|
||||
|
||||
// Form field names in the test PDF
|
||||
const FORM_FIELDS = {
|
||||
TEXT_FIELD: 'test_text_field',
|
||||
COMPANY_NAME: 'company_name',
|
||||
CHECKBOX: 'accept_terms',
|
||||
DROPDOWN: 'country',
|
||||
} as const;
|
||||
|
||||
// Test values to insert into form fields
|
||||
const TEST_FORM_VALUES = {
|
||||
[FORM_FIELDS.TEXT_FIELD]: 'Hello World',
|
||||
[FORM_FIELDS.COMPANY_NAME]: 'Documenso Inc.',
|
||||
[FORM_FIELDS.CHECKBOX]: true,
|
||||
[FORM_FIELDS.DROPDOWN]: 'Germany',
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper to check if a PDF has interactive form fields.
|
||||
* Returns true if the PDF has form fields, false if they've been flattened.
|
||||
*/
|
||||
async function pdfHasFormFields(pdfBuffer: Uint8Array): Promise<boolean> {
|
||||
const pdfDoc = await PDFDocument.load(pdfBuffer);
|
||||
|
||||
const form = pdfDoc.getForm();
|
||||
const fields = form.getFields();
|
||||
|
||||
return fields.length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to get form field names from a PDF.
|
||||
*/
|
||||
async function getPdfFormFieldNames(pdfBuffer: Uint8Array): Promise<string[]> {
|
||||
const pdfDoc = await PDFDocument.load(pdfBuffer);
|
||||
|
||||
const form = pdfDoc.getForm();
|
||||
const fields = form.getFields();
|
||||
|
||||
return fields.map((field) => field.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to get the value of a text field in a PDF.
|
||||
*/
|
||||
async function getPdfTextFieldValue(
|
||||
pdfBuffer: Uint8Array,
|
||||
fieldName: string,
|
||||
): Promise<string | undefined> {
|
||||
const pdfDoc = await PDFDocument.load(pdfBuffer);
|
||||
|
||||
const form = pdfDoc.getForm();
|
||||
|
||||
try {
|
||||
const textField = form.getTextField(fieldName);
|
||||
|
||||
return textField.getText() ?? '';
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
test.describe.configure({
|
||||
mode: 'parallel',
|
||||
});
|
||||
|
||||
test.describe('Form Flattening', () => {
|
||||
const formFieldsPdf = fs.readFileSync(
|
||||
path.join(__dirname, '../../../../assets/form-fields-test.pdf'),
|
||||
);
|
||||
|
||||
test.describe('Envelope Creation (DOCUMENT type)', () => {
|
||||
test('should flatten form fields when creating a DOCUMENT envelope with formValues', async ({
|
||||
request,
|
||||
}) => {
|
||||
const { user, team } = await seedUser();
|
||||
const { token } = await createApiToken({
|
||||
userId: user.id,
|
||||
teamId: team.id,
|
||||
tokenName: 'test',
|
||||
expiresIn: null,
|
||||
});
|
||||
|
||||
const payload: TCreateEnvelopePayload = {
|
||||
type: EnvelopeType.DOCUMENT,
|
||||
title: 'Document with Form Values',
|
||||
formValues: TEST_FORM_VALUES,
|
||||
};
|
||||
|
||||
const formData = new FormData();
|
||||
|
||||
formData.append('payload', JSON.stringify(payload));
|
||||
formData.append(
|
||||
'files',
|
||||
new File([formFieldsPdf], 'form-fields-test.pdf', { type: 'application/pdf' }),
|
||||
);
|
||||
|
||||
const res = await request.post(`${baseUrl}/envelope/create`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
multipart: formData,
|
||||
});
|
||||
|
||||
expect(res.ok()).toBeTruthy();
|
||||
expect(res.status()).toBe(200);
|
||||
|
||||
const response = (await res.json()) as TCreateEnvelopeResponse;
|
||||
|
||||
// Verify the envelope was created with the correct formValues
|
||||
const envelope = await prisma.envelope.findUniqueOrThrow({
|
||||
where: { id: response.id },
|
||||
include: {
|
||||
envelopeItems: {
|
||||
include: { documentData: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(envelope.formValues).toEqual(TEST_FORM_VALUES);
|
||||
expect(envelope.type).toBe(EnvelopeType.DOCUMENT);
|
||||
|
||||
// Get the PDF and verify form fields are flattened
|
||||
const documentData = envelope.envelopeItems[0].documentData;
|
||||
const pdfBuffer = await getFileServerSide(documentData);
|
||||
|
||||
const hasFormFields = await pdfHasFormFields(pdfBuffer);
|
||||
|
||||
expect(hasFormFields).toBe(false);
|
||||
});
|
||||
|
||||
test('should flatten form fields when creating a DOCUMENT envelope without formValues', async ({
|
||||
request,
|
||||
}) => {
|
||||
const { user, team } = await seedUser();
|
||||
const { token } = await createApiToken({
|
||||
userId: user.id,
|
||||
teamId: team.id,
|
||||
tokenName: 'test',
|
||||
expiresIn: null,
|
||||
});
|
||||
|
||||
const payload: TCreateEnvelopePayload = {
|
||||
type: EnvelopeType.DOCUMENT,
|
||||
title: 'Document without Form Values',
|
||||
// No formValues - but form should still be flattened for DOCUMENT type
|
||||
};
|
||||
|
||||
const formData = new FormData();
|
||||
|
||||
formData.append('payload', JSON.stringify(payload));
|
||||
formData.append(
|
||||
'files',
|
||||
new File([formFieldsPdf], 'form-fields-test.pdf', { type: 'application/pdf' }),
|
||||
);
|
||||
|
||||
const res = await request.post(`${baseUrl}/envelope/create`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
multipart: formData,
|
||||
});
|
||||
|
||||
expect(res.ok()).toBeTruthy();
|
||||
expect(res.status()).toBe(200);
|
||||
|
||||
const response = (await res.json()) as TCreateEnvelopeResponse;
|
||||
|
||||
const envelope = await prisma.envelope.findUniqueOrThrow({
|
||||
where: { id: response.id },
|
||||
include: {
|
||||
envelopeItems: {
|
||||
include: { documentData: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Get the PDF and verify form fields are flattened
|
||||
const documentData = envelope.envelopeItems[0].documentData;
|
||||
const pdfBuffer = await getFileServerSide(documentData);
|
||||
|
||||
const hasFormFields = await pdfHasFormFields(pdfBuffer);
|
||||
|
||||
expect(hasFormFields).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Template Creation (TEMPLATE type)', () => {
|
||||
test('should NOT flatten form fields when creating a TEMPLATE envelope', async ({
|
||||
request,
|
||||
}) => {
|
||||
const { user, team } = await seedUser();
|
||||
const { token } = await createApiToken({
|
||||
userId: user.id,
|
||||
teamId: team.id,
|
||||
tokenName: 'test',
|
||||
expiresIn: null,
|
||||
});
|
||||
|
||||
const payload: TCreateEnvelopePayload = {
|
||||
type: EnvelopeType.TEMPLATE,
|
||||
title: 'Template with Form Fields',
|
||||
// Note: formValues can be set but form should NOT be flattened for templates
|
||||
};
|
||||
|
||||
const formData = new FormData();
|
||||
|
||||
formData.append('payload', JSON.stringify(payload));
|
||||
formData.append(
|
||||
'files',
|
||||
new File([formFieldsPdf], 'form-fields-test.pdf', { type: 'application/pdf' }),
|
||||
);
|
||||
|
||||
const res = await request.post(`${baseUrl}/envelope/create`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
multipart: formData,
|
||||
});
|
||||
|
||||
expect(res.ok()).toBeTruthy();
|
||||
expect(res.status()).toBe(200);
|
||||
|
||||
const response = (await res.json()) as TCreateEnvelopeResponse;
|
||||
|
||||
const envelope = await prisma.envelope.findUniqueOrThrow({
|
||||
where: { id: response.id },
|
||||
include: {
|
||||
envelopeItems: {
|
||||
include: { documentData: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(envelope.type).toBe(EnvelopeType.TEMPLATE);
|
||||
|
||||
// Get the PDF and verify form fields are NOT flattened
|
||||
const documentData = envelope.envelopeItems[0].documentData;
|
||||
const pdfBuffer = await getFileServerSide(documentData);
|
||||
|
||||
const hasFormFields = await pdfHasFormFields(pdfBuffer);
|
||||
|
||||
expect(hasFormFields).toBe(true);
|
||||
|
||||
// Verify the specific form fields still exist
|
||||
const fieldNames = await getPdfFormFieldNames(pdfBuffer);
|
||||
|
||||
expect(fieldNames).toContain(FORM_FIELDS.TEXT_FIELD);
|
||||
expect(fieldNames).toContain(FORM_FIELDS.COMPANY_NAME);
|
||||
expect(fieldNames).toContain(FORM_FIELDS.CHECKBOX);
|
||||
expect(fieldNames).toContain(FORM_FIELDS.DROPDOWN);
|
||||
});
|
||||
|
||||
test('should preserve form fields in template even when formValues are provided', async ({
|
||||
request,
|
||||
}) => {
|
||||
const { user, team } = await seedUser();
|
||||
const { token } = await createApiToken({
|
||||
userId: user.id,
|
||||
teamId: team.id,
|
||||
tokenName: 'test',
|
||||
expiresIn: null,
|
||||
});
|
||||
|
||||
const payload: TCreateEnvelopePayload = {
|
||||
type: EnvelopeType.TEMPLATE,
|
||||
title: 'Template with Form Values',
|
||||
formValues: TEST_FORM_VALUES,
|
||||
};
|
||||
|
||||
const formData = new FormData();
|
||||
|
||||
formData.append('payload', JSON.stringify(payload));
|
||||
formData.append(
|
||||
'files',
|
||||
new File([formFieldsPdf], 'form-fields-test.pdf', { type: 'application/pdf' }),
|
||||
);
|
||||
|
||||
const res = await request.post(`${baseUrl}/envelope/create`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
multipart: formData,
|
||||
});
|
||||
|
||||
expect(res.ok()).toBeTruthy();
|
||||
expect(res.status()).toBe(200);
|
||||
|
||||
const response = (await res.json()) as TCreateEnvelopeResponse;
|
||||
|
||||
const envelope = await prisma.envelope.findUniqueOrThrow({
|
||||
where: { id: response.id },
|
||||
include: {
|
||||
envelopeItems: {
|
||||
include: { documentData: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// formValues should be stored in the database
|
||||
expect(envelope.formValues).toEqual(TEST_FORM_VALUES);
|
||||
expect(envelope.type).toBe(EnvelopeType.TEMPLATE);
|
||||
|
||||
// But the PDF should still have interactive form fields
|
||||
const documentData = envelope.envelopeItems[0].documentData;
|
||||
const pdfBuffer = await getFileServerSide(documentData);
|
||||
|
||||
const hasFormFields = await pdfHasFormFields(pdfBuffer);
|
||||
|
||||
expect(hasFormFields).toBe(true);
|
||||
expect(await getPdfTextFieldValue(pdfBuffer, FORM_FIELDS.TEXT_FIELD)).toBe(
|
||||
TEST_FORM_VALUES[FORM_FIELDS.TEXT_FIELD],
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Document from Template', () => {
|
||||
test('should flatten form fields when creating document from template with formValues', async ({
|
||||
request,
|
||||
}) => {
|
||||
const { user, team } = await seedUser();
|
||||
const { token } = await createApiToken({
|
||||
userId: user.id,
|
||||
teamId: team.id,
|
||||
tokenName: 'test',
|
||||
expiresIn: null,
|
||||
});
|
||||
|
||||
// First, create a template via API
|
||||
const templatePayload: TCreateEnvelopePayload = {
|
||||
type: EnvelopeType.TEMPLATE,
|
||||
title: 'Template for Document Creation',
|
||||
recipients: [
|
||||
{
|
||||
email: 'recipient@example.com',
|
||||
name: 'Test Recipient',
|
||||
role: RecipientRole.SIGNER,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const templateFormData = new FormData();
|
||||
templateFormData.append('payload', JSON.stringify(templatePayload));
|
||||
templateFormData.append(
|
||||
'files',
|
||||
new File([formFieldsPdf], 'form-fields-test.pdf', { type: 'application/pdf' }),
|
||||
);
|
||||
|
||||
const templateRes = await request.post(`${baseUrl}/envelope/create`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
multipart: templateFormData,
|
||||
});
|
||||
|
||||
expect(templateRes.ok()).toBeTruthy();
|
||||
const templateResponse = (await templateRes.json()) as TCreateEnvelopeResponse;
|
||||
|
||||
// Verify template has form fields
|
||||
const template = await prisma.envelope.findUniqueOrThrow({
|
||||
where: { id: templateResponse.id },
|
||||
include: {
|
||||
envelopeItems: { include: { documentData: true } },
|
||||
recipients: true,
|
||||
},
|
||||
});
|
||||
|
||||
const templatePdfBuffer = await getFileServerSide(template.envelopeItems[0].documentData);
|
||||
|
||||
expect(await pdfHasFormFields(templatePdfBuffer)).toBe(true);
|
||||
|
||||
// Now create a document from the template with formValues
|
||||
const useTemplateRes = await request.post(`${WEBAPP_BASE_URL}/api/v2-beta/template/use`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
data: {
|
||||
templateId: mapSecondaryIdToTemplateId(template.secondaryId),
|
||||
recipients: [
|
||||
{
|
||||
id: template.recipients[0].id,
|
||||
email: 'recipient@example.com',
|
||||
name: 'Test Recipient',
|
||||
},
|
||||
],
|
||||
formValues: TEST_FORM_VALUES,
|
||||
},
|
||||
});
|
||||
|
||||
expect(useTemplateRes.ok()).toBeTruthy();
|
||||
expect(useTemplateRes.status()).toBe(200);
|
||||
|
||||
const documentResponse = await useTemplateRes.json();
|
||||
|
||||
// Get the created document
|
||||
const document = await prisma.envelope.findFirstOrThrow({
|
||||
where: {
|
||||
id: documentResponse.envelopeId,
|
||||
},
|
||||
include: {
|
||||
envelopeItems: { include: { documentData: true } },
|
||||
},
|
||||
});
|
||||
|
||||
expect(document.type).toBe(EnvelopeType.DOCUMENT);
|
||||
|
||||
// Verify form fields are flattened in the created document
|
||||
const documentPdfBuffer = await getFileServerSide(document.envelopeItems[0].documentData);
|
||||
const hasFormFields = await pdfHasFormFields(documentPdfBuffer);
|
||||
|
||||
expect(hasFormFields).toBe(false);
|
||||
});
|
||||
|
||||
test('should flatten form fields when creating document from template without formValues', async ({
|
||||
request,
|
||||
}) => {
|
||||
const { user, team } = await seedUser();
|
||||
const { token } = await createApiToken({
|
||||
userId: user.id,
|
||||
teamId: team.id,
|
||||
tokenName: 'test',
|
||||
expiresIn: null,
|
||||
});
|
||||
|
||||
// Create a template
|
||||
const templatePayload: TCreateEnvelopePayload = {
|
||||
type: EnvelopeType.TEMPLATE,
|
||||
title: 'Template without Form Values',
|
||||
recipients: [
|
||||
{
|
||||
email: 'recipient@example.com',
|
||||
name: 'Test Recipient',
|
||||
role: RecipientRole.SIGNER,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const templateFormData = new FormData();
|
||||
templateFormData.append('payload', JSON.stringify(templatePayload));
|
||||
templateFormData.append(
|
||||
'files',
|
||||
new File([formFieldsPdf], 'form-fields-test.pdf', { type: 'application/pdf' }),
|
||||
);
|
||||
|
||||
const templateRes = await request.post(`${baseUrl}/envelope/create`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
multipart: templateFormData,
|
||||
});
|
||||
|
||||
expect(templateRes.ok()).toBeTruthy();
|
||||
const templateResponse = (await templateRes.json()) as TCreateEnvelopeResponse;
|
||||
|
||||
const template = await prisma.envelope.findUniqueOrThrow({
|
||||
where: { id: templateResponse.id },
|
||||
include: {
|
||||
envelopeItems: { include: { documentData: true } },
|
||||
recipients: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Create document from template WITHOUT formValues
|
||||
const useTemplateRes = await request.post(`${WEBAPP_BASE_URL}/api/v2-beta/template/use`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
data: {
|
||||
templateId: mapSecondaryIdToTemplateId(template.secondaryId),
|
||||
recipients: [
|
||||
{
|
||||
id: template.recipients[0].id,
|
||||
email: 'recipient@example.com',
|
||||
name: 'Test Recipient',
|
||||
},
|
||||
],
|
||||
// No formValues provided
|
||||
},
|
||||
});
|
||||
|
||||
expect(useTemplateRes.ok()).toBeTruthy();
|
||||
const documentResponse = await useTemplateRes.json();
|
||||
|
||||
const document = await prisma.envelope.findFirstOrThrow({
|
||||
where: { id: documentResponse.envelopeId },
|
||||
include: {
|
||||
envelopeItems: { include: { documentData: true } },
|
||||
},
|
||||
});
|
||||
|
||||
expect(document.type).toBe(EnvelopeType.DOCUMENT);
|
||||
|
||||
// Form fields should still be flattened even without formValues
|
||||
const documentPdfBuffer = await getFileServerSide(document.envelopeItems[0].documentData);
|
||||
const hasFormFields = await pdfHasFormFields(documentPdfBuffer);
|
||||
|
||||
expect(hasFormFields).toBe(false);
|
||||
});
|
||||
|
||||
test('should use template formValues when creating document without override', async ({
|
||||
request,
|
||||
}) => {
|
||||
const { user, team } = await seedUser();
|
||||
const { token } = await createApiToken({
|
||||
userId: user.id,
|
||||
teamId: team.id,
|
||||
tokenName: 'test',
|
||||
expiresIn: null,
|
||||
});
|
||||
|
||||
// Create a template with formValues
|
||||
const templatePayload: TCreateEnvelopePayload = {
|
||||
type: EnvelopeType.TEMPLATE,
|
||||
title: 'Template with Default Form Values',
|
||||
formValues: {
|
||||
[FORM_FIELDS.TEXT_FIELD]: 'Default Value',
|
||||
[FORM_FIELDS.COMPANY_NAME]: 'Default Company',
|
||||
},
|
||||
recipients: [
|
||||
{
|
||||
email: 'recipient@example.com',
|
||||
name: 'Test Recipient',
|
||||
role: RecipientRole.SIGNER,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const templateFormData = new FormData();
|
||||
templateFormData.append('payload', JSON.stringify(templatePayload));
|
||||
templateFormData.append(
|
||||
'files',
|
||||
new File([formFieldsPdf], 'form-fields-test.pdf', { type: 'application/pdf' }),
|
||||
);
|
||||
|
||||
const templateRes = await request.post(`${baseUrl}/envelope/create`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
multipart: templateFormData,
|
||||
});
|
||||
|
||||
expect(templateRes.ok()).toBeTruthy();
|
||||
const templateResponse = (await templateRes.json()) as TCreateEnvelopeResponse;
|
||||
|
||||
const template = await prisma.envelope.findUniqueOrThrow({
|
||||
where: { id: templateResponse.id },
|
||||
include: {
|
||||
envelopeItems: { include: { documentData: true } },
|
||||
recipients: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Verify template stored the formValues
|
||||
expect(template.formValues).toEqual({
|
||||
[FORM_FIELDS.TEXT_FIELD]: 'Default Value',
|
||||
[FORM_FIELDS.COMPANY_NAME]: 'Default Company',
|
||||
});
|
||||
|
||||
// Create document from template without providing new formValues
|
||||
// The template's formValues should be used
|
||||
const useTemplateRes = await request.post(`${WEBAPP_BASE_URL}/api/v2-beta/template/use`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
data: {
|
||||
templateId: mapSecondaryIdToTemplateId(template.secondaryId),
|
||||
recipients: [
|
||||
{
|
||||
id: template.recipients[0].id,
|
||||
email: 'recipient@example.com',
|
||||
name: 'Test Recipient',
|
||||
},
|
||||
],
|
||||
// No formValues - should inherit from template
|
||||
},
|
||||
});
|
||||
|
||||
expect(useTemplateRes.ok()).toBeTruthy();
|
||||
const documentResponse = await useTemplateRes.json();
|
||||
|
||||
const document = await prisma.envelope.findFirstOrThrow({
|
||||
where: { id: documentResponse.envelopeId },
|
||||
include: {
|
||||
envelopeItems: { include: { documentData: true } },
|
||||
},
|
||||
});
|
||||
|
||||
// Form fields should be flattened
|
||||
const documentPdfBuffer = await getFileServerSide(document.envelopeItems[0].documentData);
|
||||
|
||||
expect(await pdfHasFormFields(documentPdfBuffer)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Form Values Verification', () => {
|
||||
test('should correctly insert form values into PDF before flattening', async ({ request }) => {
|
||||
const { user, team } = await seedUser();
|
||||
const { token } = await createApiToken({
|
||||
userId: user.id,
|
||||
teamId: team.id,
|
||||
tokenName: 'test',
|
||||
expiresIn: null,
|
||||
});
|
||||
|
||||
// Create a template first (form fields preserved)
|
||||
const templatePayload: TCreateEnvelopePayload = {
|
||||
type: EnvelopeType.TEMPLATE,
|
||||
title: 'Template for Value Verification',
|
||||
recipients: [
|
||||
{
|
||||
email: 'recipient@example.com',
|
||||
name: 'Test Recipient',
|
||||
role: RecipientRole.SIGNER,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const templateFormData = new FormData();
|
||||
|
||||
templateFormData.append('payload', JSON.stringify(templatePayload));
|
||||
templateFormData.append(
|
||||
'files',
|
||||
new File([formFieldsPdf], 'form-fields-test.pdf', { type: 'application/pdf' }),
|
||||
);
|
||||
|
||||
const templateRes = await request.post(`${baseUrl}/envelope/create`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
multipart: templateFormData,
|
||||
});
|
||||
|
||||
const templateResponse = (await templateRes.json()) as TCreateEnvelopeResponse;
|
||||
const template = await prisma.envelope.findUniqueOrThrow({
|
||||
where: { id: templateResponse.id },
|
||||
include: {
|
||||
envelopeItems: { include: { documentData: true } },
|
||||
recipients: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Verify template PDF still has form fields
|
||||
const templatePdfBuffer = await getFileServerSide(template.envelopeItems[0].documentData);
|
||||
expect(await pdfHasFormFields(templatePdfBuffer)).toBe(true);
|
||||
|
||||
// Verify we can read a text field value (should be empty initially)
|
||||
const initialValue = await getPdfTextFieldValue(templatePdfBuffer, FORM_FIELDS.TEXT_FIELD);
|
||||
expect(initialValue).toBe('');
|
||||
|
||||
// Now create a document with form values
|
||||
const testValues = {
|
||||
[FORM_FIELDS.TEXT_FIELD]: 'Inserted Text Value',
|
||||
[FORM_FIELDS.COMPANY_NAME]: 'Test Company Name',
|
||||
};
|
||||
|
||||
const useTemplateRes = await request.post(`${WEBAPP_BASE_URL}/api/v2-beta/template/use`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
data: {
|
||||
templateId: mapSecondaryIdToTemplateId(template.secondaryId),
|
||||
recipients: [
|
||||
{
|
||||
id: template.recipients[0].id,
|
||||
email: 'recipient@example.com',
|
||||
name: 'Test Recipient',
|
||||
},
|
||||
],
|
||||
formValues: testValues,
|
||||
},
|
||||
});
|
||||
|
||||
expect(useTemplateRes.ok()).toBeTruthy();
|
||||
const documentResponse = await useTemplateRes.json();
|
||||
|
||||
const document = await prisma.envelope.findFirstOrThrow({
|
||||
where: { id: documentResponse.envelopeId },
|
||||
include: {
|
||||
envelopeItems: { include: { documentData: true } },
|
||||
},
|
||||
});
|
||||
|
||||
// The form should be flattened, so we can't read form fields
|
||||
const documentPdfBuffer = await getFileServerSide(document.envelopeItems[0].documentData);
|
||||
expect(await pdfHasFormFields(documentPdfBuffer)).toBe(false);
|
||||
|
||||
// The values should have been inserted before flattening
|
||||
// We can't verify the actual text content easily without visual inspection,
|
||||
// but we can verify the form fields are gone (flattened)
|
||||
const fieldNames = await getPdfFormFieldNames(documentPdfBuffer);
|
||||
expect(fieldNames.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Edge Cases', () => {
|
||||
test('should handle PDF without form fields gracefully', async ({ request }) => {
|
||||
const { user, team } = await seedUser();
|
||||
const { token } = await createApiToken({
|
||||
userId: user.id,
|
||||
teamId: team.id,
|
||||
tokenName: 'test',
|
||||
expiresIn: null,
|
||||
});
|
||||
|
||||
// Use a PDF without form fields
|
||||
const examplePdf = fs.readFileSync(path.join(__dirname, '../../../../assets/example.pdf'));
|
||||
|
||||
const payload: TCreateEnvelopePayload = {
|
||||
type: EnvelopeType.DOCUMENT,
|
||||
title: 'Document with No Form Fields',
|
||||
formValues: {
|
||||
nonexistent_field: 'Some Value',
|
||||
},
|
||||
};
|
||||
|
||||
const formData = new FormData();
|
||||
|
||||
formData.append('payload', JSON.stringify(payload));
|
||||
formData.append('files', new File([examplePdf], 'example.pdf', { type: 'application/pdf' }));
|
||||
|
||||
const res = await request.post(`${baseUrl}/envelope/create`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
multipart: formData,
|
||||
});
|
||||
|
||||
// Should succeed even with formValues for non-existent fields
|
||||
expect(res.ok()).toBeTruthy();
|
||||
expect(res.status()).toBe(200);
|
||||
|
||||
const response = (await res.json()) as TCreateEnvelopeResponse;
|
||||
const envelope = await prisma.envelope.findUniqueOrThrow({
|
||||
where: { id: response.id },
|
||||
include: {
|
||||
envelopeItems: { include: { documentData: true } },
|
||||
},
|
||||
});
|
||||
|
||||
expect(envelope.formValues).toEqual({ nonexistent_field: 'Some Value' });
|
||||
});
|
||||
|
||||
test('should handle empty formValues object', async ({ request }) => {
|
||||
const { user, team } = await seedUser();
|
||||
const { token } = await createApiToken({
|
||||
userId: user.id,
|
||||
teamId: team.id,
|
||||
tokenName: 'test',
|
||||
expiresIn: null,
|
||||
});
|
||||
|
||||
const payload: TCreateEnvelopePayload = {
|
||||
type: EnvelopeType.DOCUMENT,
|
||||
title: 'Document with Empty Form Values',
|
||||
formValues: {},
|
||||
};
|
||||
|
||||
const formData = new FormData();
|
||||
|
||||
formData.append('payload', JSON.stringify(payload));
|
||||
formData.append(
|
||||
'files',
|
||||
new File([formFieldsPdf], 'form-fields-test.pdf', { type: 'application/pdf' }),
|
||||
);
|
||||
|
||||
const res = await request.post(`${baseUrl}/envelope/create`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
multipart: formData,
|
||||
});
|
||||
|
||||
expect(res.ok()).toBeTruthy();
|
||||
expect(res.status()).toBe(200);
|
||||
|
||||
const response = (await res.json()) as TCreateEnvelopeResponse;
|
||||
const envelope = await prisma.envelope.findUniqueOrThrow({
|
||||
where: { id: response.id },
|
||||
include: {
|
||||
envelopeItems: { include: { documentData: true } },
|
||||
},
|
||||
});
|
||||
|
||||
// Form should still be flattened for DOCUMENT type
|
||||
const documentData = envelope.envelopeItems[0].documentData;
|
||||
const pdfBuffer = await getFileServerSide(documentData);
|
||||
|
||||
expect(await pdfHasFormFields(pdfBuffer)).toBe(false);
|
||||
});
|
||||
|
||||
test('should handle partial formValues (only some fields)', async ({ request }) => {
|
||||
const { user, team } = await seedUser();
|
||||
const { token } = await createApiToken({
|
||||
userId: user.id,
|
||||
teamId: team.id,
|
||||
tokenName: 'test',
|
||||
expiresIn: null,
|
||||
});
|
||||
|
||||
const payload: TCreateEnvelopePayload = {
|
||||
type: EnvelopeType.DOCUMENT,
|
||||
title: 'Document with Partial Form Values',
|
||||
formValues: {
|
||||
[FORM_FIELDS.TEXT_FIELD]: 'Only this field',
|
||||
// Other fields not set
|
||||
},
|
||||
};
|
||||
|
||||
const formData = new FormData();
|
||||
|
||||
formData.append('payload', JSON.stringify(payload));
|
||||
formData.append(
|
||||
'files',
|
||||
new File([formFieldsPdf], 'form-fields-test.pdf', { type: 'application/pdf' }),
|
||||
);
|
||||
|
||||
const res = await request.post(`${baseUrl}/envelope/create`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
multipart: formData,
|
||||
});
|
||||
|
||||
expect(res.ok()).toBeTruthy();
|
||||
expect(res.status()).toBe(200);
|
||||
|
||||
const response = (await res.json()) as TCreateEnvelopeResponse;
|
||||
const envelope = await prisma.envelope.findUniqueOrThrow({
|
||||
where: { id: response.id },
|
||||
include: {
|
||||
envelopeItems: { include: { documentData: true } },
|
||||
},
|
||||
});
|
||||
|
||||
// Should store the partial formValues
|
||||
expect(envelope.formValues).toEqual({
|
||||
[FORM_FIELDS.TEXT_FIELD]: 'Only this field',
|
||||
});
|
||||
|
||||
// Form should still be flattened
|
||||
const documentData = envelope.envelopeItems[0].documentData;
|
||||
const pdfBuffer = await getFileServerSide(documentData);
|
||||
|
||||
expect(await pdfHasFormFields(pdfBuffer)).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,427 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
||||
import { createApiToken } from '@documenso/lib/server-only/public-api/create-api-token';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
import { EnvelopeType, RecipientRole } from '@documenso/prisma/client';
|
||||
import { seedTeam } from '@documenso/prisma/seed/teams';
|
||||
import { seedBlankTemplate } from '@documenso/prisma/seed/templates';
|
||||
import type {
|
||||
TCreateEnvelopePayload,
|
||||
TCreateEnvelopeResponse,
|
||||
} from '@documenso/trpc/server/envelope-router/create-envelope.types';
|
||||
|
||||
import { apiSignin } from '../fixtures/authentication';
|
||||
|
||||
const WEBAPP_BASE_URL = NEXT_PUBLIC_WEBAPP_URL();
|
||||
const baseUrl = `${WEBAPP_BASE_URL}/api/v2-beta`;
|
||||
|
||||
test.describe.configure({ mode: 'parallel' });
|
||||
|
||||
/**
|
||||
* Helper function to set default recipients for a team
|
||||
*/
|
||||
const setTeamDefaultRecipients = async (
|
||||
teamId: number,
|
||||
defaultRecipients: Array<{ email: string; name: string; role: RecipientRole }>,
|
||||
) => {
|
||||
const teamSettings = await prisma.teamGlobalSettings.findFirstOrThrow({
|
||||
where: {
|
||||
team: {
|
||||
id: teamId,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await prisma.teamGlobalSettings.update({
|
||||
where: {
|
||||
id: teamSettings.id,
|
||||
},
|
||||
data: {
|
||||
defaultRecipients,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
test.describe('Default Recipients', () => {
|
||||
test('[DEFAULT_RECIPIENTS]: default recipients are added to documents created via UI', async ({
|
||||
page,
|
||||
}) => {
|
||||
const { team, owner } = await seedTeam({
|
||||
createTeamMembers: 2,
|
||||
});
|
||||
|
||||
// Get a team member to set as default recipient
|
||||
const teamMembers = await prisma.organisationMember.findMany({
|
||||
where: {
|
||||
organisationId: team.organisationId,
|
||||
userId: {
|
||||
not: owner.id,
|
||||
},
|
||||
},
|
||||
include: {
|
||||
user: true,
|
||||
},
|
||||
});
|
||||
|
||||
const defaultRecipientUser = teamMembers[0].user;
|
||||
|
||||
// Set up default recipients for the team
|
||||
await setTeamDefaultRecipients(team.id, [
|
||||
{
|
||||
email: defaultRecipientUser.email,
|
||||
name: defaultRecipientUser.name || defaultRecipientUser.email,
|
||||
role: RecipientRole.CC,
|
||||
},
|
||||
]);
|
||||
|
||||
await apiSignin({
|
||||
page,
|
||||
email: owner.email,
|
||||
redirectPath: `/t/${team.url}/documents`,
|
||||
});
|
||||
|
||||
// Upload document via UI - this triggers document creation with default recipients
|
||||
const [fileChooser] = await Promise.all([
|
||||
page.waitForEvent('filechooser'),
|
||||
page
|
||||
.locator('input[type=file]')
|
||||
.nth(1)
|
||||
.evaluate((e) => {
|
||||
if (e instanceof HTMLInputElement) {
|
||||
e.click();
|
||||
}
|
||||
}),
|
||||
]);
|
||||
|
||||
await fileChooser.setFiles(path.join(__dirname, '../../../../assets/example.pdf'));
|
||||
|
||||
// Wait to be redirected to the edit page (v2 envelope editor)
|
||||
await page.waitForURL(new RegExp(`/t/${team.url}/documents/envelope_.*`));
|
||||
|
||||
// Extract document ID from URL
|
||||
const urlParts = page.url().split('/');
|
||||
const documentId = urlParts.find((part) => part.startsWith('envelope_'));
|
||||
|
||||
// Wait for the Recipients card to be visible (v2 envelope editor)
|
||||
await expect(page.getByRole('heading', { name: 'Recipients' })).toBeVisible();
|
||||
|
||||
await expect(page.getByTestId('signer-email-input').first()).not.toBeEmpty();
|
||||
|
||||
await page.getByRole('button', { name: 'Add Signer' }).click();
|
||||
|
||||
// Add a regular signer using the v2 editor
|
||||
await page.getByTestId('signer-email-input').last().fill('regular-signer@documenso.com');
|
||||
await page
|
||||
.getByPlaceholder(/Recipient/)
|
||||
.first()
|
||||
.fill('Regular Signer');
|
||||
|
||||
// Wait for autosave to complete
|
||||
await page.waitForTimeout(3000);
|
||||
|
||||
// Verify that default recipient is present in the database
|
||||
await expect(async () => {
|
||||
const envelope = await prisma.envelope.findFirstOrThrow({
|
||||
where: {
|
||||
id: documentId,
|
||||
},
|
||||
include: {
|
||||
recipients: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Should have 2 recipients: the regular signer + the default recipient
|
||||
expect(envelope.recipients.length).toBe(2);
|
||||
|
||||
const defaultRecipient = envelope.recipients.find(
|
||||
(r) => r.email.toLowerCase() === defaultRecipientUser.email.toLowerCase(),
|
||||
);
|
||||
expect(defaultRecipient).toBeDefined();
|
||||
expect(defaultRecipient?.role).toBe(RecipientRole.CC);
|
||||
|
||||
const regularSigner = envelope.recipients.find(
|
||||
(r) => r.email === 'regular-signer@documenso.com',
|
||||
);
|
||||
expect(regularSigner).toBeDefined();
|
||||
}).toPass();
|
||||
});
|
||||
|
||||
// TODO: Are we intending to allow default recipients to be removed from a document?
|
||||
test.skip('[DEFAULT_RECIPIENTS]: default recipients cannot be removed from a document', async ({
|
||||
page,
|
||||
}) => {
|
||||
const { team, owner } = await seedTeam({
|
||||
createTeamMembers: 2,
|
||||
});
|
||||
|
||||
// Get a team member to set as default recipient
|
||||
const teamMembers = await prisma.organisationMember.findMany({
|
||||
where: {
|
||||
organisationId: team.organisationId,
|
||||
userId: {
|
||||
not: owner.id,
|
||||
},
|
||||
},
|
||||
include: {
|
||||
user: true,
|
||||
},
|
||||
});
|
||||
|
||||
const defaultRecipientUser = teamMembers[0].user;
|
||||
|
||||
// Set up default recipients for the team
|
||||
await setTeamDefaultRecipients(team.id, [
|
||||
{
|
||||
email: defaultRecipientUser.email,
|
||||
name: defaultRecipientUser.name || defaultRecipientUser.email,
|
||||
role: RecipientRole.CC,
|
||||
},
|
||||
]);
|
||||
|
||||
await apiSignin({
|
||||
page,
|
||||
email: owner.email,
|
||||
redirectPath: `/t/${team.url}/documents`,
|
||||
});
|
||||
|
||||
// Upload document via UI - this triggers document creation with default recipients
|
||||
const [fileChooser] = await Promise.all([
|
||||
page.waitForEvent('filechooser'),
|
||||
page
|
||||
.locator('input[type=file]')
|
||||
.nth(1)
|
||||
.evaluate((e) => {
|
||||
if (e instanceof HTMLInputElement) {
|
||||
e.click();
|
||||
}
|
||||
}),
|
||||
]);
|
||||
|
||||
await fileChooser.setFiles(path.join(__dirname, '../../../../assets/example.pdf'));
|
||||
|
||||
// Wait to be redirected to the edit page (v2 envelope editor)
|
||||
await page.waitForURL(new RegExp(`/t/${team.url}/documents/envelope_.*`));
|
||||
|
||||
// Extract document ID from URL
|
||||
const urlParts = page.url().split('/');
|
||||
const documentId = urlParts.find((part) => part.startsWith('envelope_'));
|
||||
|
||||
// Replace the default recipient with a regular signer
|
||||
await page.getByTestId('signer-email-input').first().fill('regular-signer@documenso.com');
|
||||
await page
|
||||
.getByPlaceholder(/Recipient/)
|
||||
.first()
|
||||
.fill('Regular Signer');
|
||||
|
||||
// Wait for autosave to complete
|
||||
await page.waitForTimeout(3000);
|
||||
|
||||
// Wait for recipients to be saved
|
||||
await expect(async () => {
|
||||
const envelope = await prisma.envelope.findFirstOrThrow({
|
||||
where: {
|
||||
id: documentId,
|
||||
},
|
||||
include: {
|
||||
recipients: true,
|
||||
},
|
||||
});
|
||||
expect(envelope.recipients.length).toBe(2);
|
||||
}).toPass();
|
||||
|
||||
// Verify that the default recipient's remove button is disabled
|
||||
// In the v2 editor, default recipients should have a disabled remove button
|
||||
// Find the fieldset containing the default recipient's email and check if its remove button is disabled
|
||||
const defaultRecipientRow = page.locator('fieldset').filter({
|
||||
hasText: defaultRecipientUser.email,
|
||||
});
|
||||
|
||||
// The default recipient row should exist and have a disabled remove button
|
||||
await expect(defaultRecipientRow).toHaveCount(1);
|
||||
const removeButton = defaultRecipientRow.getByTestId('remove-signer-button');
|
||||
await expect(removeButton).toBeDisabled();
|
||||
});
|
||||
|
||||
test('[DEFAULT_RECIPIENTS]: documents created via API have default recipients', async ({
|
||||
request,
|
||||
}) => {
|
||||
const { team, owner } = await seedTeam({
|
||||
createTeamMembers: 2,
|
||||
});
|
||||
|
||||
// Get a team member to set as default recipient
|
||||
const teamMembers = await prisma.organisationMember.findMany({
|
||||
where: {
|
||||
organisationId: team.organisationId,
|
||||
userId: {
|
||||
not: owner.id,
|
||||
},
|
||||
},
|
||||
include: {
|
||||
user: true,
|
||||
},
|
||||
});
|
||||
|
||||
const defaultRecipientUser = teamMembers[0].user;
|
||||
|
||||
// Set up default recipients for the team
|
||||
await setTeamDefaultRecipients(team.id, [
|
||||
{
|
||||
email: defaultRecipientUser.email,
|
||||
name: defaultRecipientUser.name || defaultRecipientUser.email,
|
||||
role: RecipientRole.CC,
|
||||
},
|
||||
]);
|
||||
|
||||
// Create API token
|
||||
const { token } = await createApiToken({
|
||||
userId: owner.id,
|
||||
teamId: team.id,
|
||||
tokenName: 'test-token',
|
||||
expiresIn: null,
|
||||
});
|
||||
|
||||
// Create envelope via API
|
||||
const payload: TCreateEnvelopePayload = {
|
||||
type: EnvelopeType.DOCUMENT,
|
||||
title: 'Test Document with Default Recipients',
|
||||
recipients: [
|
||||
{
|
||||
email: 'api-recipient@documenso.com',
|
||||
name: 'API Recipient',
|
||||
role: RecipientRole.SIGNER,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('payload', JSON.stringify(payload));
|
||||
|
||||
const pdfData = fs.readFileSync(path.join(__dirname, '../../../../assets/example.pdf'));
|
||||
formData.append('files', new File([pdfData], 'test.pdf', { type: 'application/pdf' }));
|
||||
|
||||
const res = await request.post(`${baseUrl}/envelope/create`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
multipart: formData,
|
||||
});
|
||||
|
||||
expect(res.ok()).toBeTruthy();
|
||||
expect(res.status()).toBe(200);
|
||||
|
||||
const response = (await res.json()) as TCreateEnvelopeResponse;
|
||||
|
||||
// Verify the envelope has both the API recipient and the default recipient
|
||||
const envelope = await prisma.envelope.findUniqueOrThrow({
|
||||
where: {
|
||||
id: response.id,
|
||||
},
|
||||
include: {
|
||||
recipients: true,
|
||||
},
|
||||
});
|
||||
|
||||
expect(envelope.recipients.length).toBe(2);
|
||||
|
||||
const apiRecipient = envelope.recipients.find((r) => r.email === 'api-recipient@documenso.com');
|
||||
expect(apiRecipient).toBeDefined();
|
||||
expect(apiRecipient?.role).toBe(RecipientRole.SIGNER);
|
||||
|
||||
const defaultRecipient = envelope.recipients.find(
|
||||
(r) => r.email.toLowerCase() === defaultRecipientUser.email.toLowerCase(),
|
||||
);
|
||||
expect(defaultRecipient).toBeDefined();
|
||||
expect(defaultRecipient?.role).toBe(RecipientRole.CC);
|
||||
});
|
||||
|
||||
test('[DEFAULT_RECIPIENTS]: documents created from template have default recipients', async ({
|
||||
page,
|
||||
}) => {
|
||||
const { team, owner } = await seedTeam({
|
||||
createTeamMembers: 2,
|
||||
});
|
||||
|
||||
// Get a team member to set as default recipient
|
||||
const teamMembers = await prisma.organisationMember.findMany({
|
||||
where: {
|
||||
organisationId: team.organisationId,
|
||||
userId: {
|
||||
not: owner.id,
|
||||
},
|
||||
},
|
||||
include: {
|
||||
user: true,
|
||||
},
|
||||
});
|
||||
|
||||
const defaultRecipientUser = teamMembers[0].user;
|
||||
|
||||
// Set up default recipients for the team
|
||||
await setTeamDefaultRecipients(team.id, [
|
||||
{
|
||||
email: defaultRecipientUser.email,
|
||||
name: defaultRecipientUser.name || defaultRecipientUser.email,
|
||||
role: RecipientRole.CC,
|
||||
},
|
||||
]);
|
||||
|
||||
// Create a template
|
||||
const template = await seedBlankTemplate(owner, team.id);
|
||||
|
||||
await apiSignin({
|
||||
page,
|
||||
email: owner.email,
|
||||
redirectPath: `/t/${team.url}/templates/${template.id}/edit`,
|
||||
});
|
||||
|
||||
// Set template title
|
||||
await page.getByLabel('Title').fill('Template with Default Recipients');
|
||||
|
||||
await page.getByRole('button', { name: 'Continue' }).click();
|
||||
await expect(page.getByRole('heading', { name: 'Add Placeholder' })).toBeVisible();
|
||||
|
||||
// Add a template recipient
|
||||
await page.getByPlaceholder('Email').fill('template-recipient@documenso.com');
|
||||
await page.getByPlaceholder('Name').fill('Template Recipient');
|
||||
|
||||
await page.getByRole('button', { name: 'Continue' }).click();
|
||||
await expect(page.getByRole('heading', { name: 'Add Fields' })).toBeVisible();
|
||||
|
||||
await page.getByRole('button', { name: 'Save template' }).click();
|
||||
|
||||
// Use template to create document
|
||||
await page.waitForURL(`/t/${team.url}/templates`);
|
||||
await page.getByRole('button', { name: 'Use Template' }).click();
|
||||
await page.getByRole('button', { name: 'Create as draft' }).click();
|
||||
|
||||
// Wait for document to be created
|
||||
await page.waitForURL(new RegExp(`/t/${team.url}/documents/envelope_.*`));
|
||||
|
||||
const documentId = page.url().split('/').pop();
|
||||
|
||||
// Verify the document has both the template recipient and the default recipient
|
||||
const document = await prisma.envelope.findFirstOrThrow({
|
||||
where: {
|
||||
id: documentId,
|
||||
},
|
||||
include: {
|
||||
recipients: true,
|
||||
},
|
||||
});
|
||||
|
||||
expect(document.recipients.length).toBe(2);
|
||||
|
||||
const templateRecipient = document.recipients.find(
|
||||
(r) => r.email === 'template-recipient@documenso.com',
|
||||
);
|
||||
expect(templateRecipient).toBeDefined();
|
||||
|
||||
const defaultRecipient = document.recipients.find(
|
||||
(r) => r.email.toLowerCase() === defaultRecipientUser.email.toLowerCase(),
|
||||
);
|
||||
expect(defaultRecipient).toBeDefined();
|
||||
expect(defaultRecipient?.role).toBe(RecipientRole.CC);
|
||||
});
|
||||
});
|
||||
@@ -505,7 +505,9 @@ test('[TEMPLATE]: should create a document from a template using template docume
|
||||
});
|
||||
|
||||
expect(document.title).toEqual('TEMPLATE_WITH_ORIGINAL_DOC');
|
||||
expect(firstDocumentData.data).toEqual(templateWithData.envelopeItems[0].documentData.data);
|
||||
expect(firstDocumentData.initialData).toEqual(
|
||||
templateWithData.envelopeItems[0].documentData.data,
|
||||
);
|
||||
expect(firstDocumentData.initialData).toEqual(
|
||||
templateWithData.envelopeItems[0].documentData.initialData,
|
||||
);
|
||||
|
||||
@@ -15,11 +15,12 @@
|
||||
"@hono/standard-validator": "^0.2.0",
|
||||
"@oslojs/crypto": "^1.0.1",
|
||||
"@oslojs/encoding": "^1.1.0",
|
||||
"@simplewebauthn/server": "^13.2.2",
|
||||
"arctic": "^3.7.0",
|
||||
"hono": "4.10.6",
|
||||
"hono": "4.11.4",
|
||||
"luxon": "^3.7.2",
|
||||
"nanoid": "^5.1.6",
|
||||
"ts-pattern": "^5.9.0",
|
||||
"zod": "^3.25.76"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { sValidator } from '@hono/standard-validator';
|
||||
import { UserSecurityAuditLogType } from '@prisma/client';
|
||||
import { verifyAuthenticationResponse } from '@simplewebauthn/server';
|
||||
import { isoBase64URL } from '@simplewebauthn/server/helpers';
|
||||
import { Hono } from 'hono';
|
||||
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
@@ -80,9 +81,9 @@ export const passkeyRoute = new Hono<HonoAuthContext>()
|
||||
expectedChallenge: challengeToken.token,
|
||||
expectedOrigin: origin,
|
||||
expectedRPID: rpId,
|
||||
authenticator: {
|
||||
credentialID: new Uint8Array(Array.from(passkey.credentialId)),
|
||||
credentialPublicKey: new Uint8Array(passkey.credentialPublicKey),
|
||||
credential: {
|
||||
id: isoBase64URL.fromBuffer(passkey.credentialId),
|
||||
publicKey: new Uint8Array(passkey.credentialPublicKey),
|
||||
counter: Number(passkey.counter),
|
||||
},
|
||||
}).catch(() => null);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/consistent-type-assertions */
|
||||
import { RefObject, useEffect, useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
/**
|
||||
* Calculate the width and height of a text element.
|
||||
|
||||
@@ -1,30 +1,33 @@
|
||||
import type { MessageDescriptor } from '@lingui/core';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
|
||||
import type { TDocumentAuth } from '../types/document-auth';
|
||||
import { DocumentAuth } from '../types/document-auth';
|
||||
|
||||
type DocumentAuthTypeData = {
|
||||
key: TDocumentAuth;
|
||||
value: string;
|
||||
value: MessageDescriptor;
|
||||
};
|
||||
|
||||
export const DOCUMENT_AUTH_TYPES: Record<string, DocumentAuthTypeData> = {
|
||||
[DocumentAuth.ACCOUNT]: {
|
||||
key: DocumentAuth.ACCOUNT,
|
||||
value: 'Require account',
|
||||
value: msg`Require account`,
|
||||
},
|
||||
[DocumentAuth.PASSKEY]: {
|
||||
key: DocumentAuth.PASSKEY,
|
||||
value: 'Require passkey',
|
||||
value: msg`Require passkey`,
|
||||
},
|
||||
[DocumentAuth.TWO_FACTOR_AUTH]: {
|
||||
key: DocumentAuth.TWO_FACTOR_AUTH,
|
||||
value: 'Require 2FA',
|
||||
value: msg`Require 2FA`,
|
||||
},
|
||||
[DocumentAuth.PASSWORD]: {
|
||||
key: DocumentAuth.PASSWORD,
|
||||
value: 'Require password',
|
||||
value: msg`Require password`,
|
||||
},
|
||||
[DocumentAuth.EXPLICIT_NONE]: {
|
||||
key: DocumentAuth.EXPLICIT_NONE,
|
||||
value: 'None (Overrides global settings)',
|
||||
value: msg`None (Overrides global settings)`,
|
||||
},
|
||||
} satisfies Record<TDocumentAuth, DocumentAuthTypeData>;
|
||||
|
||||
@@ -1,23 +1,26 @@
|
||||
import type { MessageDescriptor } from '@lingui/core';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
|
||||
import { DocumentVisibility } from '@documenso/lib/types/document-visibility';
|
||||
|
||||
import type { TDocumentVisibility } from '../types/document-visibility';
|
||||
|
||||
type DocumentVisibilityTypeData = {
|
||||
key: TDocumentVisibility;
|
||||
value: string;
|
||||
value: MessageDescriptor;
|
||||
};
|
||||
|
||||
export const DOCUMENT_VISIBILITY: Record<string, DocumentVisibilityTypeData> = {
|
||||
[DocumentVisibility.ADMIN]: {
|
||||
key: DocumentVisibility.ADMIN,
|
||||
value: 'Admins only',
|
||||
value: msg`Admins only`,
|
||||
},
|
||||
[DocumentVisibility.EVERYONE]: {
|
||||
key: DocumentVisibility.EVERYONE,
|
||||
value: 'Everyone',
|
||||
value: msg`Everyone`,
|
||||
},
|
||||
[DocumentVisibility.MANAGER_AND_ABOVE]: {
|
||||
key: DocumentVisibility.MANAGER_AND_ABOVE,
|
||||
value: 'Managers and above',
|
||||
value: msg`Managers and above`,
|
||||
},
|
||||
} satisfies Record<TDocumentVisibility, DocumentVisibilityTypeData>;
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
"@node-rs/bcrypt": "^1.10.7",
|
||||
"@pdf-lib/fontkit": "^1.1.1",
|
||||
"@scure/base": "^1.2.6",
|
||||
"@simplewebauthn/server": "^13.2.2",
|
||||
"@sindresorhus/slugify": "^3.0.0",
|
||||
"@team-plain/typescript-sdk": "^5.11.0",
|
||||
"@vvo/tzdb": "^6.196.0",
|
||||
@@ -54,7 +55,7 @@
|
||||
"posthog-js": "^1.297.2",
|
||||
"posthog-node": "4.18.0",
|
||||
"react": "^18",
|
||||
"react-pdf": "^10.2.0",
|
||||
"react-pdf": "^10.3.0",
|
||||
"remeda": "^2.32.0",
|
||||
"sharp": "0.34.5",
|
||||
"skia-canvas": "^3.0.8",
|
||||
@@ -67,4 +68,4 @@
|
||||
"@types/luxon": "^3.7.1",
|
||||
"@types/pg": "^8.15.6"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Passkey } from '@prisma/client';
|
||||
import { generateAuthenticationOptions } from '@simplewebauthn/server';
|
||||
import type { AuthenticatorTransportFuture } from '@simplewebauthn/types';
|
||||
import type { AuthenticatorTransportFuture } from '@simplewebauthn/server';
|
||||
import { isoBase64URL } from '@simplewebauthn/server/helpers';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
@@ -53,8 +54,7 @@ export const createPasskeyAuthenticationOptions = async ({
|
||||
allowCredentials: preferredPasskey
|
||||
? [
|
||||
{
|
||||
id: preferredPasskey.credentialId,
|
||||
type: 'public-key',
|
||||
id: isoBase64URL.fromBuffer(preferredPasskey.credentialId),
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
transports: preferredPasskey.transports as AuthenticatorTransportFuture[],
|
||||
},
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { generateRegistrationOptions } from '@simplewebauthn/server';
|
||||
import type { AuthenticatorTransportFuture } from '@simplewebauthn/types';
|
||||
import type { AuthenticatorTransportFuture } from '@simplewebauthn/server';
|
||||
import { isoBase64URL } from '@simplewebauthn/server/helpers';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
@@ -32,14 +33,13 @@ export const createPasskeyRegistrationOptions = async ({
|
||||
const options = await generateRegistrationOptions({
|
||||
rpName,
|
||||
rpID,
|
||||
userID: userId.toString(),
|
||||
userID: Buffer.from(userId.toString()),
|
||||
userName: user.email,
|
||||
userDisplayName: user.name ?? undefined,
|
||||
timeout: PASSKEY_TIMEOUT,
|
||||
attestationType: 'none',
|
||||
excludeCredentials: passkeys.map((passkey) => ({
|
||||
id: passkey.credentialId,
|
||||
type: 'public-key',
|
||||
id: isoBase64URL.fromBuffer(passkey.credentialId),
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
transports: passkey.transports as AuthenticatorTransportFuture[],
|
||||
})),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { UserSecurityAuditLogType } from '@prisma/client';
|
||||
import { verifyRegistrationResponse } from '@simplewebauthn/server';
|
||||
import type { RegistrationResponseJSON } from '@simplewebauthn/types';
|
||||
import type { RegistrationResponseJSON } from '@simplewebauthn/server';
|
||||
import { isoBase64URL } from '@simplewebauthn/server/helpers';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
@@ -83,20 +84,19 @@ export const createPasskey = async ({
|
||||
});
|
||||
}
|
||||
|
||||
const { credentialPublicKey, credentialID, counter, credentialDeviceType, credentialBackedUp } =
|
||||
verification.registrationInfo;
|
||||
const { credentialDeviceType, credentialBackedUp, credential } = verification.registrationInfo;
|
||||
|
||||
await prisma.$transaction(async (tx) => {
|
||||
await tx.passkey.create({
|
||||
data: {
|
||||
userId,
|
||||
name: passkeyName,
|
||||
credentialId: Buffer.from(credentialID),
|
||||
credentialPublicKey: Buffer.from(credentialPublicKey),
|
||||
counter,
|
||||
credentialId: Buffer.from(isoBase64URL.toBuffer(credential.id)),
|
||||
credentialPublicKey: Buffer.from(credential.publicKey),
|
||||
counter: credential.counter,
|
||||
credentialDeviceType,
|
||||
credentialBackedUp,
|
||||
transports: verificationResponse.response.transports,
|
||||
transports: credential.transports,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { Envelope, Recipient } from '@prisma/client';
|
||||
import { verifyAuthenticationResponse } from '@simplewebauthn/server';
|
||||
import { isoBase64URL } from '@simplewebauthn/server/helpers';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import { prisma } from '@documenso/prisma';
|
||||
@@ -252,9 +253,9 @@ const verifyPasskey = async ({
|
||||
expectedChallenge: verificationToken.token,
|
||||
expectedOrigin: origin,
|
||||
expectedRPID: rpId,
|
||||
authenticator: {
|
||||
credentialID: new Uint8Array(Array.from(passkey.credentialId)),
|
||||
credentialPublicKey: new Uint8Array(passkey.credentialPublicKey),
|
||||
credential: {
|
||||
id: isoBase64URL.fromBuffer(passkey.credentialId),
|
||||
publicKey: new Uint8Array(passkey.credentialPublicKey),
|
||||
counter: Number(passkey.counter),
|
||||
},
|
||||
}).catch(() => null); // May want to log this for insights.
|
||||
|
||||
@@ -33,7 +33,7 @@ import {
|
||||
mapEnvelopeToWebhookDocumentPayload,
|
||||
} from '../../types/webhook-payload';
|
||||
import { getFileServerSide } from '../../universal/upload/get-file.server';
|
||||
import { putPdfFileServerSide } from '../../universal/upload/put-file.server';
|
||||
import { putNormalizedPdfFileServerSide } from '../../universal/upload/put-file.server';
|
||||
import { isDocumentCompleted } from '../../utils/document';
|
||||
import { extractDocumentAuthMethods } from '../../utils/document-auth';
|
||||
import { type EnvelopeIdOptions, mapSecondaryIdToDocumentId } from '../../utils/envelope';
|
||||
@@ -334,7 +334,7 @@ const injectFormValuesIntoDocument = async (
|
||||
fileName = `${envelope.title}.pdf`;
|
||||
}
|
||||
|
||||
const newDocumentData = await putPdfFileServerSide({
|
||||
const newDocumentData = await putNormalizedPdfFileServerSide({
|
||||
name: fileName,
|
||||
type: 'application/pdf',
|
||||
arrayBuffer: async () => Promise.resolve(prefilled),
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { normalizePdf as makeNormalizedPdf } from '@documenso/lib/server-only/pdf/normalize-pdf';
|
||||
import { ZDefaultRecipientsSchema } from '@documenso/lib/types/default-recipients';
|
||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
|
||||
import type { ApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
|
||||
import { nanoid, prefixedId } from '@documenso/lib/universal/id';
|
||||
@@ -184,7 +185,9 @@ export const createEnvelope = async ({
|
||||
|
||||
const buffer = await getFileServerSide(documentData);
|
||||
|
||||
const normalizedPdf = await makeNormalizedPdf(Buffer.from(buffer));
|
||||
const normalizedPdf = await makeNormalizedPdf(Buffer.from(buffer), {
|
||||
flattenForm: type !== EnvelopeType.TEMPLATE,
|
||||
});
|
||||
|
||||
const titleToUse = item.title || title;
|
||||
|
||||
@@ -345,8 +348,22 @@ export const createEnvelope = async ({
|
||||
|
||||
const firstEnvelopeItem = envelope.envelopeItems[0];
|
||||
|
||||
const defaultRecipients = settings.defaultRecipients
|
||||
? ZDefaultRecipientsSchema.parse(settings.defaultRecipients)
|
||||
: [];
|
||||
|
||||
const mappedDefaultRecipients: CreateEnvelopeRecipientOptions[] = defaultRecipients.map(
|
||||
(recipient) => ({
|
||||
email: recipient.email,
|
||||
name: recipient.name,
|
||||
role: recipient.role,
|
||||
}),
|
||||
);
|
||||
|
||||
const allRecipients = [...(data.recipients || []), ...mappedDefaultRecipients];
|
||||
|
||||
await Promise.all(
|
||||
(data.recipients || []).map(async (recipient) => {
|
||||
allRecipients.map(async (recipient) => {
|
||||
const recipientAuthOptions = createRecipientAuthOptions({
|
||||
accessAuth: recipient.accessAuth ?? [],
|
||||
actionAuth: recipient.actionAuth ?? [],
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Prisma } from '@prisma/client';
|
||||
import { Prisma } from '@prisma/client';
|
||||
import { OrganisationType } from '@prisma/client';
|
||||
import { OrganisationMemberRole } from '@prisma/client';
|
||||
|
||||
@@ -63,6 +63,7 @@ export const createOrganisation = async ({
|
||||
const organisationSetting = await tx.organisationGlobalSettings.create({
|
||||
data: {
|
||||
...generateDefaultOrganisationSettings(),
|
||||
defaultRecipients: Prisma.DbNull,
|
||||
id: generateDatabaseId('org_setting'),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -4,7 +4,9 @@ import { AppError } from '../../errors/app-error';
|
||||
import { flattenAnnotations } from './flatten-annotations';
|
||||
import { flattenForm, removeOptionalContentGroups } from './flatten-form';
|
||||
|
||||
export const normalizePdf = async (pdf: Buffer) => {
|
||||
export const normalizePdf = async (pdf: Buffer, options: { flattenForm?: boolean } = {}) => {
|
||||
const shouldFlattenForm = options.flattenForm ?? true;
|
||||
|
||||
const pdfDoc = await PDFDocument.load(pdf).catch((e) => {
|
||||
console.error(`PDF normalization error: ${e.message}`);
|
||||
|
||||
@@ -20,8 +22,11 @@ export const normalizePdf = async (pdf: Buffer) => {
|
||||
}
|
||||
|
||||
removeOptionalContentGroups(pdfDoc);
|
||||
await flattenForm(pdfDoc);
|
||||
flattenAnnotations(pdfDoc);
|
||||
|
||||
if (shouldFlattenForm) {
|
||||
await flattenForm(pdfDoc);
|
||||
flattenAnnotations(pdfDoc);
|
||||
}
|
||||
|
||||
return Buffer.from(await pdfDoc.save());
|
||||
};
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import { OrganisationGroupType, OrganisationMemberRole, TeamMemberRole } from '@prisma/client';
|
||||
import {
|
||||
OrganisationGroupType,
|
||||
OrganisationMemberRole,
|
||||
Prisma,
|
||||
TeamMemberRole,
|
||||
} from '@prisma/client';
|
||||
import { match } from 'ts-pattern';
|
||||
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
@@ -137,6 +142,7 @@ export const createTeam = async ({
|
||||
const teamSettings = await tx.teamGlobalSettings.create({
|
||||
data: {
|
||||
...generateDefaultTeamSettings(),
|
||||
defaultRecipients: Prisma.DbNull,
|
||||
id: generateDatabaseId('team_setting'),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -19,9 +19,11 @@ import { prisma } from '@documenso/prisma';
|
||||
import { DEFAULT_DOCUMENT_DATE_FORMAT } from '../../constants/date-formats';
|
||||
import type { SupportedLanguageCodes } from '../../constants/i18n';
|
||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||
import { ZDefaultRecipientsSchema } from '../../types/default-recipients';
|
||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '../../types/document-audit-logs';
|
||||
import { ZRecipientAuthOptionsSchema } from '../../types/document-auth';
|
||||
import type { TDocumentEmailSettings } from '../../types/document-email';
|
||||
import type { TDocumentFormValues } from '../../types/document-form-values';
|
||||
import type {
|
||||
TCheckboxFieldMeta,
|
||||
TDropdownFieldMeta,
|
||||
@@ -42,7 +44,7 @@ import {
|
||||
} from '../../types/webhook-payload';
|
||||
import type { ApiRequestMetadata } from '../../universal/extract-request-metadata';
|
||||
import { getFileServerSide } from '../../universal/upload/get-file.server';
|
||||
import { putPdfFileServerSide } from '../../universal/upload/put-file.server';
|
||||
import { putNormalizedPdfFileServerSide } from '../../universal/upload/put-file.server';
|
||||
import { extractDerivedDocumentMeta } from '../../utils/document';
|
||||
import { createDocumentAuditLogData } from '../../utils/document-audit-logs';
|
||||
import {
|
||||
@@ -55,6 +57,7 @@ import { mapSecondaryIdToTemplateId } from '../../utils/envelope';
|
||||
import { buildTeamWhereQuery } from '../../utils/teams';
|
||||
import { getEnvelopeWhereInput } from '../envelope/get-envelope-by-id';
|
||||
import { incrementDocumentId } from '../envelope/increment-id';
|
||||
import { insertFormValuesInPdf } from '../pdf/insert-form-values-in-pdf';
|
||||
import { getTeamSettings } from '../team/get-team-settings';
|
||||
import { triggerWebhook } from '../webhooks/trigger/trigger-webhook';
|
||||
|
||||
@@ -117,6 +120,8 @@ export type CreateDocumentFromTemplateOptions = {
|
||||
uploadSignatureEnabled?: boolean;
|
||||
drawSignatureEnabled?: boolean;
|
||||
};
|
||||
|
||||
formValues?: TDocumentFormValues;
|
||||
requestMetadata: ApiRequestMetadata;
|
||||
};
|
||||
|
||||
@@ -303,6 +308,7 @@ export const createDocumentFromTemplate = async ({
|
||||
folderId,
|
||||
prefillFields,
|
||||
attachments,
|
||||
formValues,
|
||||
}: CreateDocumentFromTemplateOptions) => {
|
||||
const { envelopeWhereInput } = await getEnvelopeWhereInput({
|
||||
id,
|
||||
@@ -396,6 +402,30 @@ export const createDocumentFromTemplate = async ({
|
||||
};
|
||||
});
|
||||
|
||||
const defaultRecipients = settings.defaultRecipients
|
||||
? ZDefaultRecipientsSchema.parse(settings.defaultRecipients)
|
||||
: [];
|
||||
|
||||
const defaultRecipientsFinal: FinalRecipient[] = defaultRecipients.map((recipient) => {
|
||||
const authOptions = ZRecipientAuthOptionsSchema.parse({});
|
||||
|
||||
return {
|
||||
templateRecipientId: -1,
|
||||
fields: [],
|
||||
name: recipient.name || recipient.email,
|
||||
email: recipient.email,
|
||||
role: recipient.role,
|
||||
signingOrder: null,
|
||||
authOptions: createRecipientAuthOptions({
|
||||
accessAuth: authOptions.accessAuth,
|
||||
actionAuth: authOptions.actionAuth,
|
||||
}),
|
||||
token: nanoid(),
|
||||
};
|
||||
});
|
||||
|
||||
const allFinalRecipients = [...finalRecipients, ...defaultRecipientsFinal];
|
||||
|
||||
// Key = original envelope item ID
|
||||
// Value = duplicated envelope item ID.
|
||||
const oldEnvelopeItemToNewEnvelopeItemIdMap: Record<string, string> = {};
|
||||
@@ -431,11 +461,19 @@ export const createDocumentFromTemplate = async ({
|
||||
});
|
||||
}
|
||||
|
||||
const buffer = await getFileServerSide(documentDataToDuplicate);
|
||||
let buffer = await getFileServerSide(documentDataToDuplicate);
|
||||
|
||||
const titleToUse = item.title || finalEnvelopeTitle;
|
||||
|
||||
const duplicatedFile = await putPdfFileServerSide({
|
||||
if (formValues) {
|
||||
// eslint-disable-next-line require-atomic-updates
|
||||
buffer = await insertFormValuesInPdf({
|
||||
pdf: Buffer.from(buffer),
|
||||
formValues,
|
||||
});
|
||||
}
|
||||
|
||||
const duplicatedFile = await putNormalizedPdfFileServerSide({
|
||||
name: titleToUse,
|
||||
type: 'application/pdf',
|
||||
arrayBuffer: async () => Promise.resolve(buffer),
|
||||
@@ -445,7 +483,7 @@ export const createDocumentFromTemplate = async ({
|
||||
data: {
|
||||
type: duplicatedFile.type,
|
||||
data: duplicatedFile.data,
|
||||
initialData: duplicatedFile.initialData,
|
||||
initialData: documentDataToDuplicate.data,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -515,7 +553,7 @@ export const createDocumentFromTemplate = async ({
|
||||
documentMetaId: documentMeta.id,
|
||||
recipients: {
|
||||
createMany: {
|
||||
data: finalRecipients.map((recipient) => {
|
||||
data: allFinalRecipients.map((recipient) => {
|
||||
const authOptions = ZRecipientAuthOptionsSchema.parse(recipient?.authOptions);
|
||||
|
||||
return {
|
||||
@@ -596,7 +634,7 @@ export const createDocumentFromTemplate = async ({
|
||||
}
|
||||
}
|
||||
|
||||
Object.values(finalRecipients).forEach(({ token, fields }) => {
|
||||
Object.values(allFinalRecipients).forEach(({ token, fields }) => {
|
||||
const recipient = envelope.recipients.find((recipient) => recipient.token === token);
|
||||
|
||||
if (!recipient) {
|
||||
|
||||
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: de\n"
|
||||
"Project-Id-Version: documenso-app\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2026-01-06 04:17\n"
|
||||
"PO-Revision-Date: 2026-01-13 02:39\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: German\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
@@ -1291,6 +1291,7 @@ msgid "Admin Panel"
|
||||
msgstr "Admin-Panel"
|
||||
|
||||
#: apps/remix/app/components/dialogs/folder-update-dialog.tsx
|
||||
#: packages/lib/constants/document-visibility.ts
|
||||
msgid "Admins only"
|
||||
msgstr "Nur Admins"
|
||||
|
||||
@@ -2578,6 +2579,7 @@ msgid "Configure the team roles for each member"
|
||||
msgstr "Konfigurieren Sie die Teamrollen für jedes Mitglied"
|
||||
|
||||
#: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-checkbox-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/template-direct-link-dialog.tsx
|
||||
msgid "Confirm"
|
||||
msgstr "Bestätigen"
|
||||
@@ -3734,6 +3736,7 @@ msgctxt "Audit log format"
|
||||
msgid "Document sent"
|
||||
msgstr "Dokument gesendet"
|
||||
|
||||
#: apps/remix/app/components/general/envelope-editor/envelope-editor-settings-dialog.tsx
|
||||
#: apps/remix/app/components/general/envelope-editor/envelope-editor.tsx
|
||||
msgid "Document Settings"
|
||||
msgstr "Dokumenteneinstellungen"
|
||||
@@ -4321,6 +4324,14 @@ msgstr "Beigefügte Dokument"
|
||||
msgid "Enclosed Documents"
|
||||
msgstr "Beigefügte Dokumente"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-email-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-initials-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-name-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-number-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-text-dialog.tsx
|
||||
msgid "Enter"
|
||||
msgstr "Eingeben"
|
||||
|
||||
#: apps/remix/app/components/dialogs/folder-create-dialog.tsx
|
||||
msgid "Enter a name for your new folder. Folders help you organise your items."
|
||||
msgstr "Geben Sie einen Namen für Ihren neuen Ordner ein. Ordner helfen Ihnen, Ihre Dateien zu organisieren."
|
||||
@@ -4329,6 +4340,26 @@ msgstr "Geben Sie einen Namen für Ihren neuen Ordner ein. Ordner helfen Ihnen,
|
||||
msgid "Enter claim name"
|
||||
msgstr "Anspruchsname eingeben"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-email-dialog.tsx
|
||||
msgid "Enter Email"
|
||||
msgstr "E-Mail eingeben"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-initials-dialog.tsx
|
||||
msgid "Enter Initials"
|
||||
msgstr "Initialen eingeben"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-name-dialog.tsx
|
||||
msgid "Enter Name"
|
||||
msgstr "Namen eingeben"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-number-dialog.tsx
|
||||
msgid "Enter Number"
|
||||
msgstr "Nummer eingeben"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-text-dialog.tsx
|
||||
msgid "Enter Text"
|
||||
msgstr "Text eingeben"
|
||||
|
||||
#: apps/remix/app/components/dialogs/organisation-email-domain-create-dialog.tsx
|
||||
msgid "Enter the domain you want to use for sending emails (without http:// or www)"
|
||||
msgstr "Geben Sie die Domain ein, die Sie zum Versenden von E-Mails verwenden möchten (ohne http:// oder www)"
|
||||
@@ -4523,6 +4554,7 @@ msgid "Event Type"
|
||||
msgstr "Ereignistyp"
|
||||
|
||||
#: apps/remix/app/components/dialogs/folder-update-dialog.tsx
|
||||
#: packages/lib/constants/document-visibility.ts
|
||||
#: packages/ui/components/document/document-visibility-select.tsx
|
||||
msgid "Everyone"
|
||||
msgstr "Jeder"
|
||||
@@ -5222,14 +5254,6 @@ msgstr "Initialen sind erforderlich"
|
||||
msgid "Initials Settings"
|
||||
msgstr "Einstellungen für Initialen"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-number-dialog.tsx
|
||||
msgid "Insert a value into the number field"
|
||||
msgstr "Fügen Sie einen Wert in das Zahlenfeld ein."
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-text-dialog.tsx
|
||||
msgid "Insert a value into the text field"
|
||||
msgstr "Fügen Sie einen Wert in das Textfeld ein."
|
||||
|
||||
#: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx
|
||||
msgid "Inserted"
|
||||
msgstr "Eingefügt"
|
||||
@@ -5738,6 +5762,7 @@ msgid "Manager"
|
||||
msgstr "Manager"
|
||||
|
||||
#: apps/remix/app/components/dialogs/folder-update-dialog.tsx
|
||||
#: packages/lib/constants/document-visibility.ts
|
||||
msgid "Managers and above"
|
||||
msgstr "Manager und höher"
|
||||
|
||||
@@ -6133,6 +6158,10 @@ msgstr "Keine Sorge, das passiert! Geben Sie Ihre E-Mail ein, und wir senden Ihn
|
||||
msgid "None"
|
||||
msgstr "Keine"
|
||||
|
||||
#: packages/lib/constants/document-auth.ts
|
||||
msgid "None (Overrides global settings)"
|
||||
msgstr "Keine (überschreibt globale Einstellungen)"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/documents.$id._index.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/documents.$id.edit.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/templates.$id._index.tsx
|
||||
@@ -6696,6 +6725,10 @@ msgstr "Bitte kontaktieren Sie den Webseiteninhaber für weitere Unterstützung.
|
||||
msgid "Please enter a meaningful name for your token. This will help you identify it later."
|
||||
msgstr "Bitte geben Sie einen aussagekräftigen Namen für Ihr Token ein. Dies wird Ihnen helfen, es später zu identifizieren."
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-number-dialog.tsx
|
||||
msgid "Please enter a number"
|
||||
msgstr "Bitte gib eine Zahl ein"
|
||||
|
||||
#: apps/remix/app/components/forms/profile.tsx
|
||||
#: apps/remix/app/components/forms/signup.tsx
|
||||
#: apps/remix/app/components/general/claim-account.tsx
|
||||
@@ -6706,6 +6739,22 @@ msgstr "Bitte geben Sie einen gültigen Namen ein."
|
||||
msgid "Please enter a valid number"
|
||||
msgstr "Bitte geben Sie eine gültige Nummer ein."
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-text-dialog.tsx
|
||||
msgid "Please enter a value"
|
||||
msgstr "Bitte gib einen Wert ein"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-email-dialog.tsx
|
||||
msgid "Please enter your email address"
|
||||
msgstr "Bitte gib deine E-Mail-Adresse ein"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-name-dialog.tsx
|
||||
msgid "Please enter your full name"
|
||||
msgstr "Bitte gib deinen vollständigen Namen ein"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-initials-dialog.tsx
|
||||
msgid "Please enter your initials"
|
||||
msgstr "Bitte geben Sie Ihre Initialen ein"
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-mobile-widget.tsx
|
||||
msgid "Please mark as viewed to complete"
|
||||
msgstr "Bitte als angesehen markieren, um zu vervollständigen"
|
||||
@@ -7250,6 +7299,22 @@ msgstr "Anfrage"
|
||||
msgid "Requesting Organisation"
|
||||
msgstr "Anfragende Organisation"
|
||||
|
||||
#: packages/lib/constants/document-auth.ts
|
||||
msgid "Require 2FA"
|
||||
msgstr "2FA erforderlich"
|
||||
|
||||
#: packages/lib/constants/document-auth.ts
|
||||
msgid "Require account"
|
||||
msgstr "Konto erforderlich"
|
||||
|
||||
#: packages/lib/constants/document-auth.ts
|
||||
msgid "Require passkey"
|
||||
msgstr "Passkey erforderlich"
|
||||
|
||||
#: packages/lib/constants/document-auth.ts
|
||||
msgid "Require password"
|
||||
msgstr "Passwort erforderlich"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/checkbox-field.tsx
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/dropdown-field.tsx
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx
|
||||
@@ -7662,6 +7727,10 @@ msgstr "Wählen Sie Mitglieder aus, die diesem Team hinzugefügt werden sollen"
|
||||
msgid "Select Option"
|
||||
msgstr "Option auswählen"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-checkbox-dialog.tsx
|
||||
msgid "Select Options"
|
||||
msgstr "Optionen auswählen"
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auth-passkey.tsx
|
||||
msgid "Select passkey"
|
||||
msgstr "Passkey auswählen"
|
||||
@@ -7884,13 +7953,7 @@ msgstr "Erweiterte Einstellungen anzeigen"
|
||||
msgid "Show templates in your public profile for your audience to sign and get started quickly"
|
||||
msgstr "Vorlagen in Ihrem öffentlichen Profil anzeigen, damit Ihre Zielgruppe unterschreiben und schnell loslegen kann"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-checkbox-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-email-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-initials-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-name-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-number-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-signature-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-text-dialog.tsx
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auth-2fa.tsx
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auth-password.tsx
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auto-sign.tsx
|
||||
@@ -7924,10 +7987,6 @@ msgstr "Unterzeichnen als {0} <0>({1})</0>"
|
||||
msgid "Sign as<0>{0} <1>({1})</1></0>"
|
||||
msgstr "Unterzeichnen als<0>{0} <1>({1})</1></0>"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-checkbox-dialog.tsx
|
||||
msgid "Sign Checkbox Field"
|
||||
msgstr "Sign Checkbox-Feld"
|
||||
|
||||
#: apps/remix/app/components/embed/embed-direct-template-client-page.tsx
|
||||
#: apps/remix/app/components/embed/embed-document-signing-page-v1.tsx
|
||||
#: apps/remix/app/components/embed/multisign/multi-sign-document-signing-view.tsx
|
||||
@@ -7944,10 +8003,6 @@ msgstr "Dokument signieren"
|
||||
msgid "Sign Documents"
|
||||
msgstr "Dokumente unterschreiben"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-email-dialog.tsx
|
||||
msgid "Sign Email"
|
||||
msgstr "Signatur-E-Mail"
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auth-dialog.tsx
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auth-dialog.tsx
|
||||
msgid "Sign field"
|
||||
@@ -7970,18 +8025,6 @@ msgstr "Anmelden"
|
||||
msgid "Sign in to your account"
|
||||
msgstr "Melden Sie sich bei Ihrem Konto an"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-initials-dialog.tsx
|
||||
msgid "Sign Initials"
|
||||
msgstr "Initialen signieren"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-name-dialog.tsx
|
||||
msgid "Sign Name"
|
||||
msgstr "Name signieren"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-number-dialog.tsx
|
||||
msgid "Sign Number Field"
|
||||
msgstr "Zahlenfeld signieren"
|
||||
|
||||
#: apps/remix/app/components/general/app-nav-mobile.tsx
|
||||
#: apps/remix/app/components/general/menu-switcher.tsx
|
||||
#: apps/remix/app/components/general/org-menu-switcher.tsx
|
||||
@@ -7992,10 +8035,6 @@ msgstr "Ausloggen"
|
||||
msgid "Sign Signature Field"
|
||||
msgstr "Signaturfeld signieren"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-text-dialog.tsx
|
||||
msgid "Sign Text Field"
|
||||
msgstr "Textfeld signieren"
|
||||
|
||||
#: apps/remix/app/components/embed/embed-direct-template-client-page.tsx
|
||||
#: apps/remix/app/components/embed/embed-document-signing-page-v1.tsx
|
||||
#: apps/remix/app/components/embed/multisign/multi-sign-document-signing-view.tsx
|
||||
@@ -8018,18 +8057,6 @@ msgstr "Registrieren mit Microsoft"
|
||||
msgid "Sign Up with OIDC"
|
||||
msgstr "Registrieren mit OIDC"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-email-dialog.tsx
|
||||
msgid "Sign your email into the field"
|
||||
msgstr "E-Mail ins Feld signieren"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-name-dialog.tsx
|
||||
msgid "Sign your full name into the field"
|
||||
msgstr "Vollständigen Namen ins Feld signieren"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-initials-dialog.tsx
|
||||
msgid "Sign your initials into the field"
|
||||
msgstr "Initialen ins Feld signieren"
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
|
||||
#: apps/remix/app/components/embed/embed-direct-template-client-page.tsx
|
||||
#: apps/remix/app/components/embed/embed-document-signing-page-v1.tsx
|
||||
|
||||
@@ -1286,6 +1286,7 @@ msgid "Admin Panel"
|
||||
msgstr "Admin Panel"
|
||||
|
||||
#: apps/remix/app/components/dialogs/folder-update-dialog.tsx
|
||||
#: packages/lib/constants/document-visibility.ts
|
||||
msgid "Admins only"
|
||||
msgstr "Admins only"
|
||||
|
||||
@@ -2573,6 +2574,7 @@ msgid "Configure the team roles for each member"
|
||||
msgstr "Configure the team roles for each member"
|
||||
|
||||
#: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-checkbox-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/template-direct-link-dialog.tsx
|
||||
msgid "Confirm"
|
||||
msgstr "Confirm"
|
||||
@@ -3729,6 +3731,7 @@ msgctxt "Audit log format"
|
||||
msgid "Document sent"
|
||||
msgstr "Document sent"
|
||||
|
||||
#: apps/remix/app/components/general/envelope-editor/envelope-editor-settings-dialog.tsx
|
||||
#: apps/remix/app/components/general/envelope-editor/envelope-editor.tsx
|
||||
msgid "Document Settings"
|
||||
msgstr "Document Settings"
|
||||
@@ -4316,6 +4319,14 @@ msgstr "Enclosed Document"
|
||||
msgid "Enclosed Documents"
|
||||
msgstr "Enclosed Documents"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-email-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-initials-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-name-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-number-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-text-dialog.tsx
|
||||
msgid "Enter"
|
||||
msgstr "Enter"
|
||||
|
||||
#: apps/remix/app/components/dialogs/folder-create-dialog.tsx
|
||||
msgid "Enter a name for your new folder. Folders help you organise your items."
|
||||
msgstr "Enter a name for your new folder. Folders help you organise your items."
|
||||
@@ -4324,6 +4335,26 @@ msgstr "Enter a name for your new folder. Folders help you organise your items."
|
||||
msgid "Enter claim name"
|
||||
msgstr "Enter claim name"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-email-dialog.tsx
|
||||
msgid "Enter Email"
|
||||
msgstr "Enter Email"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-initials-dialog.tsx
|
||||
msgid "Enter Initials"
|
||||
msgstr "Enter Initials"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-name-dialog.tsx
|
||||
msgid "Enter Name"
|
||||
msgstr "Enter Name"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-number-dialog.tsx
|
||||
msgid "Enter Number"
|
||||
msgstr "Enter Number"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-text-dialog.tsx
|
||||
msgid "Enter Text"
|
||||
msgstr "Enter Text"
|
||||
|
||||
#: apps/remix/app/components/dialogs/organisation-email-domain-create-dialog.tsx
|
||||
msgid "Enter the domain you want to use for sending emails (without http:// or www)"
|
||||
msgstr "Enter the domain you want to use for sending emails (without http:// or www)"
|
||||
@@ -4518,6 +4549,7 @@ msgid "Event Type"
|
||||
msgstr "Event Type"
|
||||
|
||||
#: apps/remix/app/components/dialogs/folder-update-dialog.tsx
|
||||
#: packages/lib/constants/document-visibility.ts
|
||||
#: packages/ui/components/document/document-visibility-select.tsx
|
||||
msgid "Everyone"
|
||||
msgstr "Everyone"
|
||||
@@ -5217,14 +5249,6 @@ msgstr "Initials are required"
|
||||
msgid "Initials Settings"
|
||||
msgstr "Initials Settings"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-number-dialog.tsx
|
||||
msgid "Insert a value into the number field"
|
||||
msgstr "Insert a value into the number field"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-text-dialog.tsx
|
||||
msgid "Insert a value into the text field"
|
||||
msgstr "Insert a value into the text field"
|
||||
|
||||
#: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx
|
||||
msgid "Inserted"
|
||||
msgstr "Inserted"
|
||||
@@ -5733,6 +5757,7 @@ msgid "Manager"
|
||||
msgstr "Manager"
|
||||
|
||||
#: apps/remix/app/components/dialogs/folder-update-dialog.tsx
|
||||
#: packages/lib/constants/document-visibility.ts
|
||||
msgid "Managers and above"
|
||||
msgstr "Managers and above"
|
||||
|
||||
@@ -6128,6 +6153,10 @@ msgstr "No worries, it happens! Enter your email and we'll email you a special l
|
||||
msgid "None"
|
||||
msgstr "None"
|
||||
|
||||
#: packages/lib/constants/document-auth.ts
|
||||
msgid "None (Overrides global settings)"
|
||||
msgstr "None (Overrides global settings)"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/documents.$id._index.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/documents.$id.edit.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/templates.$id._index.tsx
|
||||
@@ -6691,6 +6720,10 @@ msgstr "Please contact the site owner for further assistance."
|
||||
msgid "Please enter a meaningful name for your token. This will help you identify it later."
|
||||
msgstr "Please enter a meaningful name for your token. This will help you identify it later."
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-number-dialog.tsx
|
||||
msgid "Please enter a number"
|
||||
msgstr "Please enter a number"
|
||||
|
||||
#: apps/remix/app/components/forms/profile.tsx
|
||||
#: apps/remix/app/components/forms/signup.tsx
|
||||
#: apps/remix/app/components/general/claim-account.tsx
|
||||
@@ -6701,6 +6734,22 @@ msgstr "Please enter a valid name."
|
||||
msgid "Please enter a valid number"
|
||||
msgstr "Please enter a valid number"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-text-dialog.tsx
|
||||
msgid "Please enter a value"
|
||||
msgstr "Please enter a value"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-email-dialog.tsx
|
||||
msgid "Please enter your email address"
|
||||
msgstr "Please enter your email address"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-name-dialog.tsx
|
||||
msgid "Please enter your full name"
|
||||
msgstr "Please enter your full name"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-initials-dialog.tsx
|
||||
msgid "Please enter your initials"
|
||||
msgstr "Please enter your initials"
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-mobile-widget.tsx
|
||||
msgid "Please mark as viewed to complete"
|
||||
msgstr "Please mark as viewed to complete"
|
||||
@@ -7245,6 +7294,22 @@ msgstr "Request"
|
||||
msgid "Requesting Organisation"
|
||||
msgstr "Requesting Organisation"
|
||||
|
||||
#: packages/lib/constants/document-auth.ts
|
||||
msgid "Require 2FA"
|
||||
msgstr "Require 2FA"
|
||||
|
||||
#: packages/lib/constants/document-auth.ts
|
||||
msgid "Require account"
|
||||
msgstr "Require account"
|
||||
|
||||
#: packages/lib/constants/document-auth.ts
|
||||
msgid "Require passkey"
|
||||
msgstr "Require passkey"
|
||||
|
||||
#: packages/lib/constants/document-auth.ts
|
||||
msgid "Require password"
|
||||
msgstr "Require password"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/checkbox-field.tsx
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/dropdown-field.tsx
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx
|
||||
@@ -7657,6 +7722,10 @@ msgstr "Select members to add to this team"
|
||||
msgid "Select Option"
|
||||
msgstr "Select Option"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-checkbox-dialog.tsx
|
||||
msgid "Select Options"
|
||||
msgstr "Select Options"
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auth-passkey.tsx
|
||||
msgid "Select passkey"
|
||||
msgstr "Select passkey"
|
||||
@@ -7879,13 +7948,7 @@ msgstr "Show advanced settings"
|
||||
msgid "Show templates in your public profile for your audience to sign and get started quickly"
|
||||
msgstr "Show templates in your public profile for your audience to sign and get started quickly"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-checkbox-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-email-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-initials-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-name-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-number-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-signature-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-text-dialog.tsx
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auth-2fa.tsx
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auth-password.tsx
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auto-sign.tsx
|
||||
@@ -7919,10 +7982,6 @@ msgstr "Sign as {0} <0>({1})</0>"
|
||||
msgid "Sign as<0>{0} <1>({1})</1></0>"
|
||||
msgstr "Sign as<0>{0} <1>({1})</1></0>"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-checkbox-dialog.tsx
|
||||
msgid "Sign Checkbox Field"
|
||||
msgstr "Sign Checkbox Field"
|
||||
|
||||
#: apps/remix/app/components/embed/embed-direct-template-client-page.tsx
|
||||
#: apps/remix/app/components/embed/embed-document-signing-page-v1.tsx
|
||||
#: apps/remix/app/components/embed/multisign/multi-sign-document-signing-view.tsx
|
||||
@@ -7939,10 +7998,6 @@ msgstr "Sign Document"
|
||||
msgid "Sign Documents"
|
||||
msgstr "Sign Documents"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-email-dialog.tsx
|
||||
msgid "Sign Email"
|
||||
msgstr "Sign Email"
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auth-dialog.tsx
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auth-dialog.tsx
|
||||
msgid "Sign field"
|
||||
@@ -7965,18 +8020,6 @@ msgstr "Sign In"
|
||||
msgid "Sign in to your account"
|
||||
msgstr "Sign in to your account"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-initials-dialog.tsx
|
||||
msgid "Sign Initials"
|
||||
msgstr "Sign Initials"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-name-dialog.tsx
|
||||
msgid "Sign Name"
|
||||
msgstr "Sign Name"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-number-dialog.tsx
|
||||
msgid "Sign Number Field"
|
||||
msgstr "Sign Number Field"
|
||||
|
||||
#: apps/remix/app/components/general/app-nav-mobile.tsx
|
||||
#: apps/remix/app/components/general/menu-switcher.tsx
|
||||
#: apps/remix/app/components/general/org-menu-switcher.tsx
|
||||
@@ -7987,10 +8030,6 @@ msgstr "Sign Out"
|
||||
msgid "Sign Signature Field"
|
||||
msgstr "Sign Signature Field"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-text-dialog.tsx
|
||||
msgid "Sign Text Field"
|
||||
msgstr "Sign Text Field"
|
||||
|
||||
#: apps/remix/app/components/embed/embed-direct-template-client-page.tsx
|
||||
#: apps/remix/app/components/embed/embed-document-signing-page-v1.tsx
|
||||
#: apps/remix/app/components/embed/multisign/multi-sign-document-signing-view.tsx
|
||||
@@ -8013,18 +8052,6 @@ msgstr "Sign Up with Microsoft"
|
||||
msgid "Sign Up with OIDC"
|
||||
msgstr "Sign Up with OIDC"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-email-dialog.tsx
|
||||
msgid "Sign your email into the field"
|
||||
msgstr "Sign your email into the field"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-name-dialog.tsx
|
||||
msgid "Sign your full name into the field"
|
||||
msgstr "Sign your full name into the field"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-initials-dialog.tsx
|
||||
msgid "Sign your initials into the field"
|
||||
msgstr "Sign your initials into the field"
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
|
||||
#: apps/remix/app/components/embed/embed-direct-template-client-page.tsx
|
||||
#: apps/remix/app/components/embed/embed-document-signing-page-v1.tsx
|
||||
|
||||
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: es\n"
|
||||
"Project-Id-Version: documenso-app\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2026-01-06 04:17\n"
|
||||
"PO-Revision-Date: 2026-01-13 02:39\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Spanish\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
@@ -1291,6 +1291,7 @@ msgid "Admin Panel"
|
||||
msgstr "Panel de Administración"
|
||||
|
||||
#: apps/remix/app/components/dialogs/folder-update-dialog.tsx
|
||||
#: packages/lib/constants/document-visibility.ts
|
||||
msgid "Admins only"
|
||||
msgstr "Solo para administradores"
|
||||
|
||||
@@ -2578,6 +2579,7 @@ msgid "Configure the team roles for each member"
|
||||
msgstr "Configura los roles del equipo para cada miembro"
|
||||
|
||||
#: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-checkbox-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/template-direct-link-dialog.tsx
|
||||
msgid "Confirm"
|
||||
msgstr "Confirmar"
|
||||
@@ -3734,6 +3736,7 @@ msgctxt "Audit log format"
|
||||
msgid "Document sent"
|
||||
msgstr "Documento enviado"
|
||||
|
||||
#: apps/remix/app/components/general/envelope-editor/envelope-editor-settings-dialog.tsx
|
||||
#: apps/remix/app/components/general/envelope-editor/envelope-editor.tsx
|
||||
msgid "Document Settings"
|
||||
msgstr "Configuración del Documento"
|
||||
@@ -4321,6 +4324,14 @@ msgstr "Documento Adjunto"
|
||||
msgid "Enclosed Documents"
|
||||
msgstr "Documentos adjuntos"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-email-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-initials-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-name-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-number-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-text-dialog.tsx
|
||||
msgid "Enter"
|
||||
msgstr "Ingresar"
|
||||
|
||||
#: apps/remix/app/components/dialogs/folder-create-dialog.tsx
|
||||
msgid "Enter a name for your new folder. Folders help you organise your items."
|
||||
msgstr "Ingrese un nombre para su nueva carpeta. Las carpetas le ayudan a organizar sus elementos."
|
||||
@@ -4329,6 +4340,26 @@ msgstr "Ingrese un nombre para su nueva carpeta. Las carpetas le ayudan a organi
|
||||
msgid "Enter claim name"
|
||||
msgstr "Ingresar nombre de la reclamación"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-email-dialog.tsx
|
||||
msgid "Enter Email"
|
||||
msgstr "Ingresar correo electrónico"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-initials-dialog.tsx
|
||||
msgid "Enter Initials"
|
||||
msgstr "Ingresar iniciales"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-name-dialog.tsx
|
||||
msgid "Enter Name"
|
||||
msgstr "Ingresar nombre"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-number-dialog.tsx
|
||||
msgid "Enter Number"
|
||||
msgstr "Ingresar número"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-text-dialog.tsx
|
||||
msgid "Enter Text"
|
||||
msgstr "Ingresar texto"
|
||||
|
||||
#: apps/remix/app/components/dialogs/organisation-email-domain-create-dialog.tsx
|
||||
msgid "Enter the domain you want to use for sending emails (without http:// or www)"
|
||||
msgstr "Ingresa el dominio que deseas usar para enviar correos electrónicos (sin http:// ni www)"
|
||||
@@ -4523,6 +4554,7 @@ msgid "Event Type"
|
||||
msgstr "Tipo de evento"
|
||||
|
||||
#: apps/remix/app/components/dialogs/folder-update-dialog.tsx
|
||||
#: packages/lib/constants/document-visibility.ts
|
||||
#: packages/ui/components/document/document-visibility-select.tsx
|
||||
msgid "Everyone"
|
||||
msgstr "Todos"
|
||||
@@ -5222,14 +5254,6 @@ msgstr "Se requieren iniciales"
|
||||
msgid "Initials Settings"
|
||||
msgstr "Configuración de Iniciales"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-number-dialog.tsx
|
||||
msgid "Insert a value into the number field"
|
||||
msgstr "Introduce un valor en el campo de número"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-text-dialog.tsx
|
||||
msgid "Insert a value into the text field"
|
||||
msgstr "Introduce un valor en el campo de texto"
|
||||
|
||||
#: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx
|
||||
msgid "Inserted"
|
||||
msgstr "Insertado"
|
||||
@@ -5738,6 +5762,7 @@ msgid "Manager"
|
||||
msgstr "Gerente"
|
||||
|
||||
#: apps/remix/app/components/dialogs/folder-update-dialog.tsx
|
||||
#: packages/lib/constants/document-visibility.ts
|
||||
msgid "Managers and above"
|
||||
msgstr "Gerentes y superiores"
|
||||
|
||||
@@ -6133,6 +6158,10 @@ msgstr "¡No te preocupes, sucede! Ingresa tu correo electrónico y te enviaremo
|
||||
msgid "None"
|
||||
msgstr "Ninguno"
|
||||
|
||||
#: packages/lib/constants/document-auth.ts
|
||||
msgid "None (Overrides global settings)"
|
||||
msgstr "Ninguno (anula la configuración global)"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/documents.$id._index.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/documents.$id.edit.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/templates.$id._index.tsx
|
||||
@@ -6696,6 +6725,10 @@ msgstr "Por favor, contacte al propietario del sitio para más asistencia."
|
||||
msgid "Please enter a meaningful name for your token. This will help you identify it later."
|
||||
msgstr "Por favor, ingresa un nombre significativo para tu token. Esto te ayudará a identificarlo más tarde."
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-number-dialog.tsx
|
||||
msgid "Please enter a number"
|
||||
msgstr "Por favor ingresa un número"
|
||||
|
||||
#: apps/remix/app/components/forms/profile.tsx
|
||||
#: apps/remix/app/components/forms/signup.tsx
|
||||
#: apps/remix/app/components/general/claim-account.tsx
|
||||
@@ -6706,6 +6739,22 @@ msgstr "Por favor, introduce un nombre válido."
|
||||
msgid "Please enter a valid number"
|
||||
msgstr "Por favor, ingresa un número válido"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-text-dialog.tsx
|
||||
msgid "Please enter a value"
|
||||
msgstr "Por favor ingresa un valor"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-email-dialog.tsx
|
||||
msgid "Please enter your email address"
|
||||
msgstr "Por favor ingresa tu dirección de correo electrónico"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-name-dialog.tsx
|
||||
msgid "Please enter your full name"
|
||||
msgstr "Por favor ingresa tu nombre completo"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-initials-dialog.tsx
|
||||
msgid "Please enter your initials"
|
||||
msgstr "Por favor, introduzca sus iniciales"
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-mobile-widget.tsx
|
||||
msgid "Please mark as viewed to complete"
|
||||
msgstr "Por favor marque como visto para completar"
|
||||
@@ -7250,6 +7299,22 @@ msgstr "Solicitud"
|
||||
msgid "Requesting Organisation"
|
||||
msgstr "Organización solicitante"
|
||||
|
||||
#: packages/lib/constants/document-auth.ts
|
||||
msgid "Require 2FA"
|
||||
msgstr "Requerir 2FA"
|
||||
|
||||
#: packages/lib/constants/document-auth.ts
|
||||
msgid "Require account"
|
||||
msgstr "Requerir cuenta"
|
||||
|
||||
#: packages/lib/constants/document-auth.ts
|
||||
msgid "Require passkey"
|
||||
msgstr "Requerir llave de acceso"
|
||||
|
||||
#: packages/lib/constants/document-auth.ts
|
||||
msgid "Require password"
|
||||
msgstr "Requerir contraseña"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/checkbox-field.tsx
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/dropdown-field.tsx
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx
|
||||
@@ -7662,6 +7727,10 @@ msgstr "Seleccione los miembros para añadir a este equipo"
|
||||
msgid "Select Option"
|
||||
msgstr "Seleccionar una opción"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-checkbox-dialog.tsx
|
||||
msgid "Select Options"
|
||||
msgstr "Seleccionar opciones"
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auth-passkey.tsx
|
||||
msgid "Select passkey"
|
||||
msgstr "Seleccionar clave de acceso"
|
||||
@@ -7884,13 +7953,7 @@ msgstr "Mostrar configuraciones avanzadas"
|
||||
msgid "Show templates in your public profile for your audience to sign and get started quickly"
|
||||
msgstr "Mostrar plantillas en tu perfil público para que tu audiencia firme y comience rápidamente"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-checkbox-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-email-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-initials-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-name-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-number-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-signature-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-text-dialog.tsx
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auth-2fa.tsx
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auth-password.tsx
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auto-sign.tsx
|
||||
@@ -7924,10 +7987,6 @@ msgstr "Firmar como {0} <0>({1})</0>"
|
||||
msgid "Sign as<0>{0} <1>({1})</1></0>"
|
||||
msgstr "Firmar como<0>{0} <1>({1})</1></0>"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-checkbox-dialog.tsx
|
||||
msgid "Sign Checkbox Field"
|
||||
msgstr "Campo de casilla de verificación de firma"
|
||||
|
||||
#: apps/remix/app/components/embed/embed-direct-template-client-page.tsx
|
||||
#: apps/remix/app/components/embed/embed-document-signing-page-v1.tsx
|
||||
#: apps/remix/app/components/embed/multisign/multi-sign-document-signing-view.tsx
|
||||
@@ -7944,10 +8003,6 @@ msgstr "Firmar Documento"
|
||||
msgid "Sign Documents"
|
||||
msgstr "Firmar documentos"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-email-dialog.tsx
|
||||
msgid "Sign Email"
|
||||
msgstr "Correo de firma"
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auth-dialog.tsx
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auth-dialog.tsx
|
||||
msgid "Sign field"
|
||||
@@ -7970,18 +8025,6 @@ msgstr "Iniciar sesión"
|
||||
msgid "Sign in to your account"
|
||||
msgstr "Inicia sesión en tu cuenta"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-initials-dialog.tsx
|
||||
msgid "Sign Initials"
|
||||
msgstr "Iniciales de firma"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-name-dialog.tsx
|
||||
msgid "Sign Name"
|
||||
msgstr "Nombre de firma"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-number-dialog.tsx
|
||||
msgid "Sign Number Field"
|
||||
msgstr "Campo de número de firma"
|
||||
|
||||
#: apps/remix/app/components/general/app-nav-mobile.tsx
|
||||
#: apps/remix/app/components/general/menu-switcher.tsx
|
||||
#: apps/remix/app/components/general/org-menu-switcher.tsx
|
||||
@@ -7992,10 +8035,6 @@ msgstr "Cerrar sesión"
|
||||
msgid "Sign Signature Field"
|
||||
msgstr "Campo de firma Firma"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-text-dialog.tsx
|
||||
msgid "Sign Text Field"
|
||||
msgstr "Campo de texto de firma"
|
||||
|
||||
#: apps/remix/app/components/embed/embed-direct-template-client-page.tsx
|
||||
#: apps/remix/app/components/embed/embed-document-signing-page-v1.tsx
|
||||
#: apps/remix/app/components/embed/multisign/multi-sign-document-signing-view.tsx
|
||||
@@ -8018,18 +8057,6 @@ msgstr "Registrarse con Microsoft"
|
||||
msgid "Sign Up with OIDC"
|
||||
msgstr "Regístrate con OIDC"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-email-dialog.tsx
|
||||
msgid "Sign your email into the field"
|
||||
msgstr "Firme su correo electrónico en el campo"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-name-dialog.tsx
|
||||
msgid "Sign your full name into the field"
|
||||
msgstr "Firme su nombre completo en el campo"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-initials-dialog.tsx
|
||||
msgid "Sign your initials into the field"
|
||||
msgstr "Firme sus iniciales en el campo"
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
|
||||
#: apps/remix/app/components/embed/embed-direct-template-client-page.tsx
|
||||
#: apps/remix/app/components/embed/embed-document-signing-page-v1.tsx
|
||||
|
||||
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: fr\n"
|
||||
"Project-Id-Version: documenso-app\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2026-01-06 04:17\n"
|
||||
"PO-Revision-Date: 2026-01-13 02:39\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: French\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
@@ -1291,6 +1291,7 @@ msgid "Admin Panel"
|
||||
msgstr "Panneau d'administration"
|
||||
|
||||
#: apps/remix/app/components/dialogs/folder-update-dialog.tsx
|
||||
#: packages/lib/constants/document-visibility.ts
|
||||
msgid "Admins only"
|
||||
msgstr "Administrateurs uniquement"
|
||||
|
||||
@@ -2578,6 +2579,7 @@ msgid "Configure the team roles for each member"
|
||||
msgstr "Configurez les rôles d'équipe pour chaque membre"
|
||||
|
||||
#: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-checkbox-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/template-direct-link-dialog.tsx
|
||||
msgid "Confirm"
|
||||
msgstr "Confirmer"
|
||||
@@ -3734,6 +3736,7 @@ msgctxt "Audit log format"
|
||||
msgid "Document sent"
|
||||
msgstr "Document envoyé"
|
||||
|
||||
#: apps/remix/app/components/general/envelope-editor/envelope-editor-settings-dialog.tsx
|
||||
#: apps/remix/app/components/general/envelope-editor/envelope-editor.tsx
|
||||
msgid "Document Settings"
|
||||
msgstr "Paramètres du document"
|
||||
@@ -4321,6 +4324,14 @@ msgstr "Document joint"
|
||||
msgid "Enclosed Documents"
|
||||
msgstr "Documents joints"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-email-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-initials-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-name-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-number-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-text-dialog.tsx
|
||||
msgid "Enter"
|
||||
msgstr "Entrer"
|
||||
|
||||
#: apps/remix/app/components/dialogs/folder-create-dialog.tsx
|
||||
msgid "Enter a name for your new folder. Folders help you organise your items."
|
||||
msgstr "Entrez un nom pour votre nouveau dossier. Les dossiers vous aident à organiser vos éléments."
|
||||
@@ -4329,6 +4340,26 @@ msgstr "Entrez un nom pour votre nouveau dossier. Les dossiers vous aident à or
|
||||
msgid "Enter claim name"
|
||||
msgstr "Entrez le nom de la réclamation"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-email-dialog.tsx
|
||||
msgid "Enter Email"
|
||||
msgstr "Entrer l’adresse e-mail"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-initials-dialog.tsx
|
||||
msgid "Enter Initials"
|
||||
msgstr "Entrer les initiales"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-name-dialog.tsx
|
||||
msgid "Enter Name"
|
||||
msgstr "Entrer le nom"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-number-dialog.tsx
|
||||
msgid "Enter Number"
|
||||
msgstr "Entrer le numéro"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-text-dialog.tsx
|
||||
msgid "Enter Text"
|
||||
msgstr "Entrer le texte"
|
||||
|
||||
#: apps/remix/app/components/dialogs/organisation-email-domain-create-dialog.tsx
|
||||
msgid "Enter the domain you want to use for sending emails (without http:// or www)"
|
||||
msgstr "Entrez le domaine que vous souhaitez utiliser pour l'envoi des e-mails (sans http:// ou www)"
|
||||
@@ -4523,6 +4554,7 @@ msgid "Event Type"
|
||||
msgstr "Type d'événement"
|
||||
|
||||
#: apps/remix/app/components/dialogs/folder-update-dialog.tsx
|
||||
#: packages/lib/constants/document-visibility.ts
|
||||
#: packages/ui/components/document/document-visibility-select.tsx
|
||||
msgid "Everyone"
|
||||
msgstr "Tout le monde"
|
||||
@@ -5222,14 +5254,6 @@ msgstr "Les initiales sont requises"
|
||||
msgid "Initials Settings"
|
||||
msgstr "Paramètres des initiales"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-number-dialog.tsx
|
||||
msgid "Insert a value into the number field"
|
||||
msgstr "Insérez une valeur dans le champ de nombre"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-text-dialog.tsx
|
||||
msgid "Insert a value into the text field"
|
||||
msgstr "Insérez une valeur dans le champ de texte"
|
||||
|
||||
#: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx
|
||||
msgid "Inserted"
|
||||
msgstr "Inséré"
|
||||
@@ -5738,6 +5762,7 @@ msgid "Manager"
|
||||
msgstr "Gestionnaire"
|
||||
|
||||
#: apps/remix/app/components/dialogs/folder-update-dialog.tsx
|
||||
#: packages/lib/constants/document-visibility.ts
|
||||
msgid "Managers and above"
|
||||
msgstr "Responsables et supérieur"
|
||||
|
||||
@@ -6133,6 +6158,10 @@ msgstr "Pas de soucis, ça arrive ! Entrez votre email et nous vous enverrons un
|
||||
msgid "None"
|
||||
msgstr "Aucun"
|
||||
|
||||
#: packages/lib/constants/document-auth.ts
|
||||
msgid "None (Overrides global settings)"
|
||||
msgstr "Aucun (remplace les paramètres globaux)"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/documents.$id._index.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/documents.$id.edit.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/templates.$id._index.tsx
|
||||
@@ -6696,6 +6725,10 @@ msgstr "Veuillez contacter le propriétaire du site pour obtenir de l'aide suppl
|
||||
msgid "Please enter a meaningful name for your token. This will help you identify it later."
|
||||
msgstr "Veuillez entrer un nom significatif pour votre token. Cela vous aidera à l'identifier plus tard."
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-number-dialog.tsx
|
||||
msgid "Please enter a number"
|
||||
msgstr "Veuillez entrer un nombre"
|
||||
|
||||
#: apps/remix/app/components/forms/profile.tsx
|
||||
#: apps/remix/app/components/forms/signup.tsx
|
||||
#: apps/remix/app/components/general/claim-account.tsx
|
||||
@@ -6706,6 +6739,22 @@ msgstr "Veuiillez entrer un nom valide."
|
||||
msgid "Please enter a valid number"
|
||||
msgstr "Veuillez entrer un numéro valide"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-text-dialog.tsx
|
||||
msgid "Please enter a value"
|
||||
msgstr "Veuillez entrer une valeur"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-email-dialog.tsx
|
||||
msgid "Please enter your email address"
|
||||
msgstr "Veuillez entrer votre adresse e-mail"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-name-dialog.tsx
|
||||
msgid "Please enter your full name"
|
||||
msgstr "Veuillez entrer votre nom complet"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-initials-dialog.tsx
|
||||
msgid "Please enter your initials"
|
||||
msgstr "Veuillez saisir vos initiales"
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-mobile-widget.tsx
|
||||
msgid "Please mark as viewed to complete"
|
||||
msgstr "Veuillez marquer comme vu pour compléter"
|
||||
@@ -7250,6 +7299,22 @@ msgstr "Requête"
|
||||
msgid "Requesting Organisation"
|
||||
msgstr "Organisation Demandeuse"
|
||||
|
||||
#: packages/lib/constants/document-auth.ts
|
||||
msgid "Require 2FA"
|
||||
msgstr "Exiger une 2FA"
|
||||
|
||||
#: packages/lib/constants/document-auth.ts
|
||||
msgid "Require account"
|
||||
msgstr "Exiger un compte"
|
||||
|
||||
#: packages/lib/constants/document-auth.ts
|
||||
msgid "Require passkey"
|
||||
msgstr "Exiger une clé d’accès"
|
||||
|
||||
#: packages/lib/constants/document-auth.ts
|
||||
msgid "Require password"
|
||||
msgstr "Exiger un mot de passe"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/checkbox-field.tsx
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/dropdown-field.tsx
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx
|
||||
@@ -7662,6 +7727,10 @@ msgstr "Sélectionnez des membres à ajouter à cette équipe"
|
||||
msgid "Select Option"
|
||||
msgstr "Sélectionner une option"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-checkbox-dialog.tsx
|
||||
msgid "Select Options"
|
||||
msgstr "Sélectionner des options"
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auth-passkey.tsx
|
||||
msgid "Select passkey"
|
||||
msgstr "Sélectionner la clé d'authentification"
|
||||
@@ -7884,13 +7953,7 @@ msgstr "Afficher les paramètres avancés"
|
||||
msgid "Show templates in your public profile for your audience to sign and get started quickly"
|
||||
msgstr "Afficher des modèles dans votre profil public pour que votre audience puisse signer et commencer rapidement"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-checkbox-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-email-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-initials-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-name-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-number-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-signature-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-text-dialog.tsx
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auth-2fa.tsx
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auth-password.tsx
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auto-sign.tsx
|
||||
@@ -7924,10 +7987,6 @@ msgstr "Signer comme {0} <0>({1})</0>"
|
||||
msgid "Sign as<0>{0} <1>({1})</1></0>"
|
||||
msgstr "Signer comme<0>{0} <1>({1})</1></0>"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-checkbox-dialog.tsx
|
||||
msgid "Sign Checkbox Field"
|
||||
msgstr "Champ de case à cocher signe"
|
||||
|
||||
#: apps/remix/app/components/embed/embed-direct-template-client-page.tsx
|
||||
#: apps/remix/app/components/embed/embed-document-signing-page-v1.tsx
|
||||
#: apps/remix/app/components/embed/multisign/multi-sign-document-signing-view.tsx
|
||||
@@ -7944,10 +8003,6 @@ msgstr "Signer le document"
|
||||
msgid "Sign Documents"
|
||||
msgstr "Signer des documents"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-email-dialog.tsx
|
||||
msgid "Sign Email"
|
||||
msgstr "Email de Signature"
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auth-dialog.tsx
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auth-dialog.tsx
|
||||
msgid "Sign field"
|
||||
@@ -7970,18 +8025,6 @@ msgstr "Se connecter"
|
||||
msgid "Sign in to your account"
|
||||
msgstr "Connectez-vous à votre compte"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-initials-dialog.tsx
|
||||
msgid "Sign Initials"
|
||||
msgstr "Initiales de Signature"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-name-dialog.tsx
|
||||
msgid "Sign Name"
|
||||
msgstr "Nom de Signature"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-number-dialog.tsx
|
||||
msgid "Sign Number Field"
|
||||
msgstr "Champ de nombre à signer"
|
||||
|
||||
#: apps/remix/app/components/general/app-nav-mobile.tsx
|
||||
#: apps/remix/app/components/general/menu-switcher.tsx
|
||||
#: apps/remix/app/components/general/org-menu-switcher.tsx
|
||||
@@ -7992,10 +8035,6 @@ msgstr "Déconnexion"
|
||||
msgid "Sign Signature Field"
|
||||
msgstr "Champ de signature"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-text-dialog.tsx
|
||||
msgid "Sign Text Field"
|
||||
msgstr "Champ de texte à signer"
|
||||
|
||||
#: apps/remix/app/components/embed/embed-direct-template-client-page.tsx
|
||||
#: apps/remix/app/components/embed/embed-document-signing-page-v1.tsx
|
||||
#: apps/remix/app/components/embed/multisign/multi-sign-document-signing-view.tsx
|
||||
@@ -8018,18 +8057,6 @@ msgstr "S'inscrire avec Microsoft"
|
||||
msgid "Sign Up with OIDC"
|
||||
msgstr "S'inscrire avec OIDC"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-email-dialog.tsx
|
||||
msgid "Sign your email into the field"
|
||||
msgstr "Signez votre email dans le champ"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-name-dialog.tsx
|
||||
msgid "Sign your full name into the field"
|
||||
msgstr "Signez votre nom complet dans le champ"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-initials-dialog.tsx
|
||||
msgid "Sign your initials into the field"
|
||||
msgstr "Signez vos initiales dans le champ"
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
|
||||
#: apps/remix/app/components/embed/embed-direct-template-client-page.tsx
|
||||
#: apps/remix/app/components/embed/embed-document-signing-page-v1.tsx
|
||||
|
||||
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: it\n"
|
||||
"Project-Id-Version: documenso-app\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2026-01-06 04:17\n"
|
||||
"PO-Revision-Date: 2026-01-13 02:39\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Italian\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
@@ -1291,6 +1291,7 @@ msgid "Admin Panel"
|
||||
msgstr "Pannello di Amministrazione"
|
||||
|
||||
#: apps/remix/app/components/dialogs/folder-update-dialog.tsx
|
||||
#: packages/lib/constants/document-visibility.ts
|
||||
msgid "Admins only"
|
||||
msgstr "Solo amministratori"
|
||||
|
||||
@@ -2578,6 +2579,7 @@ msgid "Configure the team roles for each member"
|
||||
msgstr "Configura i ruoli del team per ogni membro"
|
||||
|
||||
#: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-checkbox-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/template-direct-link-dialog.tsx
|
||||
msgid "Confirm"
|
||||
msgstr "Conferma"
|
||||
@@ -3734,6 +3736,7 @@ msgctxt "Audit log format"
|
||||
msgid "Document sent"
|
||||
msgstr "Documento inviato"
|
||||
|
||||
#: apps/remix/app/components/general/envelope-editor/envelope-editor-settings-dialog.tsx
|
||||
#: apps/remix/app/components/general/envelope-editor/envelope-editor.tsx
|
||||
msgid "Document Settings"
|
||||
msgstr "Impostazioni Documento"
|
||||
@@ -4321,6 +4324,14 @@ msgstr "Documento Allegato"
|
||||
msgid "Enclosed Documents"
|
||||
msgstr "Documenti allegati"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-email-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-initials-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-name-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-number-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-text-dialog.tsx
|
||||
msgid "Enter"
|
||||
msgstr "Inserisci"
|
||||
|
||||
#: apps/remix/app/components/dialogs/folder-create-dialog.tsx
|
||||
msgid "Enter a name for your new folder. Folders help you organise your items."
|
||||
msgstr "Inserisci un nome per la tua nuova cartella. Le cartelle ti aiutano a organizzare i tuoi elementi."
|
||||
@@ -4329,6 +4340,26 @@ msgstr "Inserisci un nome per la tua nuova cartella. Le cartelle ti aiutano a or
|
||||
msgid "Enter claim name"
|
||||
msgstr "Inserisci nome richiesta"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-email-dialog.tsx
|
||||
msgid "Enter Email"
|
||||
msgstr "Inserisci l'email"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-initials-dialog.tsx
|
||||
msgid "Enter Initials"
|
||||
msgstr "Inserisci le iniziali"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-name-dialog.tsx
|
||||
msgid "Enter Name"
|
||||
msgstr "Inserisci il nome"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-number-dialog.tsx
|
||||
msgid "Enter Number"
|
||||
msgstr "Inserisci il numero"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-text-dialog.tsx
|
||||
msgid "Enter Text"
|
||||
msgstr "Inserisci il testo"
|
||||
|
||||
#: apps/remix/app/components/dialogs/organisation-email-domain-create-dialog.tsx
|
||||
msgid "Enter the domain you want to use for sending emails (without http:// or www)"
|
||||
msgstr "Inserisci il dominio che desideri utilizzare per inviare email (senza http:// o www)"
|
||||
@@ -4523,6 +4554,7 @@ msgid "Event Type"
|
||||
msgstr "Tipo di Evento"
|
||||
|
||||
#: apps/remix/app/components/dialogs/folder-update-dialog.tsx
|
||||
#: packages/lib/constants/document-visibility.ts
|
||||
#: packages/ui/components/document/document-visibility-select.tsx
|
||||
msgid "Everyone"
|
||||
msgstr "Tutti"
|
||||
@@ -5222,14 +5254,6 @@ msgstr "Iniziali richieste"
|
||||
msgid "Initials Settings"
|
||||
msgstr "Impostazioni Iniziali"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-number-dialog.tsx
|
||||
msgid "Insert a value into the number field"
|
||||
msgstr "Inserisci un valore nel campo numero"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-text-dialog.tsx
|
||||
msgid "Insert a value into the text field"
|
||||
msgstr "Inserisci un valore nel campo di testo"
|
||||
|
||||
#: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx
|
||||
msgid "Inserted"
|
||||
msgstr "Inserito"
|
||||
@@ -5738,6 +5762,7 @@ msgid "Manager"
|
||||
msgstr "Responsabile"
|
||||
|
||||
#: apps/remix/app/components/dialogs/folder-update-dialog.tsx
|
||||
#: packages/lib/constants/document-visibility.ts
|
||||
msgid "Managers and above"
|
||||
msgstr "Manager e superiori"
|
||||
|
||||
@@ -6133,6 +6158,10 @@ msgstr "Non ti preoccupare, succede! Inserisci la tua email e ti invieremo un li
|
||||
msgid "None"
|
||||
msgstr "Nessuno"
|
||||
|
||||
#: packages/lib/constants/document-auth.ts
|
||||
msgid "None (Overrides global settings)"
|
||||
msgstr "Nessuno (sovrascrive le impostazioni globali)"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/documents.$id._index.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/documents.$id.edit.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/templates.$id._index.tsx
|
||||
@@ -6696,6 +6725,10 @@ msgstr "Si prega di contattare il proprietario del sito per ulteriori assistenza
|
||||
msgid "Please enter a meaningful name for your token. This will help you identify it later."
|
||||
msgstr "Si prega di inserire un nome significativo per il proprio token. Questo ti aiuterà a identificarlo più tardi."
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-number-dialog.tsx
|
||||
msgid "Please enter a number"
|
||||
msgstr "Inserisci un numero"
|
||||
|
||||
#: apps/remix/app/components/forms/profile.tsx
|
||||
#: apps/remix/app/components/forms/signup.tsx
|
||||
#: apps/remix/app/components/general/claim-account.tsx
|
||||
@@ -6706,6 +6739,22 @@ msgstr "Per favore inserisci un nome valido."
|
||||
msgid "Please enter a valid number"
|
||||
msgstr "Per favore inserisci un numero valido"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-text-dialog.tsx
|
||||
msgid "Please enter a value"
|
||||
msgstr "Inserisci un valore"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-email-dialog.tsx
|
||||
msgid "Please enter your email address"
|
||||
msgstr "Inserisci il tuo indirizzo email"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-name-dialog.tsx
|
||||
msgid "Please enter your full name"
|
||||
msgstr "Inserisci il tuo nome completo"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-initials-dialog.tsx
|
||||
msgid "Please enter your initials"
|
||||
msgstr "Inserisci le tue iniziali"
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-mobile-widget.tsx
|
||||
msgid "Please mark as viewed to complete"
|
||||
msgstr "Per favore segna come visto per completare"
|
||||
@@ -7250,6 +7299,22 @@ msgstr "Richiesta"
|
||||
msgid "Requesting Organisation"
|
||||
msgstr "Organizzazione richiedente"
|
||||
|
||||
#: packages/lib/constants/document-auth.ts
|
||||
msgid "Require 2FA"
|
||||
msgstr "Richiedi 2FA"
|
||||
|
||||
#: packages/lib/constants/document-auth.ts
|
||||
msgid "Require account"
|
||||
msgstr "Richiedi account"
|
||||
|
||||
#: packages/lib/constants/document-auth.ts
|
||||
msgid "Require passkey"
|
||||
msgstr "Richiedi passkey"
|
||||
|
||||
#: packages/lib/constants/document-auth.ts
|
||||
msgid "Require password"
|
||||
msgstr "Richiedi password"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/checkbox-field.tsx
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/dropdown-field.tsx
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx
|
||||
@@ -7662,6 +7727,10 @@ msgstr "Seleziona membri da aggiungere a questo team"
|
||||
msgid "Select Option"
|
||||
msgstr "Seleziona opzione"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-checkbox-dialog.tsx
|
||||
msgid "Select Options"
|
||||
msgstr "Seleziona opzioni"
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auth-passkey.tsx
|
||||
msgid "Select passkey"
|
||||
msgstr "Seleziona una chiave di accesso"
|
||||
@@ -7884,13 +7953,7 @@ msgstr "Mostra impostazioni avanzate"
|
||||
msgid "Show templates in your public profile for your audience to sign and get started quickly"
|
||||
msgstr "Mostra modelli nel tuo profilo pubblico per il tuo pubblico da firmare e iniziare rapidamente"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-checkbox-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-email-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-initials-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-name-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-number-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-signature-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-text-dialog.tsx
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auth-2fa.tsx
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auth-password.tsx
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auto-sign.tsx
|
||||
@@ -7924,10 +7987,6 @@ msgstr "Firma come {0} <0>({1})</0>"
|
||||
msgid "Sign as<0>{0} <1>({1})</1></0>"
|
||||
msgstr "Firma come<0>{0} <1>({1})</1></0>"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-checkbox-dialog.tsx
|
||||
msgid "Sign Checkbox Field"
|
||||
msgstr "Campo casella di controllo firma"
|
||||
|
||||
#: apps/remix/app/components/embed/embed-direct-template-client-page.tsx
|
||||
#: apps/remix/app/components/embed/embed-document-signing-page-v1.tsx
|
||||
#: apps/remix/app/components/embed/multisign/multi-sign-document-signing-view.tsx
|
||||
@@ -7944,10 +8003,6 @@ msgstr "Firma documento"
|
||||
msgid "Sign Documents"
|
||||
msgstr "Firma documenti"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-email-dialog.tsx
|
||||
msgid "Sign Email"
|
||||
msgstr "Firma email"
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auth-dialog.tsx
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auth-dialog.tsx
|
||||
msgid "Sign field"
|
||||
@@ -7970,18 +8025,6 @@ msgstr "Accedi"
|
||||
msgid "Sign in to your account"
|
||||
msgstr "Accedi al tuo account"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-initials-dialog.tsx
|
||||
msgid "Sign Initials"
|
||||
msgstr "Firma iniziali"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-name-dialog.tsx
|
||||
msgid "Sign Name"
|
||||
msgstr "Firma nome"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-number-dialog.tsx
|
||||
msgid "Sign Number Field"
|
||||
msgstr "Campo numero firma"
|
||||
|
||||
#: apps/remix/app/components/general/app-nav-mobile.tsx
|
||||
#: apps/remix/app/components/general/menu-switcher.tsx
|
||||
#: apps/remix/app/components/general/org-menu-switcher.tsx
|
||||
@@ -7992,10 +8035,6 @@ msgstr "Disconnetti"
|
||||
msgid "Sign Signature Field"
|
||||
msgstr "Campo firma firma"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-text-dialog.tsx
|
||||
msgid "Sign Text Field"
|
||||
msgstr "Campo testo firma"
|
||||
|
||||
#: apps/remix/app/components/embed/embed-direct-template-client-page.tsx
|
||||
#: apps/remix/app/components/embed/embed-document-signing-page-v1.tsx
|
||||
#: apps/remix/app/components/embed/multisign/multi-sign-document-signing-view.tsx
|
||||
@@ -8018,18 +8057,6 @@ msgstr "Registrati con Microsoft"
|
||||
msgid "Sign Up with OIDC"
|
||||
msgstr "Iscriviti con OIDC"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-email-dialog.tsx
|
||||
msgid "Sign your email into the field"
|
||||
msgstr "Firma la tua email nel campo"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-name-dialog.tsx
|
||||
msgid "Sign your full name into the field"
|
||||
msgstr "Firma il tuo nome completo nel campo"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-initials-dialog.tsx
|
||||
msgid "Sign your initials into the field"
|
||||
msgstr "Firma le tue iniziali nel campo"
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
|
||||
#: apps/remix/app/components/embed/embed-direct-template-client-page.tsx
|
||||
#: apps/remix/app/components/embed/embed-document-signing-page-v1.tsx
|
||||
|
||||
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: ja\n"
|
||||
"Project-Id-Version: documenso-app\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2026-01-06 04:17\n"
|
||||
"PO-Revision-Date: 2026-01-13 02:39\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Japanese\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
@@ -1291,6 +1291,7 @@ msgid "Admin Panel"
|
||||
msgstr "管理画面"
|
||||
|
||||
#: apps/remix/app/components/dialogs/folder-update-dialog.tsx
|
||||
#: packages/lib/constants/document-visibility.ts
|
||||
msgid "Admins only"
|
||||
msgstr "管理者のみ"
|
||||
|
||||
@@ -2578,6 +2579,7 @@ msgid "Configure the team roles for each member"
|
||||
msgstr "メンバーごとにチームロールを設定します"
|
||||
|
||||
#: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-checkbox-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/template-direct-link-dialog.tsx
|
||||
msgid "Confirm"
|
||||
msgstr "確認"
|
||||
@@ -3734,6 +3736,7 @@ msgctxt "Audit log format"
|
||||
msgid "Document sent"
|
||||
msgstr "ドキュメントが送信されました"
|
||||
|
||||
#: apps/remix/app/components/general/envelope-editor/envelope-editor-settings-dialog.tsx
|
||||
#: apps/remix/app/components/general/envelope-editor/envelope-editor.tsx
|
||||
msgid "Document Settings"
|
||||
msgstr "ドキュメント設定"
|
||||
@@ -4321,6 +4324,14 @@ msgstr "同封文書"
|
||||
msgid "Enclosed Documents"
|
||||
msgstr "同封された文書"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-email-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-initials-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-name-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-number-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-text-dialog.tsx
|
||||
msgid "Enter"
|
||||
msgstr "入力してください"
|
||||
|
||||
#: apps/remix/app/components/dialogs/folder-create-dialog.tsx
|
||||
msgid "Enter a name for your new folder. Folders help you organise your items."
|
||||
msgstr "新しいフォルダ名を入力してください。フォルダを使うとアイテムを整理できます。"
|
||||
@@ -4329,6 +4340,26 @@ msgstr "新しいフォルダ名を入力してください。フォルダを使
|
||||
msgid "Enter claim name"
|
||||
msgstr "クレーム名を入力"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-email-dialog.tsx
|
||||
msgid "Enter Email"
|
||||
msgstr "メールアドレスを入力してください"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-initials-dialog.tsx
|
||||
msgid "Enter Initials"
|
||||
msgstr "イニシャルを入力してください"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-name-dialog.tsx
|
||||
msgid "Enter Name"
|
||||
msgstr "氏名を入力してください"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-number-dialog.tsx
|
||||
msgid "Enter Number"
|
||||
msgstr "番号を入力してください"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-text-dialog.tsx
|
||||
msgid "Enter Text"
|
||||
msgstr "テキストを入力してください"
|
||||
|
||||
#: apps/remix/app/components/dialogs/organisation-email-domain-create-dialog.tsx
|
||||
msgid "Enter the domain you want to use for sending emails (without http:// or www)"
|
||||
msgstr "メール送信に使用したいドメインを入力してください(http:// や www は不要)"
|
||||
@@ -4523,6 +4554,7 @@ msgid "Event Type"
|
||||
msgstr "イベントタイプ"
|
||||
|
||||
#: apps/remix/app/components/dialogs/folder-update-dialog.tsx
|
||||
#: packages/lib/constants/document-visibility.ts
|
||||
#: packages/ui/components/document/document-visibility-select.tsx
|
||||
msgid "Everyone"
|
||||
msgstr "全員"
|
||||
@@ -5222,14 +5254,6 @@ msgstr "イニシャルは必須です"
|
||||
msgid "Initials Settings"
|
||||
msgstr "イニシャルの設定"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-number-dialog.tsx
|
||||
msgid "Insert a value into the number field"
|
||||
msgstr "数値フィールドに値を入力してください"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-text-dialog.tsx
|
||||
msgid "Insert a value into the text field"
|
||||
msgstr "テキストフィールドに値を入力してください"
|
||||
|
||||
#: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx
|
||||
msgid "Inserted"
|
||||
msgstr "挿入済み"
|
||||
@@ -5738,6 +5762,7 @@ msgid "Manager"
|
||||
msgstr "マネージャー"
|
||||
|
||||
#: apps/remix/app/components/dialogs/folder-update-dialog.tsx
|
||||
#: packages/lib/constants/document-visibility.ts
|
||||
msgid "Managers and above"
|
||||
msgstr "マネージャー以上"
|
||||
|
||||
@@ -6133,6 +6158,10 @@ msgstr "大丈夫です。メールアドレスを入力していただければ
|
||||
msgid "None"
|
||||
msgstr "なし"
|
||||
|
||||
#: packages/lib/constants/document-auth.ts
|
||||
msgid "None (Overrides global settings)"
|
||||
msgstr "なし(グローバル設定を上書き)"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/documents.$id._index.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/documents.$id.edit.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/templates.$id._index.tsx
|
||||
@@ -6696,6 +6725,10 @@ msgstr "詳しくはサイトのオーナーにお問い合わせください。
|
||||
msgid "Please enter a meaningful name for your token. This will help you identify it later."
|
||||
msgstr "トークンの用途が分かる名前を入力してください。後から識別しやすくなります。"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-number-dialog.tsx
|
||||
msgid "Please enter a number"
|
||||
msgstr "数値を入力してください"
|
||||
|
||||
#: apps/remix/app/components/forms/profile.tsx
|
||||
#: apps/remix/app/components/forms/signup.tsx
|
||||
#: apps/remix/app/components/general/claim-account.tsx
|
||||
@@ -6706,6 +6739,22 @@ msgstr "有効な名前を入力してください。"
|
||||
msgid "Please enter a valid number"
|
||||
msgstr "有効な数値を入力してください"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-text-dialog.tsx
|
||||
msgid "Please enter a value"
|
||||
msgstr "値を入力してください"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-email-dialog.tsx
|
||||
msgid "Please enter your email address"
|
||||
msgstr "メールアドレスを入力してください"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-name-dialog.tsx
|
||||
msgid "Please enter your full name"
|
||||
msgstr "氏名を入力してください"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-initials-dialog.tsx
|
||||
msgid "Please enter your initials"
|
||||
msgstr "イニシャルを入力してください"
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-mobile-widget.tsx
|
||||
msgid "Please mark as viewed to complete"
|
||||
msgstr "完了するには閲覧済みにしてください"
|
||||
@@ -7250,6 +7299,22 @@ msgstr "リクエスト"
|
||||
msgid "Requesting Organisation"
|
||||
msgstr "要求元の組織"
|
||||
|
||||
#: packages/lib/constants/document-auth.ts
|
||||
msgid "Require 2FA"
|
||||
msgstr "2 要素認証を必須にする"
|
||||
|
||||
#: packages/lib/constants/document-auth.ts
|
||||
msgid "Require account"
|
||||
msgstr "アカウントを必須にする"
|
||||
|
||||
#: packages/lib/constants/document-auth.ts
|
||||
msgid "Require passkey"
|
||||
msgstr "パスキーを必須にする"
|
||||
|
||||
#: packages/lib/constants/document-auth.ts
|
||||
msgid "Require password"
|
||||
msgstr "パスワードを必須にする"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/checkbox-field.tsx
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/dropdown-field.tsx
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx
|
||||
@@ -7662,6 +7727,10 @@ msgstr "このチームに追加するメンバーを選択"
|
||||
msgid "Select Option"
|
||||
msgstr "オプションを選択"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-checkbox-dialog.tsx
|
||||
msgid "Select Options"
|
||||
msgstr "オプションを選択"
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auth-passkey.tsx
|
||||
msgid "Select passkey"
|
||||
msgstr "パスキーを選択"
|
||||
@@ -7884,13 +7953,7 @@ msgstr "詳細設定を表示"
|
||||
msgid "Show templates in your public profile for your audience to sign and get started quickly"
|
||||
msgstr "オーディエンスがすぐに署名を開始できるよう、公開プロフィールにテンプレートを表示します"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-checkbox-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-email-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-initials-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-name-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-number-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-signature-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-text-dialog.tsx
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auth-2fa.tsx
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auth-password.tsx
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auto-sign.tsx
|
||||
@@ -7924,10 +7987,6 @@ msgstr "{0} として署名 <0>({1})</0>"
|
||||
msgid "Sign as<0>{0} <1>({1})</1></0>"
|
||||
msgstr "<0>{0} <1>({1})</1></0>として署名"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-checkbox-dialog.tsx
|
||||
msgid "Sign Checkbox Field"
|
||||
msgstr "署名用チェックボックスフィールド"
|
||||
|
||||
#: apps/remix/app/components/embed/embed-direct-template-client-page.tsx
|
||||
#: apps/remix/app/components/embed/embed-document-signing-page-v1.tsx
|
||||
#: apps/remix/app/components/embed/multisign/multi-sign-document-signing-view.tsx
|
||||
@@ -7944,10 +8003,6 @@ msgstr "文書に署名"
|
||||
msgid "Sign Documents"
|
||||
msgstr "ドキュメントに署名"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-email-dialog.tsx
|
||||
msgid "Sign Email"
|
||||
msgstr "署名用メールアドレス"
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auth-dialog.tsx
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auth-dialog.tsx
|
||||
msgid "Sign field"
|
||||
@@ -7970,18 +8025,6 @@ msgstr "サインイン"
|
||||
msgid "Sign in to your account"
|
||||
msgstr "アカウントにサインイン"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-initials-dialog.tsx
|
||||
msgid "Sign Initials"
|
||||
msgstr "署名用イニシャル"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-name-dialog.tsx
|
||||
msgid "Sign Name"
|
||||
msgstr "署名用氏名"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-number-dialog.tsx
|
||||
msgid "Sign Number Field"
|
||||
msgstr "署名用数値フィールド"
|
||||
|
||||
#: apps/remix/app/components/general/app-nav-mobile.tsx
|
||||
#: apps/remix/app/components/general/menu-switcher.tsx
|
||||
#: apps/remix/app/components/general/org-menu-switcher.tsx
|
||||
@@ -7992,10 +8035,6 @@ msgstr "サインアウト"
|
||||
msgid "Sign Signature Field"
|
||||
msgstr "署名用署名フィールド"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-text-dialog.tsx
|
||||
msgid "Sign Text Field"
|
||||
msgstr "署名用テキストフィールド"
|
||||
|
||||
#: apps/remix/app/components/embed/embed-direct-template-client-page.tsx
|
||||
#: apps/remix/app/components/embed/embed-document-signing-page-v1.tsx
|
||||
#: apps/remix/app/components/embed/multisign/multi-sign-document-signing-view.tsx
|
||||
@@ -8018,18 +8057,6 @@ msgstr "Microsoftでサインアップ"
|
||||
msgid "Sign Up with OIDC"
|
||||
msgstr "OIDC でサインアップ"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-email-dialog.tsx
|
||||
msgid "Sign your email into the field"
|
||||
msgstr "メールアドレスをフィールドに入力してください"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-name-dialog.tsx
|
||||
msgid "Sign your full name into the field"
|
||||
msgstr "氏名をフィールドに入力してください"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-initials-dialog.tsx
|
||||
msgid "Sign your initials into the field"
|
||||
msgstr "イニシャルをフィールドに入力してください"
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
|
||||
#: apps/remix/app/components/embed/embed-direct-template-client-page.tsx
|
||||
#: apps/remix/app/components/embed/embed-document-signing-page-v1.tsx
|
||||
|
||||
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: ko\n"
|
||||
"Project-Id-Version: documenso-app\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2026-01-06 04:17\n"
|
||||
"PO-Revision-Date: 2026-01-13 02:39\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Korean\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
@@ -1291,6 +1291,7 @@ msgid "Admin Panel"
|
||||
msgstr "관리자 패널"
|
||||
|
||||
#: apps/remix/app/components/dialogs/folder-update-dialog.tsx
|
||||
#: packages/lib/constants/document-visibility.ts
|
||||
msgid "Admins only"
|
||||
msgstr "관리자 전용"
|
||||
|
||||
@@ -2578,6 +2579,7 @@ msgid "Configure the team roles for each member"
|
||||
msgstr "각 구성원에 대한 팀 역할을 구성하세요."
|
||||
|
||||
#: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-checkbox-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/template-direct-link-dialog.tsx
|
||||
msgid "Confirm"
|
||||
msgstr "확인"
|
||||
@@ -3734,6 +3736,7 @@ msgctxt "Audit log format"
|
||||
msgid "Document sent"
|
||||
msgstr "문서가 전송되었습니다."
|
||||
|
||||
#: apps/remix/app/components/general/envelope-editor/envelope-editor-settings-dialog.tsx
|
||||
#: apps/remix/app/components/general/envelope-editor/envelope-editor.tsx
|
||||
msgid "Document Settings"
|
||||
msgstr "문서 설정"
|
||||
@@ -4321,6 +4324,14 @@ msgstr "동봉 문서"
|
||||
msgid "Enclosed Documents"
|
||||
msgstr "동봉된 문서들"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-email-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-initials-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-name-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-number-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-text-dialog.tsx
|
||||
msgid "Enter"
|
||||
msgstr "입력하세요"
|
||||
|
||||
#: apps/remix/app/components/dialogs/folder-create-dialog.tsx
|
||||
msgid "Enter a name for your new folder. Folders help you organise your items."
|
||||
msgstr "새 폴더 이름을 입력하세요. 폴더는 항목을 정리하는 데 도움이 됩니다."
|
||||
@@ -4329,6 +4340,26 @@ msgstr "새 폴더 이름을 입력하세요. 폴더는 항목을 정리하는
|
||||
msgid "Enter claim name"
|
||||
msgstr "클레임 이름 입력"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-email-dialog.tsx
|
||||
msgid "Enter Email"
|
||||
msgstr "이메일을 입력하세요"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-initials-dialog.tsx
|
||||
msgid "Enter Initials"
|
||||
msgstr "이니셜을 입력하세요"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-name-dialog.tsx
|
||||
msgid "Enter Name"
|
||||
msgstr "이름을 입력하세요"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-number-dialog.tsx
|
||||
msgid "Enter Number"
|
||||
msgstr "숫자를 입력하세요"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-text-dialog.tsx
|
||||
msgid "Enter Text"
|
||||
msgstr "텍스트를 입력하세요"
|
||||
|
||||
#: apps/remix/app/components/dialogs/organisation-email-domain-create-dialog.tsx
|
||||
msgid "Enter the domain you want to use for sending emails (without http:// or www)"
|
||||
msgstr "이메일 발신에 사용할 도메인을 입력하세요( http:// 또는 www 제외)."
|
||||
@@ -4523,6 +4554,7 @@ msgid "Event Type"
|
||||
msgstr "이벤트 유형"
|
||||
|
||||
#: apps/remix/app/components/dialogs/folder-update-dialog.tsx
|
||||
#: packages/lib/constants/document-visibility.ts
|
||||
#: packages/ui/components/document/document-visibility-select.tsx
|
||||
msgid "Everyone"
|
||||
msgstr "모두"
|
||||
@@ -5222,14 +5254,6 @@ msgstr "이니셜은 필수 항목입니다"
|
||||
msgid "Initials Settings"
|
||||
msgstr "이니셜 설정"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-number-dialog.tsx
|
||||
msgid "Insert a value into the number field"
|
||||
msgstr "숫자 필드에 값을 입력하세요"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-text-dialog.tsx
|
||||
msgid "Insert a value into the text field"
|
||||
msgstr "텍스트 필드에 값을 입력하세요"
|
||||
|
||||
#: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx
|
||||
msgid "Inserted"
|
||||
msgstr "삽입됨"
|
||||
@@ -5738,6 +5762,7 @@ msgid "Manager"
|
||||
msgstr "관리자"
|
||||
|
||||
#: apps/remix/app/components/dialogs/folder-update-dialog.tsx
|
||||
#: packages/lib/constants/document-visibility.ts
|
||||
msgid "Managers and above"
|
||||
msgstr "관리자 이상"
|
||||
|
||||
@@ -6133,6 +6158,10 @@ msgstr "걱정하지 마세요. 이메일을 입력하시면 비밀번호를 재
|
||||
msgid "None"
|
||||
msgstr "없음"
|
||||
|
||||
#: packages/lib/constants/document-auth.ts
|
||||
msgid "None (Overrides global settings)"
|
||||
msgstr "없음(전역 설정 무시)"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/documents.$id._index.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/documents.$id.edit.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/templates.$id._index.tsx
|
||||
@@ -6696,6 +6725,10 @@ msgstr "사이트 소유자에게 추가 지원을 요청해 주세요."
|
||||
msgid "Please enter a meaningful name for your token. This will help you identify it later."
|
||||
msgstr "토큰을 나중에 식별할 수 있도록 의미 있는 이름을 입력해 주세요."
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-number-dialog.tsx
|
||||
msgid "Please enter a number"
|
||||
msgstr "숫자를 입력하세요"
|
||||
|
||||
#: apps/remix/app/components/forms/profile.tsx
|
||||
#: apps/remix/app/components/forms/signup.tsx
|
||||
#: apps/remix/app/components/general/claim-account.tsx
|
||||
@@ -6706,6 +6739,22 @@ msgstr "올바른 이름을 입력해 주세요."
|
||||
msgid "Please enter a valid number"
|
||||
msgstr "올바른 숫자를 입력하세요"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-text-dialog.tsx
|
||||
msgid "Please enter a value"
|
||||
msgstr "값을 입력하세요"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-email-dialog.tsx
|
||||
msgid "Please enter your email address"
|
||||
msgstr "이메일 주소를 입력하세요"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-name-dialog.tsx
|
||||
msgid "Please enter your full name"
|
||||
msgstr "전체 이름을 입력하세요"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-initials-dialog.tsx
|
||||
msgid "Please enter your initials"
|
||||
msgstr "이니셜을 입력하세요"
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-mobile-widget.tsx
|
||||
msgid "Please mark as viewed to complete"
|
||||
msgstr "완료하려면 확인함으로 표시해 주세요"
|
||||
@@ -7250,6 +7299,22 @@ msgstr "요청"
|
||||
msgid "Requesting Organisation"
|
||||
msgstr "요청하는 조직"
|
||||
|
||||
#: packages/lib/constants/document-auth.ts
|
||||
msgid "Require 2FA"
|
||||
msgstr "2단계 인증 필수"
|
||||
|
||||
#: packages/lib/constants/document-auth.ts
|
||||
msgid "Require account"
|
||||
msgstr "계정 필수"
|
||||
|
||||
#: packages/lib/constants/document-auth.ts
|
||||
msgid "Require passkey"
|
||||
msgstr "패스키 필수"
|
||||
|
||||
#: packages/lib/constants/document-auth.ts
|
||||
msgid "Require password"
|
||||
msgstr "비밀번호 필수"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/checkbox-field.tsx
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/dropdown-field.tsx
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx
|
||||
@@ -7662,6 +7727,10 @@ msgstr "이 팀에 추가할 구성원을 선택하세요."
|
||||
msgid "Select Option"
|
||||
msgstr "옵션 선택"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-checkbox-dialog.tsx
|
||||
msgid "Select Options"
|
||||
msgstr "옵션 선택"
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auth-passkey.tsx
|
||||
msgid "Select passkey"
|
||||
msgstr "패스키 선택"
|
||||
@@ -7884,13 +7953,7 @@ msgstr "고급 설정 표시"
|
||||
msgid "Show templates in your public profile for your audience to sign and get started quickly"
|
||||
msgstr "공개 프로필에 템플릿을 표시해, 사용자가 빠르게 서명하고 시작할 수 있도록 하세요"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-checkbox-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-email-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-initials-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-name-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-number-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-signature-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-text-dialog.tsx
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auth-2fa.tsx
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auth-password.tsx
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auto-sign.tsx
|
||||
@@ -7924,10 +7987,6 @@ msgstr "{0} <0>({1})</0>로 서명"
|
||||
msgid "Sign as<0>{0} <1>({1})</1></0>"
|
||||
msgstr "<0>{0} <1>({1})</1></0>로 서명"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-checkbox-dialog.tsx
|
||||
msgid "Sign Checkbox Field"
|
||||
msgstr "체크박스 필드 서명"
|
||||
|
||||
#: apps/remix/app/components/embed/embed-direct-template-client-page.tsx
|
||||
#: apps/remix/app/components/embed/embed-document-signing-page-v1.tsx
|
||||
#: apps/remix/app/components/embed/multisign/multi-sign-document-signing-view.tsx
|
||||
@@ -7944,10 +8003,6 @@ msgstr "문서 서명"
|
||||
msgid "Sign Documents"
|
||||
msgstr "문서 서명"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-email-dialog.tsx
|
||||
msgid "Sign Email"
|
||||
msgstr "이메일 서명"
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auth-dialog.tsx
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auth-dialog.tsx
|
||||
msgid "Sign field"
|
||||
@@ -7970,18 +8025,6 @@ msgstr "로그인"
|
||||
msgid "Sign in to your account"
|
||||
msgstr "계정에 로그인"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-initials-dialog.tsx
|
||||
msgid "Sign Initials"
|
||||
msgstr "이니셜 서명"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-name-dialog.tsx
|
||||
msgid "Sign Name"
|
||||
msgstr "이름 서명"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-number-dialog.tsx
|
||||
msgid "Sign Number Field"
|
||||
msgstr "숫자 필드 서명"
|
||||
|
||||
#: apps/remix/app/components/general/app-nav-mobile.tsx
|
||||
#: apps/remix/app/components/general/menu-switcher.tsx
|
||||
#: apps/remix/app/components/general/org-menu-switcher.tsx
|
||||
@@ -7992,10 +8035,6 @@ msgstr "로그아웃"
|
||||
msgid "Sign Signature Field"
|
||||
msgstr "서명 필드 서명"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-text-dialog.tsx
|
||||
msgid "Sign Text Field"
|
||||
msgstr "텍스트 필드 서명"
|
||||
|
||||
#: apps/remix/app/components/embed/embed-direct-template-client-page.tsx
|
||||
#: apps/remix/app/components/embed/embed-document-signing-page-v1.tsx
|
||||
#: apps/remix/app/components/embed/multisign/multi-sign-document-signing-view.tsx
|
||||
@@ -8018,18 +8057,6 @@ msgstr "Microsoft로 가입"
|
||||
msgid "Sign Up with OIDC"
|
||||
msgstr "OIDC로 가입"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-email-dialog.tsx
|
||||
msgid "Sign your email into the field"
|
||||
msgstr "필드에 이메일을 입력하세요"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-name-dialog.tsx
|
||||
msgid "Sign your full name into the field"
|
||||
msgstr "필드에 전체 이름을 입력하세요"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-initials-dialog.tsx
|
||||
msgid "Sign your initials into the field"
|
||||
msgstr "필드에 이니셜을 입력하세요"
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
|
||||
#: apps/remix/app/components/embed/embed-direct-template-client-page.tsx
|
||||
#: apps/remix/app/components/embed/embed-document-signing-page-v1.tsx
|
||||
|
||||
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: nl\n"
|
||||
"Project-Id-Version: documenso-app\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2026-01-06 04:17\n"
|
||||
"PO-Revision-Date: 2026-01-13 02:39\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Dutch\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
@@ -1291,6 +1291,7 @@ msgid "Admin Panel"
|
||||
msgstr "Beheerderspaneel"
|
||||
|
||||
#: apps/remix/app/components/dialogs/folder-update-dialog.tsx
|
||||
#: packages/lib/constants/document-visibility.ts
|
||||
msgid "Admins only"
|
||||
msgstr "Alleen beheerders"
|
||||
|
||||
@@ -2578,6 +2579,7 @@ msgid "Configure the team roles for each member"
|
||||
msgstr "Configureer de teamrollen voor elk lid"
|
||||
|
||||
#: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-checkbox-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/template-direct-link-dialog.tsx
|
||||
msgid "Confirm"
|
||||
msgstr "Bevestigen"
|
||||
@@ -3734,6 +3736,7 @@ msgctxt "Audit log format"
|
||||
msgid "Document sent"
|
||||
msgstr "Document verzonden"
|
||||
|
||||
#: apps/remix/app/components/general/envelope-editor/envelope-editor-settings-dialog.tsx
|
||||
#: apps/remix/app/components/general/envelope-editor/envelope-editor.tsx
|
||||
msgid "Document Settings"
|
||||
msgstr "Documentinstellingen"
|
||||
@@ -4321,6 +4324,14 @@ msgstr "Bijgevoegd document"
|
||||
msgid "Enclosed Documents"
|
||||
msgstr "Bijgevoegde documenten"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-email-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-initials-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-name-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-number-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-text-dialog.tsx
|
||||
msgid "Enter"
|
||||
msgstr "Invoeren"
|
||||
|
||||
#: apps/remix/app/components/dialogs/folder-create-dialog.tsx
|
||||
msgid "Enter a name for your new folder. Folders help you organise your items."
|
||||
msgstr "Voer een naam in voor je nieuwe map. Mappen helpen je je items te organiseren."
|
||||
@@ -4329,6 +4340,26 @@ msgstr "Voer een naam in voor je nieuwe map. Mappen helpen je je items te organi
|
||||
msgid "Enter claim name"
|
||||
msgstr "Voer claimnaam in"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-email-dialog.tsx
|
||||
msgid "Enter Email"
|
||||
msgstr "E-mailadres invoeren"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-initials-dialog.tsx
|
||||
msgid "Enter Initials"
|
||||
msgstr "Initialen invoeren"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-name-dialog.tsx
|
||||
msgid "Enter Name"
|
||||
msgstr "Naam invoeren"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-number-dialog.tsx
|
||||
msgid "Enter Number"
|
||||
msgstr "Nummer invoeren"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-text-dialog.tsx
|
||||
msgid "Enter Text"
|
||||
msgstr "Tekst invoeren"
|
||||
|
||||
#: apps/remix/app/components/dialogs/organisation-email-domain-create-dialog.tsx
|
||||
msgid "Enter the domain you want to use for sending emails (without http:// or www)"
|
||||
msgstr "Voer het domein in dat je wilt gebruiken voor het verzenden van e-mails (zonder http:// of www)"
|
||||
@@ -4523,6 +4554,7 @@ msgid "Event Type"
|
||||
msgstr "Gebeurtenistype"
|
||||
|
||||
#: apps/remix/app/components/dialogs/folder-update-dialog.tsx
|
||||
#: packages/lib/constants/document-visibility.ts
|
||||
#: packages/ui/components/document/document-visibility-select.tsx
|
||||
msgid "Everyone"
|
||||
msgstr "Iedereen"
|
||||
@@ -5222,14 +5254,6 @@ msgstr "Initialen zijn verplicht"
|
||||
msgid "Initials Settings"
|
||||
msgstr "Initialen-instellingen"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-number-dialog.tsx
|
||||
msgid "Insert a value into the number field"
|
||||
msgstr "Vul een waarde in het nummerveld in"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-text-dialog.tsx
|
||||
msgid "Insert a value into the text field"
|
||||
msgstr "Vul een waarde in het tekstveld in"
|
||||
|
||||
#: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx
|
||||
msgid "Inserted"
|
||||
msgstr "Ingevoegd"
|
||||
@@ -5738,6 +5762,7 @@ msgid "Manager"
|
||||
msgstr "Manager"
|
||||
|
||||
#: apps/remix/app/components/dialogs/folder-update-dialog.tsx
|
||||
#: packages/lib/constants/document-visibility.ts
|
||||
msgid "Managers and above"
|
||||
msgstr "Managers en hoger"
|
||||
|
||||
@@ -6133,6 +6158,10 @@ msgstr "Geen zorgen, dat gebeurt wel eens! Vul je e‑mail in en we sturen je ee
|
||||
msgid "None"
|
||||
msgstr "Geen"
|
||||
|
||||
#: packages/lib/constants/document-auth.ts
|
||||
msgid "None (Overrides global settings)"
|
||||
msgstr "Geen (Overschrijft globale instellingen)"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/documents.$id._index.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/documents.$id.edit.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/templates.$id._index.tsx
|
||||
@@ -6696,6 +6725,10 @@ msgstr "Neem contact op met de site-eigenaar voor verdere hulp."
|
||||
msgid "Please enter a meaningful name for your token. This will help you identify it later."
|
||||
msgstr "Voer een betekenisvolle naam in voor je token. Hiermee kun je het later herkennen."
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-number-dialog.tsx
|
||||
msgid "Please enter a number"
|
||||
msgstr "Voer een nummer in"
|
||||
|
||||
#: apps/remix/app/components/forms/profile.tsx
|
||||
#: apps/remix/app/components/forms/signup.tsx
|
||||
#: apps/remix/app/components/general/claim-account.tsx
|
||||
@@ -6706,6 +6739,22 @@ msgstr "Voer een geldige naam in."
|
||||
msgid "Please enter a valid number"
|
||||
msgstr "Voer een geldig nummer in"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-text-dialog.tsx
|
||||
msgid "Please enter a value"
|
||||
msgstr "Voer een waarde in"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-email-dialog.tsx
|
||||
msgid "Please enter your email address"
|
||||
msgstr "Voer je e-mailadres in"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-name-dialog.tsx
|
||||
msgid "Please enter your full name"
|
||||
msgstr "Voer je volledige naam in"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-initials-dialog.tsx
|
||||
msgid "Please enter your initials"
|
||||
msgstr "Vul je initialen in"
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-mobile-widget.tsx
|
||||
msgid "Please mark as viewed to complete"
|
||||
msgstr "Markeer als bekeken om te voltooien"
|
||||
@@ -7250,6 +7299,22 @@ msgstr "Verzoek"
|
||||
msgid "Requesting Organisation"
|
||||
msgstr "Aanvragende organisatie"
|
||||
|
||||
#: packages/lib/constants/document-auth.ts
|
||||
msgid "Require 2FA"
|
||||
msgstr "2FA vereisen"
|
||||
|
||||
#: packages/lib/constants/document-auth.ts
|
||||
msgid "Require account"
|
||||
msgstr "Account vereisen"
|
||||
|
||||
#: packages/lib/constants/document-auth.ts
|
||||
msgid "Require passkey"
|
||||
msgstr "Passkey vereisen"
|
||||
|
||||
#: packages/lib/constants/document-auth.ts
|
||||
msgid "Require password"
|
||||
msgstr "Wachtwoord vereisen"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/checkbox-field.tsx
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/dropdown-field.tsx
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx
|
||||
@@ -7662,6 +7727,10 @@ msgstr "Selecteer leden om aan dit team toe te voegen"
|
||||
msgid "Select Option"
|
||||
msgstr "Selecteer optie"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-checkbox-dialog.tsx
|
||||
msgid "Select Options"
|
||||
msgstr "Selecteer opties"
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auth-passkey.tsx
|
||||
msgid "Select passkey"
|
||||
msgstr "Passkey selecteren"
|
||||
@@ -7884,13 +7953,7 @@ msgstr "Geavanceerde instellingen weergeven"
|
||||
msgid "Show templates in your public profile for your audience to sign and get started quickly"
|
||||
msgstr "Toon sjablonen in je openbare profiel zodat je publiek snel kan ondertekenen en beginnen"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-checkbox-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-email-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-initials-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-name-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-number-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-signature-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-text-dialog.tsx
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auth-2fa.tsx
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auth-password.tsx
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auto-sign.tsx
|
||||
@@ -7924,10 +7987,6 @@ msgstr "Ondertekenen als {0} <0>({1})</0>"
|
||||
msgid "Sign as<0>{0} <1>({1})</1></0>"
|
||||
msgstr "Ondertekenen als<0>{0} <1>({1})</1></0>"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-checkbox-dialog.tsx
|
||||
msgid "Sign Checkbox Field"
|
||||
msgstr "Checkboxveld ondertekenen"
|
||||
|
||||
#: apps/remix/app/components/embed/embed-direct-template-client-page.tsx
|
||||
#: apps/remix/app/components/embed/embed-document-signing-page-v1.tsx
|
||||
#: apps/remix/app/components/embed/multisign/multi-sign-document-signing-view.tsx
|
||||
@@ -7944,10 +8003,6 @@ msgstr "Document ondertekenen"
|
||||
msgid "Sign Documents"
|
||||
msgstr "Documenten ondertekenen"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-email-dialog.tsx
|
||||
msgid "Sign Email"
|
||||
msgstr "E-mail ondertekenen"
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auth-dialog.tsx
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auth-dialog.tsx
|
||||
msgid "Sign field"
|
||||
@@ -7970,18 +8025,6 @@ msgstr "Inloggen"
|
||||
msgid "Sign in to your account"
|
||||
msgstr "Log in op je account"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-initials-dialog.tsx
|
||||
msgid "Sign Initials"
|
||||
msgstr "Initialen ondertekenen"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-name-dialog.tsx
|
||||
msgid "Sign Name"
|
||||
msgstr "Naam ondertekenen"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-number-dialog.tsx
|
||||
msgid "Sign Number Field"
|
||||
msgstr "Nummer-veld ondertekenen"
|
||||
|
||||
#: apps/remix/app/components/general/app-nav-mobile.tsx
|
||||
#: apps/remix/app/components/general/menu-switcher.tsx
|
||||
#: apps/remix/app/components/general/org-menu-switcher.tsx
|
||||
@@ -7992,10 +8035,6 @@ msgstr "Uitloggen"
|
||||
msgid "Sign Signature Field"
|
||||
msgstr "Handtekeningveld ondertekenen"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-text-dialog.tsx
|
||||
msgid "Sign Text Field"
|
||||
msgstr "Tekstveld ondertekenen"
|
||||
|
||||
#: apps/remix/app/components/embed/embed-direct-template-client-page.tsx
|
||||
#: apps/remix/app/components/embed/embed-document-signing-page-v1.tsx
|
||||
#: apps/remix/app/components/embed/multisign/multi-sign-document-signing-view.tsx
|
||||
@@ -8018,18 +8057,6 @@ msgstr "Registreren met Microsoft"
|
||||
msgid "Sign Up with OIDC"
|
||||
msgstr "Registreren met OIDC"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-email-dialog.tsx
|
||||
msgid "Sign your email into the field"
|
||||
msgstr "Schrijf uw e-mailadres in het veld"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-name-dialog.tsx
|
||||
msgid "Sign your full name into the field"
|
||||
msgstr "Schrijf uw volledige naam in het veld"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-initials-dialog.tsx
|
||||
msgid "Sign your initials into the field"
|
||||
msgstr "Schrijf uw initialen in het veld"
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
|
||||
#: apps/remix/app/components/embed/embed-direct-template-client-page.tsx
|
||||
#: apps/remix/app/components/embed/embed-document-signing-page-v1.tsx
|
||||
|
||||
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: pl\n"
|
||||
"Project-Id-Version: documenso-app\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2026-01-06 04:17\n"
|
||||
"PO-Revision-Date: 2026-01-13 02:39\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"
|
||||
@@ -1291,6 +1291,7 @@ msgid "Admin Panel"
|
||||
msgstr "Panel administratora"
|
||||
|
||||
#: apps/remix/app/components/dialogs/folder-update-dialog.tsx
|
||||
#: packages/lib/constants/document-visibility.ts
|
||||
msgid "Admins only"
|
||||
msgstr "Tylko dla administratorów"
|
||||
|
||||
@@ -2578,6 +2579,7 @@ msgid "Configure the team roles for each member"
|
||||
msgstr "Skonfiguruj role w zespole dla każdego użytkownika"
|
||||
|
||||
#: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-checkbox-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/template-direct-link-dialog.tsx
|
||||
msgid "Confirm"
|
||||
msgstr "Potwierdź"
|
||||
@@ -3734,6 +3736,7 @@ msgctxt "Audit log format"
|
||||
msgid "Document sent"
|
||||
msgstr "Wysłano dokument"
|
||||
|
||||
#: apps/remix/app/components/general/envelope-editor/envelope-editor-settings-dialog.tsx
|
||||
#: apps/remix/app/components/general/envelope-editor/envelope-editor.tsx
|
||||
msgid "Document Settings"
|
||||
msgstr "Ustawienia dokumentu"
|
||||
@@ -4319,7 +4322,15 @@ msgstr "Załączony dokument"
|
||||
|
||||
#: packages/lib/server-only/pdf/render-audit-logs.ts
|
||||
msgid "Enclosed Documents"
|
||||
msgstr "Załączone dokumenty"
|
||||
msgstr "Załączniki"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-email-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-initials-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-name-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-number-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-text-dialog.tsx
|
||||
msgid "Enter"
|
||||
msgstr "Wpisz lub wprowadź wartość"
|
||||
|
||||
#: apps/remix/app/components/dialogs/folder-create-dialog.tsx
|
||||
msgid "Enter a name for your new folder. Folders help you organise your items."
|
||||
@@ -4329,6 +4340,26 @@ msgstr "Wpisz nazwę nowego folderu. Foldery pomagają uporządkować elementy."
|
||||
msgid "Enter claim name"
|
||||
msgstr "Wpisz nazwę"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-email-dialog.tsx
|
||||
msgid "Enter Email"
|
||||
msgstr "Wpisz adres e-mail"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-initials-dialog.tsx
|
||||
msgid "Enter Initials"
|
||||
msgstr "Wpisz inicjały"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-name-dialog.tsx
|
||||
msgid "Enter Name"
|
||||
msgstr "Wpisz imię i nazwisko"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-number-dialog.tsx
|
||||
msgid "Enter Number"
|
||||
msgstr "Wpisz liczbę"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-text-dialog.tsx
|
||||
msgid "Enter Text"
|
||||
msgstr "Wpisz tekst"
|
||||
|
||||
#: apps/remix/app/components/dialogs/organisation-email-domain-create-dialog.tsx
|
||||
msgid "Enter the domain you want to use for sending emails (without http:// or www)"
|
||||
msgstr "Wpisz domenę do wysyłania wiadomości (bez http:// lub www)"
|
||||
@@ -4523,6 +4554,7 @@ msgid "Event Type"
|
||||
msgstr "Rodzaj zdarzenia"
|
||||
|
||||
#: apps/remix/app/components/dialogs/folder-update-dialog.tsx
|
||||
#: packages/lib/constants/document-visibility.ts
|
||||
#: packages/ui/components/document/document-visibility-select.tsx
|
||||
msgid "Everyone"
|
||||
msgstr "Wszyscy"
|
||||
@@ -5222,14 +5254,6 @@ msgstr "Inicjały są wymagane"
|
||||
msgid "Initials Settings"
|
||||
msgstr "Ustawienia inicjałów"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-number-dialog.tsx
|
||||
msgid "Insert a value into the number field"
|
||||
msgstr "Wpisz wartość do pola liczby"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-text-dialog.tsx
|
||||
msgid "Insert a value into the text field"
|
||||
msgstr "Wpisz wartość do pola tekstowego"
|
||||
|
||||
#: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx
|
||||
msgid "Inserted"
|
||||
msgstr "Wypełniono"
|
||||
@@ -5738,6 +5762,7 @@ msgid "Manager"
|
||||
msgstr "Manager"
|
||||
|
||||
#: apps/remix/app/components/dialogs/folder-update-dialog.tsx
|
||||
#: packages/lib/constants/document-visibility.ts
|
||||
msgid "Managers and above"
|
||||
msgstr "Managerowie i wyżej"
|
||||
|
||||
@@ -6133,6 +6158,10 @@ msgstr "Nie martw się! Wpisz swój adres e-mail, a my wyślemy Ci specjalny lin
|
||||
msgid "None"
|
||||
msgstr "Brak"
|
||||
|
||||
#: packages/lib/constants/document-auth.ts
|
||||
msgid "None (Overrides global settings)"
|
||||
msgstr "Brak (odziedzicz metodę uwierzytelniania)"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/documents.$id._index.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/documents.$id.edit.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/templates.$id._index.tsx
|
||||
@@ -6696,6 +6725,10 @@ msgstr "Skontaktuj się z właścicielem dokumentu, aby uzyskać pomoc."
|
||||
msgid "Please enter a meaningful name for your token. This will help you identify it later."
|
||||
msgstr "Wpisz nazwę tokena. Pomoże to później w jego identyfikacji."
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-number-dialog.tsx
|
||||
msgid "Please enter a number"
|
||||
msgstr "Wpisz liczbę"
|
||||
|
||||
#: apps/remix/app/components/forms/profile.tsx
|
||||
#: apps/remix/app/components/forms/signup.tsx
|
||||
#: apps/remix/app/components/general/claim-account.tsx
|
||||
@@ -6706,6 +6739,22 @@ msgstr "Wpisz prawidłową nazwę."
|
||||
msgid "Please enter a valid number"
|
||||
msgstr "Wpisz prawidłową liczbę"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-text-dialog.tsx
|
||||
msgid "Please enter a value"
|
||||
msgstr "Wpisz wartość"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-email-dialog.tsx
|
||||
msgid "Please enter your email address"
|
||||
msgstr "Wpisz swój adres e-mail"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-name-dialog.tsx
|
||||
msgid "Please enter your full name"
|
||||
msgstr "Wpisz swoje pełne imię i nazwisko"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-initials-dialog.tsx
|
||||
msgid "Please enter your initials"
|
||||
msgstr "Wprowadź swoje inicjały"
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-mobile-widget.tsx
|
||||
msgid "Please mark as viewed to complete"
|
||||
msgstr "Oznacz jako wyświetlony, aby zakończyć"
|
||||
@@ -7250,6 +7299,22 @@ msgstr "Żądanie"
|
||||
msgid "Requesting Organisation"
|
||||
msgstr "Wnioskująca organizacja"
|
||||
|
||||
#: packages/lib/constants/document-auth.ts
|
||||
msgid "Require 2FA"
|
||||
msgstr "Wymagana weryfikacja dwuetapowa"
|
||||
|
||||
#: packages/lib/constants/document-auth.ts
|
||||
msgid "Require account"
|
||||
msgstr "Wymagane konto"
|
||||
|
||||
#: packages/lib/constants/document-auth.ts
|
||||
msgid "Require passkey"
|
||||
msgstr "Wymagany klucz dostępu"
|
||||
|
||||
#: packages/lib/constants/document-auth.ts
|
||||
msgid "Require password"
|
||||
msgstr "Wymagane hasło"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/checkbox-field.tsx
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/dropdown-field.tsx
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx
|
||||
@@ -7662,6 +7727,10 @@ msgstr "Wybierz użytkowników, których chcesz dodać do zespołu"
|
||||
msgid "Select Option"
|
||||
msgstr "Wybierz opcję"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-checkbox-dialog.tsx
|
||||
msgid "Select Options"
|
||||
msgstr "Wybierz opcje"
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auth-passkey.tsx
|
||||
msgid "Select passkey"
|
||||
msgstr "Wybierz klucz dostępu"
|
||||
@@ -7884,13 +7953,7 @@ msgstr "Pokaż ustawienia zaawansowane"
|
||||
msgid "Show templates in your public profile for your audience to sign and get started quickly"
|
||||
msgstr "Pokaż szablony w profilu publicznym, aby szybko podpisać dokument"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-checkbox-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-email-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-initials-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-name-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-number-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-signature-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-text-dialog.tsx
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auth-2fa.tsx
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auth-password.tsx
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auto-sign.tsx
|
||||
@@ -7924,10 +7987,6 @@ msgstr "Podpisz jako {0} <0>({1})</0>"
|
||||
msgid "Sign as<0>{0} <1>({1})</1></0>"
|
||||
msgstr "Podpisz jako<0>{0} <1>({1})</1></0>"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-checkbox-dialog.tsx
|
||||
msgid "Sign Checkbox Field"
|
||||
msgstr "Podpisz pole wyboru"
|
||||
|
||||
#: apps/remix/app/components/embed/embed-direct-template-client-page.tsx
|
||||
#: apps/remix/app/components/embed/embed-document-signing-page-v1.tsx
|
||||
#: apps/remix/app/components/embed/multisign/multi-sign-document-signing-view.tsx
|
||||
@@ -7944,10 +8003,6 @@ msgstr "Podpisz dokument"
|
||||
msgid "Sign Documents"
|
||||
msgstr "Podpisz dokumenty"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-email-dialog.tsx
|
||||
msgid "Sign Email"
|
||||
msgstr "Podpisz adres e-mail"
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auth-dialog.tsx
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auth-dialog.tsx
|
||||
msgid "Sign field"
|
||||
@@ -7970,18 +8025,6 @@ msgstr "Zaloguj się"
|
||||
msgid "Sign in to your account"
|
||||
msgstr "Zaloguj się na swoje konto"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-initials-dialog.tsx
|
||||
msgid "Sign Initials"
|
||||
msgstr "Podpisz inicjały"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-name-dialog.tsx
|
||||
msgid "Sign Name"
|
||||
msgstr "Podpisz nazwę"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-number-dialog.tsx
|
||||
msgid "Sign Number Field"
|
||||
msgstr "Podpisz pole liczby"
|
||||
|
||||
#: apps/remix/app/components/general/app-nav-mobile.tsx
|
||||
#: apps/remix/app/components/general/menu-switcher.tsx
|
||||
#: apps/remix/app/components/general/org-menu-switcher.tsx
|
||||
@@ -7992,10 +8035,6 @@ msgstr "Wyloguj"
|
||||
msgid "Sign Signature Field"
|
||||
msgstr "Podpisz pole podpisu"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-text-dialog.tsx
|
||||
msgid "Sign Text Field"
|
||||
msgstr "Podpisz pole tekstowe"
|
||||
|
||||
#: apps/remix/app/components/embed/embed-direct-template-client-page.tsx
|
||||
#: apps/remix/app/components/embed/embed-document-signing-page-v1.tsx
|
||||
#: apps/remix/app/components/embed/multisign/multi-sign-document-signing-view.tsx
|
||||
@@ -8018,18 +8057,6 @@ msgstr "Zarejestruj się przez Microsoft"
|
||||
msgid "Sign Up with OIDC"
|
||||
msgstr "Zarejestruj się przez OIDC"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-email-dialog.tsx
|
||||
msgid "Sign your email into the field"
|
||||
msgstr "Podpisz adres e-mail w polu"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-name-dialog.tsx
|
||||
msgid "Sign your full name into the field"
|
||||
msgstr "Podpisz nazwę w polu"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-initials-dialog.tsx
|
||||
msgid "Sign your initials into the field"
|
||||
msgstr "Podpisz inicjały w polu"
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
|
||||
#: apps/remix/app/components/embed/embed-direct-template-client-page.tsx
|
||||
#: apps/remix/app/components/embed/embed-document-signing-page-v1.tsx
|
||||
|
||||
@@ -1286,6 +1286,7 @@ msgid "Admin Panel"
|
||||
msgstr "Painel de Administração"
|
||||
|
||||
#: apps/remix/app/components/dialogs/folder-update-dialog.tsx
|
||||
#: packages/lib/constants/document-visibility.ts
|
||||
msgid "Admins only"
|
||||
msgstr "Apenas administradores"
|
||||
|
||||
@@ -2573,6 +2574,7 @@ msgid "Configure the team roles for each member"
|
||||
msgstr "Configure as funções da equipe para cada membro"
|
||||
|
||||
#: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-checkbox-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/template-direct-link-dialog.tsx
|
||||
msgid "Confirm"
|
||||
msgstr "Confirmar"
|
||||
@@ -3729,6 +3731,7 @@ msgctxt "Audit log format"
|
||||
msgid "Document sent"
|
||||
msgstr "Documento enviado"
|
||||
|
||||
#: apps/remix/app/components/general/envelope-editor/envelope-editor-settings-dialog.tsx
|
||||
#: apps/remix/app/components/general/envelope-editor/envelope-editor.tsx
|
||||
msgid "Document Settings"
|
||||
msgstr "Configurações do Documento"
|
||||
@@ -4316,6 +4319,14 @@ msgstr "Documento Anexo"
|
||||
msgid "Enclosed Documents"
|
||||
msgstr ""
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-email-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-initials-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-name-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-number-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-text-dialog.tsx
|
||||
msgid "Enter"
|
||||
msgstr ""
|
||||
|
||||
#: apps/remix/app/components/dialogs/folder-create-dialog.tsx
|
||||
msgid "Enter a name for your new folder. Folders help you organise your items."
|
||||
msgstr "Digite um nome para sua nova pasta. As pastas ajudam você a organizar seus itens."
|
||||
@@ -4324,6 +4335,26 @@ msgstr "Digite um nome para sua nova pasta. As pastas ajudam você a organizar s
|
||||
msgid "Enter claim name"
|
||||
msgstr "Digite o nome da reivindicação"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-email-dialog.tsx
|
||||
msgid "Enter Email"
|
||||
msgstr ""
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-initials-dialog.tsx
|
||||
msgid "Enter Initials"
|
||||
msgstr ""
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-name-dialog.tsx
|
||||
msgid "Enter Name"
|
||||
msgstr ""
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-number-dialog.tsx
|
||||
msgid "Enter Number"
|
||||
msgstr ""
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-text-dialog.tsx
|
||||
msgid "Enter Text"
|
||||
msgstr ""
|
||||
|
||||
#: apps/remix/app/components/dialogs/organisation-email-domain-create-dialog.tsx
|
||||
msgid "Enter the domain you want to use for sending emails (without http:// or www)"
|
||||
msgstr "Digite o domínio que você deseja usar para enviar e-mails (sem http:// ou www)"
|
||||
@@ -4518,6 +4549,7 @@ msgid "Event Type"
|
||||
msgstr "Tipo de Evento"
|
||||
|
||||
#: apps/remix/app/components/dialogs/folder-update-dialog.tsx
|
||||
#: packages/lib/constants/document-visibility.ts
|
||||
#: packages/ui/components/document/document-visibility-select.tsx
|
||||
msgid "Everyone"
|
||||
msgstr "Todos"
|
||||
@@ -5217,14 +5249,6 @@ msgstr "Iniciais são obrigatórias"
|
||||
msgid "Initials Settings"
|
||||
msgstr "Configurações de Iniciais"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-number-dialog.tsx
|
||||
msgid "Insert a value into the number field"
|
||||
msgstr "Insira um valor no campo numérico"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-text-dialog.tsx
|
||||
msgid "Insert a value into the text field"
|
||||
msgstr "Insira um valor no campo de texto"
|
||||
|
||||
#: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx
|
||||
msgid "Inserted"
|
||||
msgstr "Inserido"
|
||||
@@ -5733,6 +5757,7 @@ msgid "Manager"
|
||||
msgstr "Gerente"
|
||||
|
||||
#: apps/remix/app/components/dialogs/folder-update-dialog.tsx
|
||||
#: packages/lib/constants/document-visibility.ts
|
||||
msgid "Managers and above"
|
||||
msgstr "Gerentes e acima"
|
||||
|
||||
@@ -6128,6 +6153,10 @@ msgstr "Não se preocupe, acontece! Digite seu e-mail e enviaremos um link espec
|
||||
msgid "None"
|
||||
msgstr "Nenhum"
|
||||
|
||||
#: packages/lib/constants/document-auth.ts
|
||||
msgid "None (Overrides global settings)"
|
||||
msgstr ""
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/documents.$id._index.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/documents.$id.edit.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/templates.$id._index.tsx
|
||||
@@ -6691,6 +6720,10 @@ msgstr "Por favor, entre em contato com o proprietário do site para mais assist
|
||||
msgid "Please enter a meaningful name for your token. This will help you identify it later."
|
||||
msgstr "Por favor, insira um nome significativo para seu token. Isso ajudará você a identificá-lo mais tarde."
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-number-dialog.tsx
|
||||
msgid "Please enter a number"
|
||||
msgstr ""
|
||||
|
||||
#: apps/remix/app/components/forms/profile.tsx
|
||||
#: apps/remix/app/components/forms/signup.tsx
|
||||
#: apps/remix/app/components/general/claim-account.tsx
|
||||
@@ -6701,6 +6734,22 @@ msgstr "Por favor, insira um nome válido."
|
||||
msgid "Please enter a valid number"
|
||||
msgstr "Por favor, insira um número válido"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-text-dialog.tsx
|
||||
msgid "Please enter a value"
|
||||
msgstr ""
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-email-dialog.tsx
|
||||
msgid "Please enter your email address"
|
||||
msgstr ""
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-name-dialog.tsx
|
||||
msgid "Please enter your full name"
|
||||
msgstr ""
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-initials-dialog.tsx
|
||||
msgid "Please enter your initials"
|
||||
msgstr ""
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-mobile-widget.tsx
|
||||
msgid "Please mark as viewed to complete"
|
||||
msgstr "Por favor, marque como visualizado para completar"
|
||||
@@ -7245,6 +7294,22 @@ msgstr "Solicitação"
|
||||
msgid "Requesting Organisation"
|
||||
msgstr "Organização Solicitante"
|
||||
|
||||
#: packages/lib/constants/document-auth.ts
|
||||
msgid "Require 2FA"
|
||||
msgstr ""
|
||||
|
||||
#: packages/lib/constants/document-auth.ts
|
||||
msgid "Require account"
|
||||
msgstr ""
|
||||
|
||||
#: packages/lib/constants/document-auth.ts
|
||||
msgid "Require passkey"
|
||||
msgstr ""
|
||||
|
||||
#: packages/lib/constants/document-auth.ts
|
||||
msgid "Require password"
|
||||
msgstr ""
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/checkbox-field.tsx
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/dropdown-field.tsx
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx
|
||||
@@ -7657,6 +7722,10 @@ msgstr "Selecione membros para adicionar a esta equipe"
|
||||
msgid "Select Option"
|
||||
msgstr "Selecionar Opção"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-checkbox-dialog.tsx
|
||||
msgid "Select Options"
|
||||
msgstr ""
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auth-passkey.tsx
|
||||
msgid "Select passkey"
|
||||
msgstr "Selecionar passkey"
|
||||
@@ -7879,13 +7948,7 @@ msgstr "Mostrar configurações avançadas"
|
||||
msgid "Show templates in your public profile for your audience to sign and get started quickly"
|
||||
msgstr "Mostre modelos em seu perfil público para seu público assinar e começar rapidamente"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-checkbox-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-email-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-initials-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-name-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-number-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-signature-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-text-dialog.tsx
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auth-2fa.tsx
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auth-password.tsx
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auto-sign.tsx
|
||||
@@ -7919,10 +7982,6 @@ msgstr "Assinar como {0} <0>({1})</0>"
|
||||
msgid "Sign as<0>{0} <1>({1})</1></0>"
|
||||
msgstr "Assinar como<0>{0} <1>({1})</1></0>"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-checkbox-dialog.tsx
|
||||
msgid "Sign Checkbox Field"
|
||||
msgstr "Assinar Campo de Caixa de Seleção"
|
||||
|
||||
#: apps/remix/app/components/embed/embed-direct-template-client-page.tsx
|
||||
#: apps/remix/app/components/embed/embed-document-signing-page-v1.tsx
|
||||
#: apps/remix/app/components/embed/multisign/multi-sign-document-signing-view.tsx
|
||||
@@ -7939,10 +7998,6 @@ msgstr "Assinar Documento"
|
||||
msgid "Sign Documents"
|
||||
msgstr "Assinar Documentos"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-email-dialog.tsx
|
||||
msgid "Sign Email"
|
||||
msgstr "Assinar E-mail"
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auth-dialog.tsx
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auth-dialog.tsx
|
||||
msgid "Sign field"
|
||||
@@ -7965,18 +8020,6 @@ msgstr "Entrar"
|
||||
msgid "Sign in to your account"
|
||||
msgstr "Entre na sua conta"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-initials-dialog.tsx
|
||||
msgid "Sign Initials"
|
||||
msgstr "Assinar Iniciais"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-name-dialog.tsx
|
||||
msgid "Sign Name"
|
||||
msgstr "Assinar Nome"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-number-dialog.tsx
|
||||
msgid "Sign Number Field"
|
||||
msgstr "Assinar Campo Numérico"
|
||||
|
||||
#: apps/remix/app/components/general/app-nav-mobile.tsx
|
||||
#: apps/remix/app/components/general/menu-switcher.tsx
|
||||
#: apps/remix/app/components/general/org-menu-switcher.tsx
|
||||
@@ -7987,10 +8030,6 @@ msgstr "Sair"
|
||||
msgid "Sign Signature Field"
|
||||
msgstr "Assinar Campo de Assinatura"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-text-dialog.tsx
|
||||
msgid "Sign Text Field"
|
||||
msgstr "Assinar Campo de Texto"
|
||||
|
||||
#: apps/remix/app/components/embed/embed-direct-template-client-page.tsx
|
||||
#: apps/remix/app/components/embed/embed-document-signing-page-v1.tsx
|
||||
#: apps/remix/app/components/embed/multisign/multi-sign-document-signing-view.tsx
|
||||
@@ -8013,18 +8052,6 @@ msgstr "Inscrever-se com Microsoft"
|
||||
msgid "Sign Up with OIDC"
|
||||
msgstr "Inscrever-se com OIDC"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-email-dialog.tsx
|
||||
msgid "Sign your email into the field"
|
||||
msgstr "Assine seu e-mail no campo"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-name-dialog.tsx
|
||||
msgid "Sign your full name into the field"
|
||||
msgstr "Assine seu nome completo no campo"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-initials-dialog.tsx
|
||||
msgid "Sign your initials into the field"
|
||||
msgstr "Assine suas iniciais no campo"
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
|
||||
#: apps/remix/app/components/embed/embed-direct-template-client-page.tsx
|
||||
#: apps/remix/app/components/embed/embed-document-signing-page-v1.tsx
|
||||
|
||||
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: zh\n"
|
||||
"Project-Id-Version: documenso-app\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2026-01-06 04:17\n"
|
||||
"PO-Revision-Date: 2026-01-13 02:39\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Chinese Simplified\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
@@ -1291,6 +1291,7 @@ msgid "Admin Panel"
|
||||
msgstr "管理面板"
|
||||
|
||||
#: apps/remix/app/components/dialogs/folder-update-dialog.tsx
|
||||
#: packages/lib/constants/document-visibility.ts
|
||||
msgid "Admins only"
|
||||
msgstr "仅限管理员"
|
||||
|
||||
@@ -2578,6 +2579,7 @@ msgid "Configure the team roles for each member"
|
||||
msgstr "配置每个成员的团队角色"
|
||||
|
||||
#: apps/remix/app/components/dialogs/public-profile-template-manage-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-checkbox-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/template-direct-link-dialog.tsx
|
||||
msgid "Confirm"
|
||||
msgstr "确认"
|
||||
@@ -3734,6 +3736,7 @@ msgctxt "Audit log format"
|
||||
msgid "Document sent"
|
||||
msgstr "文档已发送"
|
||||
|
||||
#: apps/remix/app/components/general/envelope-editor/envelope-editor-settings-dialog.tsx
|
||||
#: apps/remix/app/components/general/envelope-editor/envelope-editor.tsx
|
||||
msgid "Document Settings"
|
||||
msgstr "文档设置"
|
||||
@@ -4321,6 +4324,14 @@ msgstr "随附文档"
|
||||
msgid "Enclosed Documents"
|
||||
msgstr "随附文件"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-email-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-initials-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-name-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-number-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-text-dialog.tsx
|
||||
msgid "Enter"
|
||||
msgstr "输入"
|
||||
|
||||
#: apps/remix/app/components/dialogs/folder-create-dialog.tsx
|
||||
msgid "Enter a name for your new folder. Folders help you organise your items."
|
||||
msgstr "为新文件夹输入名称。文件夹可以帮助您整理项目。"
|
||||
@@ -4329,6 +4340,26 @@ msgstr "为新文件夹输入名称。文件夹可以帮助您整理项目。"
|
||||
msgid "Enter claim name"
|
||||
msgstr "输入声明名称"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-email-dialog.tsx
|
||||
msgid "Enter Email"
|
||||
msgstr "输入邮箱"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-initials-dialog.tsx
|
||||
msgid "Enter Initials"
|
||||
msgstr "输入姓名首字母"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-name-dialog.tsx
|
||||
msgid "Enter Name"
|
||||
msgstr "输入姓名"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-number-dialog.tsx
|
||||
msgid "Enter Number"
|
||||
msgstr "输入数字"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-text-dialog.tsx
|
||||
msgid "Enter Text"
|
||||
msgstr "输入文本"
|
||||
|
||||
#: apps/remix/app/components/dialogs/organisation-email-domain-create-dialog.tsx
|
||||
msgid "Enter the domain you want to use for sending emails (without http:// or www)"
|
||||
msgstr "输入您希望用于发送邮件的域名(不含 http:// 或 www)"
|
||||
@@ -4523,6 +4554,7 @@ msgid "Event Type"
|
||||
msgstr "事件类型"
|
||||
|
||||
#: apps/remix/app/components/dialogs/folder-update-dialog.tsx
|
||||
#: packages/lib/constants/document-visibility.ts
|
||||
#: packages/ui/components/document/document-visibility-select.tsx
|
||||
msgid "Everyone"
|
||||
msgstr "所有人"
|
||||
@@ -5222,14 +5254,6 @@ msgstr "必须填写姓名首字母缩写"
|
||||
msgid "Initials Settings"
|
||||
msgstr "姓名首字母设置"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-number-dialog.tsx
|
||||
msgid "Insert a value into the number field"
|
||||
msgstr "在数字字段中输入一个值"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-text-dialog.tsx
|
||||
msgid "Insert a value into the text field"
|
||||
msgstr "在文本字段中输入一个值"
|
||||
|
||||
#: apps/remix/app/components/tables/admin-document-recipient-item-table.tsx
|
||||
msgid "Inserted"
|
||||
msgstr "已插入"
|
||||
@@ -5738,6 +5762,7 @@ msgid "Manager"
|
||||
msgstr "管理者"
|
||||
|
||||
#: apps/remix/app/components/dialogs/folder-update-dialog.tsx
|
||||
#: packages/lib/constants/document-visibility.ts
|
||||
msgid "Managers and above"
|
||||
msgstr "管理者及以上"
|
||||
|
||||
@@ -6133,6 +6158,10 @@ msgstr "别担心,这很常见!输入你的邮箱,我们会给你发送一
|
||||
msgid "None"
|
||||
msgstr "无"
|
||||
|
||||
#: packages/lib/constants/document-auth.ts
|
||||
msgid "None (Overrides global settings)"
|
||||
msgstr "无(覆盖全局设置)"
|
||||
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/documents.$id._index.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/documents.$id.edit.tsx
|
||||
#: apps/remix/app/routes/_authenticated+/t.$teamUrl+/templates.$id._index.tsx
|
||||
@@ -6696,6 +6725,10 @@ msgstr "请联系站点所有者以获取进一步帮助。"
|
||||
msgid "Please enter a meaningful name for your token. This will help you identify it later."
|
||||
msgstr "请输入一个有意义的令牌名称,以便日后识别。"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-number-dialog.tsx
|
||||
msgid "Please enter a number"
|
||||
msgstr "请输入一个数字"
|
||||
|
||||
#: apps/remix/app/components/forms/profile.tsx
|
||||
#: apps/remix/app/components/forms/signup.tsx
|
||||
#: apps/remix/app/components/general/claim-account.tsx
|
||||
@@ -6706,6 +6739,22 @@ msgstr "请输入有效姓名。"
|
||||
msgid "Please enter a valid number"
|
||||
msgstr "请输入有效的数字"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-text-dialog.tsx
|
||||
msgid "Please enter a value"
|
||||
msgstr "请输入一个值"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-email-dialog.tsx
|
||||
msgid "Please enter your email address"
|
||||
msgstr "请输入您的邮箱地址"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-name-dialog.tsx
|
||||
msgid "Please enter your full name"
|
||||
msgstr "请输入您的全名"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-initials-dialog.tsx
|
||||
msgid "Please enter your initials"
|
||||
msgstr "请输入您的姓名首字母缩写"
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-mobile-widget.tsx
|
||||
msgid "Please mark as viewed to complete"
|
||||
msgstr "请标记为已查看以完成"
|
||||
@@ -7250,6 +7299,22 @@ msgstr "请求"
|
||||
msgid "Requesting Organisation"
|
||||
msgstr "请求的组织"
|
||||
|
||||
#: packages/lib/constants/document-auth.ts
|
||||
msgid "Require 2FA"
|
||||
msgstr "需要双重身份验证"
|
||||
|
||||
#: packages/lib/constants/document-auth.ts
|
||||
msgid "Require account"
|
||||
msgstr "需要账户"
|
||||
|
||||
#: packages/lib/constants/document-auth.ts
|
||||
msgid "Require passkey"
|
||||
msgstr "需要通行密钥"
|
||||
|
||||
#: packages/lib/constants/document-auth.ts
|
||||
msgid "Require password"
|
||||
msgstr "需要密码"
|
||||
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/checkbox-field.tsx
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/dropdown-field.tsx
|
||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx
|
||||
@@ -7662,6 +7727,10 @@ msgstr "选择要添加到此团队的成员"
|
||||
msgid "Select Option"
|
||||
msgstr "选择选项"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-checkbox-dialog.tsx
|
||||
msgid "Select Options"
|
||||
msgstr "选择选项"
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auth-passkey.tsx
|
||||
msgid "Select passkey"
|
||||
msgstr "选择通行密钥"
|
||||
@@ -7884,13 +7953,7 @@ msgstr "显示高级设置"
|
||||
msgid "Show templates in your public profile for your audience to sign and get started quickly"
|
||||
msgstr "在公开资料中展示模板,方便你的用户快速签署上手"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-checkbox-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-email-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-initials-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-name-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-number-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-signature-dialog.tsx
|
||||
#: apps/remix/app/components/dialogs/sign-field-text-dialog.tsx
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auth-2fa.tsx
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auth-password.tsx
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auto-sign.tsx
|
||||
@@ -7924,10 +7987,6 @@ msgstr "以 {0} <0>({1})</0> 的身份签署"
|
||||
msgid "Sign as<0>{0} <1>({1})</1></0>"
|
||||
msgstr "以<0>{0} <1>({1})</1></0>的身份签署"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-checkbox-dialog.tsx
|
||||
msgid "Sign Checkbox Field"
|
||||
msgstr "签署复选框字段"
|
||||
|
||||
#: apps/remix/app/components/embed/embed-direct-template-client-page.tsx
|
||||
#: apps/remix/app/components/embed/embed-document-signing-page-v1.tsx
|
||||
#: apps/remix/app/components/embed/multisign/multi-sign-document-signing-view.tsx
|
||||
@@ -7944,10 +8003,6 @@ msgstr "签署文档"
|
||||
msgid "Sign Documents"
|
||||
msgstr "签署文档"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-email-dialog.tsx
|
||||
msgid "Sign Email"
|
||||
msgstr "签署电子邮件字段"
|
||||
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auth-dialog.tsx
|
||||
#: apps/remix/app/components/general/document-signing/document-signing-auth-dialog.tsx
|
||||
msgid "Sign field"
|
||||
@@ -7970,18 +8025,6 @@ msgstr "登录"
|
||||
msgid "Sign in to your account"
|
||||
msgstr "登录到你的账号"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-initials-dialog.tsx
|
||||
msgid "Sign Initials"
|
||||
msgstr "签署姓名首字母字段"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-name-dialog.tsx
|
||||
msgid "Sign Name"
|
||||
msgstr "签署姓名字段"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-number-dialog.tsx
|
||||
msgid "Sign Number Field"
|
||||
msgstr "签署数字字段"
|
||||
|
||||
#: apps/remix/app/components/general/app-nav-mobile.tsx
|
||||
#: apps/remix/app/components/general/menu-switcher.tsx
|
||||
#: apps/remix/app/components/general/org-menu-switcher.tsx
|
||||
@@ -7992,10 +8035,6 @@ msgstr "登出"
|
||||
msgid "Sign Signature Field"
|
||||
msgstr "签署签名字段"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-text-dialog.tsx
|
||||
msgid "Sign Text Field"
|
||||
msgstr "签署文本字段"
|
||||
|
||||
#: apps/remix/app/components/embed/embed-direct-template-client-page.tsx
|
||||
#: apps/remix/app/components/embed/embed-document-signing-page-v1.tsx
|
||||
#: apps/remix/app/components/embed/multisign/multi-sign-document-signing-view.tsx
|
||||
@@ -8018,18 +8057,6 @@ msgstr "使用 Microsoft 注册"
|
||||
msgid "Sign Up with OIDC"
|
||||
msgstr "使用 OIDC 注册"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-email-dialog.tsx
|
||||
msgid "Sign your email into the field"
|
||||
msgstr "在字段中填写您的电子邮件地址"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-name-dialog.tsx
|
||||
msgid "Sign your full name into the field"
|
||||
msgstr "在字段中填写您的全名"
|
||||
|
||||
#: apps/remix/app/components/dialogs/sign-field-initials-dialog.tsx
|
||||
msgid "Sign your initials into the field"
|
||||
msgstr "在字段中填写您的姓名首字母缩写"
|
||||
|
||||
#: apps/remix/app/components/dialogs/ai-field-detection-dialog.tsx
|
||||
#: apps/remix/app/components/embed/embed-direct-template-client-page.tsx
|
||||
#: apps/remix/app/components/embed/embed-document-signing-page-v1.tsx
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { RecipientRole } from '@prisma/client';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZDefaultRecipientSchema = z.object({
|
||||
email: z.string().email(),
|
||||
name: z.string(),
|
||||
role: z.nativeEnum(RecipientRole),
|
||||
});
|
||||
|
||||
export type TDefaultRecipient = z.infer<typeof ZDefaultRecipientSchema>;
|
||||
|
||||
export const ZDefaultRecipientsSchema = z.array(ZDefaultRecipientSchema);
|
||||
|
||||
export type TDefaultRecipients = z.infer<typeof ZDefaultRecipientsSchema>;
|
||||
@@ -47,10 +47,13 @@ export const putPdfFileServerSide = async (file: File) => {
|
||||
/**
|
||||
* Uploads a pdf file and normalizes it.
|
||||
*/
|
||||
export const putNormalizedPdfFileServerSide = async (file: File) => {
|
||||
export const putNormalizedPdfFileServerSide = async (
|
||||
file: File,
|
||||
options: { flattenForm?: boolean } = {},
|
||||
) => {
|
||||
const buffer = Buffer.from(await file.arrayBuffer());
|
||||
|
||||
const normalized = await normalizePdf(buffer);
|
||||
const normalized = await normalizePdf(buffer, options);
|
||||
|
||||
const fileName = file.name.endsWith('.pdf') ? file.name : `${file.name}.pdf`;
|
||||
|
||||
|
||||
@@ -137,6 +137,7 @@ export const generateDefaultOrganisationSettings = (): Omit<
|
||||
// emailReplyToName: null,
|
||||
emailDocumentSettings: DEFAULT_DOCUMENT_EMAIL_SETTINGS,
|
||||
|
||||
defaultRecipients: null,
|
||||
aiFeaturesEnabled: false,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -7,6 +7,6 @@ export const extractInitials = (text: string) =>
|
||||
.slice(0, 2)
|
||||
.join('');
|
||||
|
||||
export const recipientAbbreviation = (recipient: Recipient) => {
|
||||
export const recipientAbbreviation = (recipient: Pick<Recipient, 'name' | 'email'>) => {
|
||||
return extractInitials(recipient.name) || recipient.email.slice(0, 1).toUpperCase();
|
||||
};
|
||||
|
||||
@@ -204,6 +204,7 @@ export const generateDefaultTeamSettings = (): Omit<TeamGlobalSettings, 'id' | '
|
||||
emailReplyTo: null,
|
||||
// emailReplyToName: null,
|
||||
|
||||
defaultRecipients: null,
|
||||
aiFeaturesEnabled: null,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "OrganisationGlobalSettings" ADD COLUMN "defaultRecipients" JSONB;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "TeamGlobalSettings" ADD COLUMN "defaultRecipients" JSONB;
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
-- CreateIndex
|
||||
CREATE INDEX IF NOT EXISTS "DocumentAuditLog_envelopeId_idx" ON "DocumentAuditLog"("envelopeId");
|
||||
@@ -472,6 +472,8 @@ model DocumentAuditLog {
|
||||
ipAddress String?
|
||||
|
||||
envelope Envelope @relation(fields: [envelopeId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([envelopeId])
|
||||
}
|
||||
|
||||
enum DocumentDataType {
|
||||
@@ -806,7 +808,7 @@ enum OrganisationMemberInviteStatus {
|
||||
DECLINED
|
||||
}
|
||||
|
||||
/// @zod.import(["import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';"])
|
||||
/// @zod.import(["import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';", "import { ZDefaultRecipientsSchema } from '@documenso/lib/types/default-recipients';"])
|
||||
model OrganisationGlobalSettings {
|
||||
id String @id
|
||||
organisation Organisation?
|
||||
@@ -824,6 +826,8 @@ model OrganisationGlobalSettings {
|
||||
uploadSignatureEnabled Boolean @default(true)
|
||||
drawSignatureEnabled Boolean @default(true)
|
||||
|
||||
defaultRecipients Json? /// [DefaultRecipient[]] @zod.custom.use(ZDefaultRecipientsSchema)
|
||||
|
||||
emailId String?
|
||||
email OrganisationEmail? @relation(fields: [emailId], references: [id])
|
||||
|
||||
@@ -840,7 +844,7 @@ model OrganisationGlobalSettings {
|
||||
aiFeaturesEnabled Boolean @default(false)
|
||||
}
|
||||
|
||||
/// @zod.import(["import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';"])
|
||||
/// @zod.import(["import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';", "import { ZDefaultRecipientsSchema } from '@documenso/lib/types/default-recipients';"])
|
||||
model TeamGlobalSettings {
|
||||
id String @id
|
||||
team Team?
|
||||
@@ -859,6 +863,8 @@ model TeamGlobalSettings {
|
||||
uploadSignatureEnabled Boolean?
|
||||
drawSignatureEnabled Boolean?
|
||||
|
||||
defaultRecipients Json? /// [DefaultRecipient[]] @zod.custom.use(ZDefaultRecipientsSchema)
|
||||
|
||||
emailId String?
|
||||
email OrganisationEmail? @relation(fields: [emailId], references: [id])
|
||||
|
||||
|
||||
Vendored
+3
@@ -1,4 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/no-namespace */
|
||||
import type { TDefaultRecipient } from '@documenso/lib/types/default-recipients';
|
||||
import type {
|
||||
TDocumentAuthOptions,
|
||||
TRecipientAuthOptions,
|
||||
@@ -26,6 +27,8 @@ declare global {
|
||||
type FieldMeta = TFieldMetaNotOptionalSchema;
|
||||
|
||||
type EnvelopeAttachmentType = TEnvelopeAttachmentType;
|
||||
|
||||
type DefaultRecipient = TDefaultRecipient;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"dependencies": {
|
||||
"@documenso/lib": "*",
|
||||
"@documenso/prisma": "*",
|
||||
"@simplewebauthn/server": "^13.2.2",
|
||||
"@tanstack/react-query": "5.90.10",
|
||||
"@trpc/client": "11.8.1",
|
||||
"@trpc/react-query": "11.8.1",
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import type { FindResultResponse } from '@documenso/lib/types/search-params';
|
||||
import { parseDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
|
||||
import { unsafeBuildEnvelopeIdQuery } from '@documenso/lib/utils/envelope';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { adminProcedure } from '../trpc';
|
||||
import {
|
||||
ZFindDocumentAuditLogsRequestSchema,
|
||||
ZFindDocumentAuditLogsResponseSchema,
|
||||
} from './find-document-audit-logs.types';
|
||||
|
||||
export const findDocumentAuditLogsRoute = adminProcedure
|
||||
.input(ZFindDocumentAuditLogsRequestSchema)
|
||||
.output(ZFindDocumentAuditLogsResponseSchema)
|
||||
.query(async ({ input }) => {
|
||||
const {
|
||||
envelopeId,
|
||||
page = 1,
|
||||
perPage = 50,
|
||||
orderByColumn = 'createdAt',
|
||||
orderByDirection = 'desc',
|
||||
} = input;
|
||||
|
||||
const envelope = await prisma.envelope.findFirst({
|
||||
where: unsafeBuildEnvelopeIdQuery(
|
||||
{
|
||||
type: 'envelopeId',
|
||||
id: envelopeId,
|
||||
},
|
||||
EnvelopeType.DOCUMENT,
|
||||
),
|
||||
});
|
||||
|
||||
if (!envelope) {
|
||||
throw new AppError(AppErrorCode.NOT_FOUND, {
|
||||
message: 'Envelope not found',
|
||||
});
|
||||
}
|
||||
|
||||
const [data, count] = await Promise.all([
|
||||
prisma.documentAuditLog.findMany({
|
||||
where: { envelopeId: envelope.id },
|
||||
skip: Math.max(page - 1, 0) * perPage,
|
||||
take: perPage,
|
||||
orderBy: {
|
||||
[orderByColumn]: orderByDirection,
|
||||
},
|
||||
}),
|
||||
prisma.documentAuditLog.count({
|
||||
where: { envelopeId: envelope.id },
|
||||
}),
|
||||
]);
|
||||
|
||||
const parsedData = data.map((auditLog) => parseDocumentAuditLogData(auditLog));
|
||||
|
||||
return {
|
||||
data: parsedData,
|
||||
count,
|
||||
currentPage: Math.max(page, 1),
|
||||
perPage,
|
||||
totalPages: Math.ceil(count / perPage),
|
||||
} satisfies FindResultResponse<typeof parsedData>;
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ZDocumentAuditLogSchema } from '@documenso/lib/types/document-audit-logs';
|
||||
import { ZFindResultResponse, ZFindSearchParamsSchema } from '@documenso/lib/types/search-params';
|
||||
|
||||
export const ZFindDocumentAuditLogsRequestSchema = ZFindSearchParamsSchema.extend({
|
||||
envelopeId: z.string(),
|
||||
orderByColumn: z.enum(['createdAt']).optional(),
|
||||
orderByDirection: z.enum(['asc', 'desc']).optional(),
|
||||
});
|
||||
|
||||
export const ZFindDocumentAuditLogsResponseSchema = ZFindResultResponse.extend({
|
||||
data: ZDocumentAuditLogSchema.array(),
|
||||
});
|
||||
|
||||
export type TFindDocumentAuditLogsRequest = z.infer<typeof ZFindDocumentAuditLogsRequestSchema>;
|
||||
export type TFindDocumentAuditLogsResponse = z.infer<typeof ZFindDocumentAuditLogsResponseSchema>;
|
||||
@@ -8,6 +8,7 @@ import { deleteUserRoute } from './delete-user';
|
||||
import { disableUserRoute } from './disable-user';
|
||||
import { enableUserRoute } from './enable-user';
|
||||
import { findAdminOrganisationsRoute } from './find-admin-organisations';
|
||||
import { findDocumentAuditLogsRoute } from './find-document-audit-logs';
|
||||
import { findDocumentJobsRoute } from './find-document-jobs';
|
||||
import { findDocumentsRoute } from './find-documents';
|
||||
import { findSubscriptionClaimsRoute } from './find-subscription-claims';
|
||||
@@ -56,6 +57,7 @@ export const adminRouter = router({
|
||||
delete: deleteDocumentRoute,
|
||||
reseal: resealDocumentRoute,
|
||||
findJobs: findDocumentJobsRoute,
|
||||
findAuditLogs: findDocumentAuditLogsRoute,
|
||||
},
|
||||
recipient: {
|
||||
update: updateRecipientRoute,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { RegistrationResponseJSON } from '@simplewebauthn/types';
|
||||
import type { RegistrationResponseJSON } from '@simplewebauthn/server';
|
||||
|
||||
import { createPasskey } from '@documenso/lib/server-only/auth/create-passkey';
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { EnvelopeType } from '@prisma/client';
|
||||
|
||||
import { getServerLimits } from '@documenso/ee/server-only/limits/server';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { createEnvelope } from '@documenso/lib/server-only/envelope/create-envelope';
|
||||
@@ -80,11 +82,16 @@ export const createEnvelopeRoute = authenticatedProcedure
|
||||
});
|
||||
}
|
||||
|
||||
const { id: documentDataId } = await putNormalizedPdfFileServerSide({
|
||||
name: file.name,
|
||||
type: 'application/pdf',
|
||||
arrayBuffer: async () => Promise.resolve(pdf),
|
||||
});
|
||||
const { id: documentDataId } = await putNormalizedPdfFileServerSide(
|
||||
{
|
||||
name: file.name,
|
||||
type: 'application/pdf',
|
||||
arrayBuffer: async () => Promise.resolve(pdf),
|
||||
},
|
||||
{
|
||||
flattenForm: type !== EnvelopeType.TEMPLATE,
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
title: file.name,
|
||||
|
||||
@@ -34,6 +34,7 @@ export const useEnvelopeRoute = authenticatedProcedure
|
||||
prefillFields,
|
||||
override,
|
||||
attachments,
|
||||
formValues,
|
||||
} = payload;
|
||||
|
||||
ctx.logger.info({
|
||||
@@ -79,7 +80,10 @@ export const useEnvelopeRoute = authenticatedProcedure
|
||||
// Process uploaded files and create document data for them
|
||||
const uploadedFiles = await Promise.all(
|
||||
filesToUpload.map(async (file) => {
|
||||
const { id: documentDataId } = await putNormalizedPdfFileServerSide(file);
|
||||
// We disable flattening here since `createDocumentFromTemplate` will handle it.
|
||||
const { id: documentDataId } = await putNormalizedPdfFileServerSide(file, {
|
||||
flattenForm: false,
|
||||
});
|
||||
|
||||
return {
|
||||
name: file.name,
|
||||
@@ -146,6 +150,7 @@ export const useEnvelopeRoute = authenticatedProcedure
|
||||
prefillFields,
|
||||
override,
|
||||
attachments,
|
||||
formValues,
|
||||
});
|
||||
|
||||
// Distribute document if requested
|
||||
|
||||
@@ -2,6 +2,7 @@ import { z } from 'zod';
|
||||
import { zfd } from 'zod-form-data';
|
||||
|
||||
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
|
||||
import { ZDocumentFormValuesSchema } from '@documenso/lib/types/document-form-values';
|
||||
import {
|
||||
ZDocumentMetaDateFormatSchema,
|
||||
ZDocumentMetaDistributionMethodSchema,
|
||||
@@ -108,6 +109,8 @@ export const ZUseEnvelopePayloadSchema = z.object({
|
||||
}),
|
||||
)
|
||||
.optional(),
|
||||
|
||||
formValues: ZDocumentFormValuesSchema.optional(),
|
||||
});
|
||||
|
||||
export const ZUseEnvelopeRequestSchema = zodFormData({
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { OrganisationType } from '@prisma/client';
|
||||
import { OrganisationType, Prisma } from '@prisma/client';
|
||||
|
||||
import { ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP } from '@documenso/lib/constants/organisations';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
@@ -36,6 +36,7 @@ export const updateOrganisationSettingsRoute = authenticatedProcedure
|
||||
typedSignatureEnabled,
|
||||
uploadSignatureEnabled,
|
||||
drawSignatureEnabled,
|
||||
defaultRecipients,
|
||||
delegateDocumentOwnership,
|
||||
|
||||
// Branding related settings.
|
||||
@@ -145,6 +146,7 @@ export const updateOrganisationSettingsRoute = authenticatedProcedure
|
||||
typedSignatureEnabled,
|
||||
uploadSignatureEnabled,
|
||||
drawSignatureEnabled,
|
||||
defaultRecipients: defaultRecipients === null ? Prisma.DbNull : defaultRecipients,
|
||||
delegateDocumentOwnership: derivedDelegateDocumentOwnership,
|
||||
|
||||
// Branding related settings.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { SUPPORTED_LANGUAGE_CODES } from '@documenso/lib/constants/i18n';
|
||||
import { ZDefaultRecipientsSchema } from '@documenso/lib/types/default-recipients';
|
||||
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
|
||||
import {
|
||||
ZDocumentMetaDateFormatSchema,
|
||||
@@ -22,6 +23,7 @@ export const ZUpdateOrganisationSettingsRequestSchema = z.object({
|
||||
typedSignatureEnabled: z.boolean().optional(),
|
||||
uploadSignatureEnabled: z.boolean().optional(),
|
||||
drawSignatureEnabled: z.boolean().optional(),
|
||||
defaultRecipients: ZDefaultRecipientsSchema.nullish(),
|
||||
delegateDocumentOwnership: z.boolean().nullish(),
|
||||
|
||||
// Branding related settings.
|
||||
|
||||
@@ -53,6 +53,8 @@ export const updateTeamSettingsRoute = authenticatedProcedure
|
||||
// emailReplyToName,
|
||||
emailDocumentSettings,
|
||||
|
||||
// Default recipients settings.
|
||||
defaultRecipients,
|
||||
// AI features settings.
|
||||
aiFeaturesEnabled,
|
||||
} = data;
|
||||
@@ -165,6 +167,7 @@ export const updateTeamSettingsRoute = authenticatedProcedure
|
||||
// emailReplyToName,
|
||||
emailDocumentSettings:
|
||||
emailDocumentSettings === null ? Prisma.DbNull : emailDocumentSettings,
|
||||
defaultRecipients: defaultRecipients === null ? Prisma.DbNull : defaultRecipients,
|
||||
|
||||
// AI features settings.
|
||||
aiFeaturesEnabled,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { SUPPORTED_LANGUAGE_CODES } from '@documenso/lib/constants/i18n';
|
||||
import { ZDefaultRecipientsSchema } from '@documenso/lib/types/default-recipients';
|
||||
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
|
||||
import {
|
||||
ZDocumentMetaDateFormatSchema,
|
||||
@@ -40,6 +41,8 @@ export const ZUpdateTeamSettingsRequestSchema = z.object({
|
||||
// emailReplyToName: z.string().nullish(),
|
||||
emailDocumentSettings: ZDocumentEmailSettingsSchema.nullish(),
|
||||
|
||||
// Default recipients settings.
|
||||
defaultRecipients: ZDefaultRecipientsSchema.nullish(),
|
||||
// AI features settings.
|
||||
aiFeaturesEnabled: z.boolean().nullish(),
|
||||
}),
|
||||
|
||||
@@ -197,7 +197,9 @@ export const templateRouter = router({
|
||||
attachments,
|
||||
} = payload;
|
||||
|
||||
const { id: templateDocumentDataId } = await putNormalizedPdfFileServerSide(file);
|
||||
const { id: templateDocumentDataId } = await putNormalizedPdfFileServerSide(file, {
|
||||
flattenForm: false,
|
||||
});
|
||||
|
||||
ctx.logger.info({
|
||||
input: {
|
||||
@@ -468,6 +470,7 @@ export const templateRouter = router({
|
||||
externalId,
|
||||
override,
|
||||
attachments,
|
||||
formValues,
|
||||
} = input;
|
||||
|
||||
ctx.logger.info({
|
||||
@@ -507,6 +510,7 @@ export const templateRouter = router({
|
||||
externalId,
|
||||
override,
|
||||
attachments,
|
||||
formValues,
|
||||
});
|
||||
|
||||
if (distributeDocument) {
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
ZDocumentActionAuthTypesSchema,
|
||||
} from '@documenso/lib/types/document-auth';
|
||||
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
|
||||
import { ZDocumentFormValuesSchema } from '@documenso/lib/types/document-form-values';
|
||||
import {
|
||||
ZDocumentMetaDateFormatSchema,
|
||||
ZDocumentMetaDistributionMethodSchema,
|
||||
@@ -172,6 +173,8 @@ export const ZCreateDocumentFromTemplateRequestSchema = z.object({
|
||||
}),
|
||||
)
|
||||
.optional(),
|
||||
|
||||
formValues: ZDocumentFormValuesSchema.optional(),
|
||||
});
|
||||
|
||||
export const ZCreateDocumentFromTemplateResponseSchema = ZDocumentSchema;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Prisma, WebhookCallStatus, WebhookTriggerEvents } from '@prisma/client';
|
||||
import type { Prisma, WebhookCallStatus, WebhookTriggerEvents } from '@prisma/client';
|
||||
|
||||
import { TEAM_MEMBER_ROLE_PERMISSIONS_MAP } from '@documenso/lib/constants/teams';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user