Compare commits

..
1 Commits
Author SHA1 Message Date
Charles Bochet e3e4a8a3cc Bump versions 2025-04-07 16:41:04 +02:00
3327 changed files with 43233 additions and 111685 deletions
-33
View File
@@ -1,33 +0,0 @@
---
description: Guidelines and best practices for working with Nx in the Twenty workspace, including workspace architecture understanding, configuration management, and generator usage.
globs:
alwaysApply: false
---
// This file is automatically generated by Nx Console
You are in an nx workspace using Nx 18.3.3 and yarn as the package manager.
You have access to the Nx MCP server and the tools it provides. Use them. Follow these guidelines in order to best help the user:
# General Guidelines
- When answering questions, use the nx_workspace tool first to gain an understanding of the workspace architecture
- For questions around nx configuration, best practices or if you're unsure, use the nx_docs tool to get relevant, up-to-date docs!! Always use this instead of assuming things about nx configuration
- If the user needs help with an Nx configuration or project graph error, use the 'nx_workspace' tool to get any errors
- To help answer questions about the workspace structure or simply help with demonstrating how tasks depend on each other, use the 'nx_visualize_graph' tool
# Generation Guidelines
If the user wants to generate something, use the following flow:
- learn about the nx workspace and any specifics the user needs by using the 'nx_workspace' tool and the 'nx_project_details' tool if applicable
- get the available generators using the 'nx_generators' tool
- decide which generator to use. If no generators seem relevant, check the 'nx_available_plugins' tool to see if the user could install a plugin to help them
- get generator details using the 'nx_generator_schema' tool
- you may use the 'nx_docs' tool to learn more about a specific generator or technology if you're unsure
- decide which options to provide in order to best complete the user's request. Don't make any assumptions and keep the options minimalistic
- open the generator UI using the 'nx_open_generate_ui' tool
- wait for the user to finish the generator
- read the generator log file using the 'nx_read_generator_log' tool
- use the information provided in the log file to answer the user's question or continue with what they were doing
@@ -84,7 +84,7 @@ Twenty uses a combination of Recoil for global state and Apollo Client for serve
`;
export const GET_USER = gql`
query GetUser($id: UUID!) {
query GetUser($id: ID!) {
user(id: $id) {
...UserFields
}
+1 -113
View File
@@ -6,71 +6,8 @@ module.exports = {
'plugin:react/recommended',
'plugin:react-hooks/recommended',
'plugin:storybook/recommended',
'plugin:prettier/recommended',
'plugin:lingui/recommended',
'plugin:@nx/typescript'
],
plugins: ['react-hooks', 'react-refresh', '@nx', 'prefer-arrow', 'import', 'unused-imports', 'unicorn', 'lingui'],
rules: {
'lingui/no-single-variables-to-translate': 'off',
'func-style': ['error', 'declaration', { allowArrowFunctions: true }],
'no-console': ['warn', { allow: ['group', 'groupCollapsed', 'groupEnd'] }],
'no-control-regex': 0,
'no-debugger': 'error',
'no-duplicate-imports': 'error',
'no-undef': 'off',
'no-unused-vars': 'off',
'@nx/enforce-module-boundaries': [
'error',
{
enforceBuildableLibDependency: true,
allow: [],
depConstraints: [
{
sourceTag: 'scope:shared',
onlyDependOnLibsWithTags: ['scope:shared'],
},
{
sourceTag: 'scope:backend',
onlyDependOnLibsWithTags: ['scope:shared', 'scope:backend'],
},
{
sourceTag: 'scope:frontend',
onlyDependOnLibsWithTags: ['scope:shared', 'scope:frontend'],
},
{
sourceTag: 'scope:zapier',
onlyDependOnLibsWithTags: ['scope:shared'],
},
],
},
],
'import/no-relative-packages': 'error',
'import/no-useless-path-segments': 'error',
'import/no-duplicates': ['error', { considerQueryString: true }],
'prefer-arrow/prefer-arrow-functions': [
'error',
{
disallowPrototype: true,
singleReturnOnly: false,
classPropertiesAllowed: false,
},
],
'unused-imports/no-unused-imports': 'warn',
'unused-imports/no-unused-vars': [
'warn',
{
vars: 'all',
varsIgnorePattern: '^_',
args: 'after-used',
argsIgnorePattern: '^_',
},
],
},
plugins: ['react-hooks', 'react-refresh'],
overrides: [
{
files: ['**/*.ts', '**/*.tsx'],
@@ -97,14 +34,6 @@ module.exports = {
],
},
],
'@typescript-eslint/no-empty-interface': [
'error',
{
allowSingleExtends: true,
},
],
'@typescript-eslint/no-empty-function': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@nx/workspace-effect-components': 'error',
'@nx/workspace-no-hardcoded-colors': 'error',
'@nx/workspace-matching-state-variable': 'error',
@@ -154,46 +83,5 @@ module.exports = {
],
},
},
{
files: ['*.js', '*.jsx'],
extends: ['plugin:@nx/javascript'],
rules: {},
},
{
files: [
'*.test.@(ts|tsx|js|jsx)',
],
env: {
jest: true,
},
rules: {
'@typescript-eslint/no-non-null-assertion': 'off',
},
},
{
files: ['**/*.constants.ts'],
rules: {
'@typescript-eslint/naming-convention': [
'error',
{
selector: 'variable',
format: ['UPPER_CASE'],
},
],
'unicorn/filename-case': [
'warn',
{
cases: {
pascalCase: true,
},
},
],
'@nx/workspace-max-consts-per-file': ['error', { max: 1 }],
},
},
{
files: ['*.json'],
parser: 'jsonc-eslint-parser',
},
],
};
+8 -4
View File
@@ -13,10 +13,14 @@ Good first issues are a great way to start contributing and get familiar with th
## Issue assignment
To avoid conflicts, we follow these guidelines:
Having multiple contributors address the same issue can cause frustration.
1. For `Good First Issue` and `Experienced Contributor` issues without `size: long` labels, we'll merge the first PRs that meet our [code quality standards](https://twenty.com/developers). **We don't assign contributors to these issues**. For `priority: high` issues, our core team will step in within days if no adequate contributions are received.
2. For `size: long` Issues, assigned contributors have one week to submit their first draft PR.
To avoid conflicts, we follow these guidelines:
1. If a core team member assigned you the issue within the last three days, your PR takes priority.
2. Otherwise, the first submitted PR is prioritized.
3. For "size: long" PRs, the assignment period extends to one week.
Please ensure you're assigned to an issue before starting work.
## How to Contribute
@@ -63,4 +67,4 @@ git push origin your-branch-name
## Reporting Issues
If you face any issues or have suggestions, please feel free to (create an issue on Twenty's GitHub repository)[https://github.com/twentyhq/twenty/issues/new]. Please provide as much detail as possible.
If you face any issues or have suggestions, please feel free to (create an issue on Twenty's GitHub repository)[https://github.com/twentyhq/twenty/issues/new]. Please provide as much detail as possible.
+1 -1
View File
@@ -39,7 +39,7 @@ jobs:
run: npx nx build twenty-chrome-extension
ci-chrome-extension-status-check:
if: always() && !cancelled()
timeout-minutes: 5
timeout-minutes: 1
runs-on: ubuntu-latest
needs: [changed-files-check, chrome-extension-build]
steps:
+1 -1
View File
@@ -124,7 +124,7 @@ jobs:
retention-days: 30
ci-e2e-status-check:
if: always() && !cancelled()
timeout-minutes: 5
timeout-minutes: 1
runs-on: ubuntu-latest
needs: [changed-files-check, test]
steps:
+1 -1
View File
@@ -53,7 +53,7 @@ jobs:
kill $SERVER_PID
ci-emails-status-check:
if: always() && !cancelled()
timeout-minutes: 5
timeout-minutes: 1
runs-on: depot-ubuntu-24.04-8
needs: [changed-files-check, emails-test]
steps:
+1 -1
View File
@@ -189,7 +189,7 @@ jobs:
key: ${{ steps.restore-task-cache.outputs.cache-primary-key }}
ci-front-status-check:
if: always() && !cancelled()
timeout-minutes: 5
timeout-minutes: 1
runs-on: depot-ubuntu-24.04-8
needs:
[
+2 -22
View File
@@ -168,25 +168,9 @@ jobs:
image: redis
ports:
- 6379:6379
clickhouse:
image: clickhouse/clickhouse-server:latest
env:
CLICKHOUSE_PASSWORD: clickhousePassword
CLICKHOUSE_URL: "http://default:clickhousePassword@localhost:8123/twenty"
ports:
- 8123:8123
- 9000:9000
options: >-
--health-cmd "clickhouse-client --host=localhost --port=9000 --user=default --password=clickhousePassword --query='SELECT 1'"
--health-interval 10s
--health-timeout 5s
--health-retries 5
env:
NX_REJECT_UNKNOWN_LOCAL_CACHE: 0
NODE_ENV: test
ANALYTICS_ENABLED: true
CLICKHOUSE_URL: "http://default:clickhousePassword@localhost:8123/twenty"
CLICKHOUSE_PASSWORD: clickhousePassword
steps:
- name: Fetch custom Github Actions and base branch history
uses: actions/checkout@v4
@@ -194,7 +178,7 @@ jobs:
fetch-depth: 0
- name: Install dependencies
uses: ./.github/workflows/actions/yarn-install
- name: Update .env.test for integrations tests
- name: Update .env.test for billing
run: |
echo "IS_BILLING_ENABLED=true" >> .env.test
echo "BILLING_STRIPE_API_KEY=test-api-key" >> .env.test
@@ -214,10 +198,6 @@ jobs:
- name: Server / Create Test DB
run: |
PGPASSWORD=postgres psql -h localhost -p 5432 -U postgres -d postgres -c 'CREATE DATABASE "test";'
- name: Run ClickHouse migrations
run: npx nx clickhouse:migrate twenty-server
- name: Run ClickHouse seeds
run: npx nx clickhouse:seed twenty-server
- name: Server / Run Integration Tests
uses: ./.github/workflows/actions/nx-affected
with:
@@ -232,7 +212,7 @@ jobs:
path: reset-logs.log
ci-server-status-check:
if: always() && !cancelled()
timeout-minutes: 5
timeout-minutes: 1
runs-on: depot-ubuntu-24.04-8
needs: [changed-files-check, server-setup, server-test, server-integration-test]
steps:
+1 -1
View File
@@ -44,7 +44,7 @@ jobs:
tasks: ${{ matrix.task }}
ci-shared-status-check:
if: always() && !cancelled()
timeout-minutes: 5
timeout-minutes: 1
runs-on: ubuntu-latest
needs: [changed-files-check, shared-test]
steps:
@@ -84,7 +84,7 @@ jobs:
working-directory: ./packages/twenty-docker/
ci-test-docker-compose-status-check:
if: always() && !cancelled()
timeout-minutes: 5
timeout-minutes: 1
runs-on: ubuntu-latest
needs: [changed-files-check, test]
steps:
+1 -1
View File
@@ -62,7 +62,7 @@ jobs:
NEXT_PUBLIC_KEYSTATIC_GITHUB_APP_SLUG: xxx
ci-website-status-check:
if: always() && !cancelled()
timeout-minutes: 5
timeout-minutes: 10
runs-on: ubuntu-latest
needs: [changed-files-check, website-build]
steps:
@@ -1,36 +0,0 @@
name: 'Preview Environment Dispatch'
on:
# Using pull_request_target instead of pull_request to have access to secrets for external contributors
# Security note: This is safe because we're only using the repository-dispatch action with limited scope
# and not checking out or running any code from the external contributor's PR
pull_request_target:
types: [opened, synchronize, reopened, labeled]
paths:
- packages/twenty-docker/**
- packages/twenty-server/**
- packages/twenty-front/**
- .github/workflows/preview-env-dispatch.yaml
- .github/workflows/preview-env-keepalive.yaml
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
trigger-preview:
permissions:
contents: write
actions: write
pull-requests: read
if: github.event.action == 'opened' || github.event.action == 'synchronize' || github.event.action == 'reopened' || (github.event.action == 'labeled' && github.event.label.name == 'preview-app')
timeout-minutes: 5
runs-on: ubuntu-latest
steps:
- name: Trigger preview environment workflow
uses: peter-evans/repository-dispatch@v2
with:
token: ${{ secrets.GITHUB_TOKEN }}
repository: ${{ github.repository }}
event-type: preview-environment
client-payload: '{"pr_number": "${{ github.event.pull_request.number }}", "pr_head_sha": "${{ github.event.pull_request.head.sha }}", "repo_full_name": "${{ github.repository }}"}'
@@ -1,150 +0,0 @@
name: 'Preview Environment Keep Alive'
on:
repository_dispatch:
types: [preview-environment]
jobs:
preview-environment:
timeout-minutes: 310
runs-on: ubuntu-latest
steps:
- name: Checkout PR
uses: actions/checkout@v4
with:
ref: ${{ github.event.client_payload.pr_head_sha }}
- name: Run compose setup
run: |
echo "Patching docker-compose.yml..."
# change image to localbuild using yq
yq eval 'del(.services.server.image)' -i packages/twenty-docker/docker-compose.yml
yq eval '.services.server.build.context = "../../"' -i packages/twenty-docker/docker-compose.yml
yq eval '.services.server.build.dockerfile = "./packages/twenty-docker/twenty/Dockerfile"' -i packages/twenty-docker/docker-compose.yml
yq eval 'del(.services.worker.image)' -i packages/twenty-docker/docker-compose.yml
yq eval '.services.worker.build.context = "../../"' -i packages/twenty-docker/docker-compose.yml
yq eval '.services.worker.build.dockerfile = "./packages/twenty-docker/twenty/Dockerfile"' -i packages/twenty-docker/docker-compose.yml
echo "Setting up .env file..."
cp packages/twenty-docker/.env.example packages/twenty-docker/.env
echo "Generating secrets..."
echo "# === Randomly generated secrets ===" >> packages/twenty-docker/.env
echo "APP_SECRET=$(openssl rand -base64 32)" >> packages/twenty-docker/.env
echo "PG_DATABASE_PASSWORD=$(openssl rand -hex 16)" >> packages/twenty-docker/.env
echo "SIGN_IN_PREFILLED=true" >> packages/twenty-docker/.env
echo "Docker compose build..."
cd packages/twenty-docker/
docker compose build
working-directory: ./
- name: Create Tunnel
id: expose-tunnel
uses: codetalkio/expose-tunnel@v1.5.0
with:
service: bore.pub
port: 3000
- name: Start services with correct SERVER_URL
run: |
cd packages/twenty-docker/
# Update the SERVER_URL with the tunnel URL
echo "Setting SERVER_URL to ${{ steps.expose-tunnel.outputs.tunnel-url }}"
sed -i '/SERVER_URL=/d' .env
echo "SERVER_URL=${{ steps.expose-tunnel.outputs.tunnel-url }}" >> .env
# Start the services
echo "Docker compose up..."
docker compose up -d || {
echo "Docker compose failed to start"
docker compose logs
exit 1
}
echo "Waiting for services to be ready..."
count=0
while [ ! $(docker inspect --format='{{.State.Health.Status}}' twenty-db-1) = "healthy" ] || [ ! $(docker inspect --format='{{.State.Health.Status}}' twenty-server-1) = "healthy" ]; do
sleep 5
count=$((count+1))
if [ $count -gt 60 ]; then
echo "Timeout waiting for services to be ready"
docker compose logs
exit 1
fi
echo "Still waiting for services... ($count/60)"
done
echo "All services are up and running!"
working-directory: ./
- name: Seed Dev Workspace
run: |
cd packages/twenty-docker/
echo "Seeding full dev workspace..."
if ! docker compose exec -T server yarn command:prod -- workspace:seed:dev; then
echo "❌ Seeding full dev workspace failed. Dumping server logs..."
docker compose logs server
exit 1
fi
working-directory: ./
- name: Output tunnel URL to logs
run: |
echo "✅ Preview Environment Ready!"
echo "🔗 Preview URL: ${{ steps.expose-tunnel.outputs.tunnel-url }}"
echo "⏱️ This environment will be available for 5 hours"
- name: Post comment on PR
uses: actions/github-script@v6
with:
github-token: ${{secrets.GITHUB_TOKEN}}
script: |
const COMMENT_MARKER = '<!-- PR_PREVIEW_ENV -->';
const commentBody = `${COMMENT_MARKER}
🚀 **Preview Environment Ready!**
Your preview environment is available at: ${{ steps.expose-tunnel.outputs.tunnel-url }}
This environment will automatically shut down when the PR is closed or after 5 hours.`;
// Get all comments
const {data: comments} = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: ${{ github.event.client_payload.pr_number }},
});
// Find our comment
const botComment = comments.find(comment => comment.body.includes(COMMENT_MARKER));
if (botComment) {
// Update existing comment
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: botComment.id,
body: commentBody
});
console.log('Updated existing comment');
} else {
// Create new comment
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: ${{ github.event.client_payload.pr_number }},
body: commentBody
});
console.log('Created new comment');
}
- name: Keep tunnel alive for 5 hours
run: timeout 300m sleep 18000 # Stop on whichever we reach first (300m or 5hour sleep)
- name: Cleanup
if: always()
run: |
cd packages/twenty-docker/
docker compose down -v
working-directory: ./
+5 -8
View File
@@ -1,12 +1,12 @@
postgres-on-docker:
docker run -d \
--name twenty_pg \
-e POSTGRES_USER=postgres \
-e POSTGRES_PASSWORD=postgres \
-e PGUSER_SUPERUSER=postgres \
-e PGPASSWORD_SUPERUSER=postgres \
-e ALLOW_NOSSL=true \
-v twenty_db_data:/var/lib/postgresql/data \
-v twenty_db_data:/home/postgres/pgdata \
-p 5432:5432 \
postgres:16
twentycrm/twenty-postgres-spilo:latest
@echo "Waiting for PostgreSQL to be ready..."
@until docker exec twenty_pg psql -U postgres -d postgres \
-c 'SELECT pg_is_in_recovery();' 2>/dev/null | grep -q 'f'; do \
@@ -17,7 +17,4 @@ postgres-on-docker:
-c "CREATE DATABASE \"test\" WITH OWNER postgres;"
redis-on-docker:
docker run -d --name twenty_redis -p 6379:6379 redis/redis-stack-server:latest
clickhouse-on-docker:
docker run -d --name twenty_clickhouse -p 8123:8123 -p 9000:9000 -e CLICKHOUSE_PASSWORD=clickhousePassword clickhouse/clickhouse-server:latest \
docker run -d --name twenty_redis -p 6379:6379 redis/redis-stack-server:latest
+34 -45
View File
@@ -1,28 +1,28 @@
<br />
<br>
<p align="center">
<a href="https://www.twenty.com">
<img src="./packages/twenty-website/public/images/core/logo.svg" width="100px" alt="Twenty logo" />
</a>
</p>
<h2 align="center" >The #1 Open-Source CRM </h2>
<h2 align="center" >The #1 Open-Source CRM </h3>
<p align="center"><a href="https://twenty.com">🌐 Website</a> · <a href="https://twenty.com/developers">📚 Documentation</a> · <a href="https://github.com/orgs/twentyhq/projects/1"><img src="./packages/twenty-website/public/images/readme/planner-icon.svg" width="12" height="12"/> Roadmap </a> · <a href="https://discord.gg/cx5n4Jzs57"><img src="./packages/twenty-website/public/images/readme/discord-icon.svg" width="12" height="12"/> Discord</a> · <a href="https://www.figma.com/file/xt8O9mFeLl46C5InWwoMrN/Twenty"><img src="./packages/twenty-website/public/images/readme/figma-icon.png" width="12" height="12"/> Figma</a></p>
<p align="center"><a href="https://twenty.com">🌐 Website</a> · <a href="https://twenty.com/developers">📚 Documentation</a> · <a href="https://github.com/orgs/twentyhq/projects/1"><img src="./packages/twenty-website/public/images/readme/planner-icon.svg" width="12" height="12"/> Roadmap </a> · <a href="https://discord.gg/cx5n4Jzs57"><img src="./packages/twenty-website/public/images/readme/discord-icon.svg" width="12" height="12"/> Discord</a> · <a href="https://www.figma.com/file/xt8O9mFeLl46C5InWwoMrN/Twenty"><img src="./packages/twenty-website/public/images/readme/figma-icon.png" width="12" height="12"/> Figma</a><p>
<br />
<p align="center">
<a href="https://www.twenty.com">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/twentyhq/twenty/v0.12.0/packages/twenty-docs/static/img/preview-dark.png" />
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/twentyhq/twenty/v0.12.0/packages/twenty-docs/static/img/preview-light.png" />
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/twentyhq/twenty/v0.12.0/packages/twenty-docs/static/img/preview-dark.png">
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/twentyhq/twenty/v0.12.0/packages/twenty-docs/static/img/preview-light.png">
<img src="./packages/twenty-docs/static/img/preview-light.png" alt="Companies view" />
</picture>
</a>
</p>
<br />
<br>
# Installation
@@ -40,7 +40,7 @@ We built Twenty for three reasons:
**We believe in Open-source and community.** Hundreds of developers are already building Twenty together. Once we have plugin capabilities, a whole ecosystem will grow around it.
<br />
<br>
# What You Can Do With Twenty
We're currently developing Twenty's beta version.
@@ -60,8 +60,8 @@ Below are a few features we have implemented to date:
<p align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/twentyhq/twenty/v0.12.0/packages/twenty-docs/static/img/index-dark.png" />
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/twentyhq/twenty/v0.12.0/packages/twenty-docs/static/img/index-light.png" />
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/twentyhq/twenty/v0.12.0/packages/twenty-docs/static/img/index-dark.png">
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/twentyhq/twenty/v0.12.0/packages/twenty-docs/static/img/index-light.png">
<img src="./packages/twenty-docs/static/img/visualise-customer-light.png" alt="Companies view" />
</picture>
</p>
@@ -70,9 +70,9 @@ Below are a few features we have implemented to date:
<p align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/twentyhq/twenty/v0.12.0/packages/twenty-docs/static/img/kanban-dark.png" />
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/twentyhq/twenty/v0.12.0/packages/twenty-docs/static/img/kanban-light.png" />
<img src="./packages/twenty-docs/static/img/follow-your-deals-light.png" alt="Opportunities view" />
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/twentyhq/twenty/v0.12.0/packages/twenty-docs/static/img/kanban-dark.png">
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/twentyhq/twenty/v0.12.0/packages/twenty-docs/static/img/kanban-light.png">
<img src="./packages/twenty-docs/static/img/follow-your-deals-light.png" alt="Companies view" />
</picture>
</p>
@@ -80,9 +80,9 @@ Below are a few features we have implemented to date:
<p align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/twentyhq/twenty/v0.12.0/packages/twenty-docs/static/img/emails-dark.png" />
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/twentyhq/twenty/v0.12.0/packages/twenty-docs/static/img/emails-light.png" />
<img src="./packages/twenty-docs/static/img/emails-light.png" alt="Emails" />
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/twentyhq/twenty/v0.12.0/packages/twenty-docs/static/img/emails-dark.png">
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/twentyhq/twenty/v0.12.0/packages/twenty-docs/static/img/emails-light.png">
<img src="./packages/twenty-docs/static/img/rich-notes-light.png" alt="Companies view" />
</picture>
</p>
@@ -90,9 +90,9 @@ Below are a few features we have implemented to date:
<p align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/twentyhq/twenty/v0.12.0/packages/twenty-docs/static/img/data-dark.png" />
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/twentyhq/twenty/v0.12.0/packages/twenty-docs/static/img/data-light.png" />
<img src="./packages/twenty-docs/static/img/data-light.png" alt="Data model" />
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/twentyhq/twenty/v0.12.0/packages/twenty-docs/static/img/data-dark.png">
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/twentyhq/twenty/v0.12.0/packages/twenty-docs/static/img/data-light.png">
<img src="./packages/twenty-docs/static/img/rich-notes-light.png" alt="Companies view" />
</picture>
</p>
@@ -100,9 +100,9 @@ Below are a few features we have implemented to date:
<p align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/twentyhq/twenty/v0.12.0/packages/twenty-docs/static/img/notes-dark.png" />
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/twentyhq/twenty/v0.12.0/packages/twenty-docs/static/img/notes-light.png" />
<img src="./packages/twenty-docs/static/img/notes-light.png" alt="Rich notes" />
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/twentyhq/twenty/v0.12.0/packages/twenty-docs/static/img/notes-dark.png">
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/twentyhq/twenty/v0.12.0/packages/twenty-docs/static/img/notes-light.png">
<img src="./packages/twenty-docs/static/img/rich-notes-light.png" alt="Companies view" />
</picture>
</p>
@@ -110,9 +110,9 @@ Below are a few features we have implemented to date:
<p align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/twentyhq/twenty/v0.12.0/packages/twenty-docs/static/img/tasks-dark.png" />
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/twentyhq/twenty/v0.12.0/packages/twenty-docs/static/img/tasks-light.png" />
<img src="./packages/twenty-docs/static/img/create-tasks-light.png" alt="Tasks" />
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/twentyhq/twenty/v0.12.0/packages/twenty-docs/static/img/tasks-dark.png">
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/twentyhq/twenty/v0.12.0/packages/twenty-docs/static/img/tasks-light.png">
<img src="./packages/twenty-docs/static/img/create-tasks-light.png" alt="Companies view" />
</picture>
</p>
@@ -120,9 +120,9 @@ Below are a few features we have implemented to date:
<p align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/twentyhq/twenty/v0.12.0/packages/twenty-docs/static/img/keyboard-dark.png" />
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/twentyhq/twenty/v0.12.0/packages/twenty-docs/static/img/keyboard-light.png" />
<img src="./packages/twenty-docs/static/img/keyboard-dark.png" alt="Keyboard shortcuts" />
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/twentyhq/twenty/v0.12.0/packages/twenty-docs/static/img/keyboard-dark.png">
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/twentyhq/twenty/v0.12.0/packages/twenty-docs/static/img/keyboard-light.png">
<img src="./packages/twenty-docs/static/img/shortcut-navigation-light.png" alt="Companies view" />
</picture>
</p>
@@ -130,33 +130,22 @@ Below are a few features we have implemented to date:
<p align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/twentyhq/twenty/v0.12.0/packages/twenty-docs/static/img/api-dark.png" />
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/twentyhq/twenty/v0.12.0/packages/twenty-docs/static/img/api-light.png" />
<img src="./packages/twenty-docs/static/img/api-light.png" alt="API" />
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/twentyhq/twenty/v0.12.0/packages/twenty-docs/static/img/api-dark.png">
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/twentyhq/twenty/v0.12.0/packages/twenty-docs/static/img/api-light.png">
<img src="./packages/twenty-docs/static/img/shortcut-navigation-light.png" alt="Companies view" />
</picture>
</p>
<br />
<br>
# Stack
- [TypeScript](https://www.typescriptlang.org/)
- [Nx](https://nx.dev/)
- [NestJS](https://nestjs.com/), with [BullMQ](https://bullmq.io/), [PostgreSQL](https://www.postgresql.org/), [Redis](https://redis.io/)
- [React](https://reactjs.org/), with [Recoil](https://recoiljs.org/), [Emotion](https://emotion.sh/) and [Lingui](https://lingui.dev/)
# Thanks
<p align="center">
<a href="https://www.chromatic.com/"><img src="./packages/twenty-website/public/images/readme/chromatic.png" height="30" alt="Chromatic" /></a>
<a href="https://greptile.com"><img src="./packages/twenty-website/public/images/readme/greptile.png" height="30" alt="Greptile" /></a>
<a href="https://sentry.io/"><img src="./packages/twenty-website/public/images/readme/sentry.png" height="30" alt="Sentry" /></a>
<a href="https://crowdin.com/"><img src="./packages/twenty-website/public/images/readme/crowdin.png" height="30" alt="Crowdin" /></a>
</p>
Thanks to these amazing services that we use and recommend for UI testing (Chromatic), code review (Greptile), catching bugs (Sentry) and translating (Crowdin).
- [React](https://reactjs.org/), with [Recoil](https://recoiljs.org/) and [Emotion](https://emotion.sh/)
- [Greptile](https://greptile.com) for code reviews.
- [Lingui](https://lingui.dev/) and [Crowdin](https://twenty.crowdin.com/twenty) for translations.
# Join the Community
View File
+4 -8
View File
@@ -1,6 +1,7 @@
{
"private": true,
"dependencies": {
"@air/react-drag-to-select": "^5.0.8",
"@apollo/client": "^3.7.17",
"@apollo/server": "^4.7.3",
"@aws-sdk/client-lambda": "^3.614.0",
@@ -76,7 +77,6 @@
"bytes": "^3.1.2",
"class-transformer": "^0.5.1",
"clsx": "^2.1.1",
"cron-parser": "^5.1.1",
"cron-validate": "^1.4.5",
"cross-env": "^7.0.3",
"css-loader": "^7.1.2",
@@ -93,16 +93,13 @@
"facepaint": "^1.2.1",
"file-type": "16.5.4",
"framer-motion": "^11.18.0",
"fuse.js": "^7.1.0",
"googleapis": "105",
"graphiql": "^3.1.1",
"graphql": "16.8.0",
"graphql-fields": "^2.0.3",
"graphql-middleware": "^6.1.35",
"graphql-rate-limit": "^3.3.0",
"graphql-redis-subscriptions": "^2.7.0",
"graphql-scalars": "^1.23.0",
"graphql-sse": "^2.5.4",
"graphql-subscriptions": "2.0.0",
"graphql-tag": "^2.12.6",
"graphql-type-json": "^0.3.2",
@@ -184,13 +181,13 @@
"semver": "^7.5.4",
"sharp": "^0.32.1",
"slash": "^5.1.0",
"storybook-addon-mock-date": "^0.6.0",
"stripe": "^17.3.1",
"ts-key-enum": "^2.0.12",
"tslib": "^2.3.0",
"tsup": "^8.2.4",
"type-fest": "4.10.1",
"typescript": "5.3.3",
"use-context-selector": "^2.0.0",
"use-debounce": "^10.0.0",
"uuid": "^9.0.0",
"vite-tsconfig-paths": "^4.2.1",
@@ -338,7 +335,7 @@
"ts-node": "10.9.1",
"tsconfig-paths": "^4.2.0",
"tsx": "^4.17.0",
"vite": "^6.3.5",
"vite": "^5.4.0",
"vite-plugin-checker": "^0.6.2",
"vite-plugin-dts": "3.8.1",
"vite-plugin-svgr": "^4.2.0",
@@ -357,8 +354,7 @@
"type-fest": "4.10.1",
"typescript": "5.3.3",
"prosemirror-model": "1.23.0",
"yjs": "13.6.18",
"graphql-redis-subscriptions/ioredis": "^5.6.0"
"yjs": "13.6.18"
},
"version": "0.2.1",
"nx": {},
@@ -1,7 +1,7 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="images/icons/android/android-launchericon-48-48.png" />
<link rel="icon" href="/icons/android/android-launchericon-48-48.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Twenty</title>
</head>
@@ -1,7 +1,7 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="images/icons/android/android-launchericon-48-48.png" />
<link rel="icon" href="/icons/android/android-launchericon-48-48.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Twenty</title>
</head>
@@ -1,7 +1,7 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="images/icons/android/android-launchericon-48-48.png" />
<link rel="icon" href="/icons/android/android-launchericon-48-48.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Twenty</title>
<style>
+20 -4
View File
@@ -1,10 +1,22 @@
name: twenty
services:
change-vol-ownership:
image: ubuntu
user: root
volumes:
- server-local-data:/tmp/server-local-data
- docker-data:/tmp/docker-data
command: >
bash -c "
chown -R 1000:1000 /tmp/server-local-data
&& chown -R 1000:1000 /tmp/docker-data"
server:
image: twentycrm/twenty:${TAG:-latest}
volumes:
- server-local-data:/app/packages/twenty-server/.local-storage
- server-local-data:/app/packages/twenty-server/${STORAGE_LOCAL_PATH:-.local-storage}
- docker-data:/app/docker-data
ports:
- "3000:3000"
environment:
@@ -19,6 +31,7 @@ services:
STORAGE_S3_ENDPOINT: ${STORAGE_S3_ENDPOINT}
APP_SECRET: ${APP_SECRET:-replace_me_with_a_random_string}
# MESSAGING_PROVIDER_GMAIL_ENABLED: ${MESSAGING_PROVIDER_GMAIL_ENABLED}
# CALENDAR_PROVIDER_GOOGLE_ENABLED: ${CALENDAR_PROVIDER_GOOGLE_ENABLED}
# AUTH_GOOGLE_CLIENT_ID: ${AUTH_GOOGLE_CLIENT_ID}
@@ -44,13 +57,15 @@ services:
# EMAIL_SMTP_PASSWORD: ${EMAIL_SMTP_PASSWORD:-}
depends_on:
change-vol-ownership:
condition: service_completed_successfully
db:
condition: service_healthy
healthcheck:
test: curl --fail http://localhost:3000/healthz
interval: 5s
timeout: 5s
retries: 20
retries: 10
restart: always
worker:
@@ -70,6 +85,7 @@ services:
STORAGE_S3_ENDPOINT: ${STORAGE_S3_ENDPOINT}
APP_SECRET: ${APP_SECRET:-replace_me_with_a_random_string}
# MESSAGING_PROVIDER_GMAIL_ENABLED: ${MESSAGING_PROVIDER_GMAIL_ENABLED}
# CALENDAR_PROVIDER_GOOGLE_ENABLED: ${CALENDAR_PROVIDER_GOOGLE_ENABLED}
# AUTH_GOOGLE_CLIENT_ID: ${AUTH_GOOGLE_CLIENT_ID}
@@ -93,7 +109,7 @@ services:
# EMAIL_SMTP_PORT: ${EMAIL_SMTP_PORT:-465}
# EMAIL_SMTP_USER: ${EMAIL_SMTP_USER:-}
# EMAIL_SMTP_PASSWORD: ${EMAIL_SMTP_PASSWORD:-}
depends_on:
db:
condition: service_healthy
@@ -118,8 +134,8 @@ services:
redis:
image: redis
restart: always
command: ["--maxmemory-policy", "noeviction"]
volumes:
docker-data:
db-data:
server-local-data:
-3
View File
@@ -1,7 +1,4 @@
# README
DISCLAIMER: The k8s and podman deployments are not maintained by the core team.
These files are provided and maintained by the community. Twenty core team
maintains support for docker deployment.
## Overview
@@ -25,10 +25,9 @@ spec:
- name: redis
image: redis/redis-stack-server:latest
imagePullPolicy: Always
args: ["--maxmemory-policy", "noeviction"]
env:
- name: PORT
value: "6379"
value: 6379
ports:
- containerPort: 6379
name: redis
-48
View File
@@ -1,48 +0,0 @@
# How to deploy twenty on podman
DISCLAIMER: The k8s and podman deployments are not maintained by the core team.
These files are provided and maintained by the community. Twenty core team
maintains support for docker deployment.
## How to use
1. Edit `.env` file. At the minimum set `POSTGRES_PASSWORD`, `SERVER_URL`, and `APP_SECRET`.
2. Start twenty by running `podman-compose up -d`.
If you need to stop twenty, you can do so by running `podman-compose down`.
### Install systemd service (optional)
If you want to install a systemd service to run twenty, you can use the provided systemd service.
Edit `twentycrm.service` and change these two variables:
WorkingDirectory=/opt/apps/twenty
EnvironmentFile=/opt/apps/twenty/.env
`WorkingDirectory` should be changed to the path in which `podman-compose.yml` is located.
`EnvironmentFile` should be changed to the path in which your `.env`file is located.
You can run the script `install-systemd-user-service` to install the systemd service under the current user.
./install-systemd-user-service
Note: this script will enable the service and also start it. So it will assume that twenty is not currently running.
If you started it previously, bring it down using:
podman-compose down
## Compatibility
These files should be compatible with podman 4.3+.
I have tested this on Debian GNU/Linux 12 (bookworm) and with the podman that is distributed with the official Debian stable mirrors (podman v4.3.1+ds1-8+deb12u1, podman-compose v1.0.3-3).
@@ -1,11 +0,0 @@
#!/bin/bash
set -e
mkdir -p ~/.config/systemd/user
[ -f twentycrm.service ] || { echo "Error: twentycrm.service not found"; exit 1; }
cp twentycrm.service ~/.config/systemd/user
systemctl --user daemon-reload
systemctl --user enable twentycrm.service
systemctl --user start twentycrm.service
@@ -1,71 +0,0 @@
#!/bin/bash
set -e
if [ ! -f "$(dirname "$0")/.env" ]; then
echo "Error: .env file not found"
exit 1
fi
source "$(dirname "$0")/.env"
## steps to cleanup and start over from scratch - useful when testing
#podman pod stop twenty-pod
#podman pod rm twenty-pod
#podman volume rm twenty-db-data twenty-server-data twenty-redis-data
# end of cleanup
podman volume create twenty-db-data
podman volume create twenty-server-data
podman volume create twenty-redis-data
podman pod create --name twenty-pod -p 127.0.0.1:8080:3000
podman run -d --pod twenty-pod --name twenty-db \
-e POSTGRES_DB=twenty \
-e POSTGRES_USER=twenty \
-e POSTGRES_PASSWORD="$POSTGRES_PASSWORD" \
-v twenty-db-data:/var/lib/postgresql/data:Z \
docker.io/library/postgres:16
podman run -d --pod twenty-pod --name twenty-redis \
-v twenty-redis-data:/data:Z \
docker.io/library/redis:latest
podman run -d --pod twenty-pod --name twenty-server \
-e NODE_PORT=3000 \
-e SERVER_URL="$SERVER_URL" \
-e PG_DATABASE_URL="postgresql://twenty:$POSTGRES_PASSWORD@twenty-db:5432/twenty" \
-e REDIS_URL="redis://twenty-redis:6379" \
-e APP_SECRET="$APP_SECRET" \
-e NODE_ENV=production \
-e LOG_LEVEL=info \
-v twenty-server-data:/app/docker-data:Z \
docker.io/twentycrm/twenty:latest
podman run -d --pod twenty-pod --name twenty-worker \
--init \
-e SERVER_URL="$SERVER_URL" \
-e PG_DATABASE_URL="postgresql://twenty:$POSTGRES_PASSWORD@twenty-db:5432/twenty" \
-e REDIS_URL="redis://twenty-redis:6379" \
-e APP_SECRET="$APP_SECRET" \
-e DISABLE_DB_MIGRATIONS=true \
-e NODE_ENV=production \
-e LOG_LEVEL=info \
-v twenty-server-data:/app/docker-data:Z \
docker.io/twentycrm/twenty:latest \
yarn worker:prod
# wait some time, check status
sleep 30
podman ps --pod -f name=twenty-pod
mkdir -p ~/.config/systemd/user
if [ ! -f "twentycrm.service" ]; then
echo "Error: twentycrm.service file not found"
exit 1
fi
cp twentycrm.service ~/.config/systemd/user
systemctl --user daemon-reload
systemctl --user enable twentycrm.service
systemctl --user start twentycrm.service
@@ -1,56 +0,0 @@
version: "3.8"
services:
db:
container_name: twenty-db
image: postgres:16
environment:
POSTGRES_DB: twenty
POSTGRES_USER: twenty
POSTGRES_PASSWORD: ${PG_DATABASE_PASSWORD}
volumes:
- twenty-db-data:/var/lib/postgresql/data:Z
redis:
container_name: twenty-redis
image: redis:latest
command: ["--maxmemory-policy", "noeviction"]
volumes:
- twenty-redis-data:/data:Z
server:
container_name: twenty-server
image: twentycrm/twenty:latest
environment:
NODE_PORT: "3000"
SERVER_URL: ${SERVER_URL}
PG_DATABASE_URL: "postgresql://twenty:${PG_DATABASE_PASSWORD}@twenty-db:5432/twenty"
REDIS_URL: "redis://twenty-redis:6379"
APP_SECRET: ${APP_SECRET}
NODE_ENV: production
LOG_LEVEL: info
volumes:
- twenty-server-data:/app/docker-data:Z
ports:
- 127.0.0.1:8080:3000
worker:
container_name: twenty-worker
image: twentycrm/twenty:latest
command: yarn worker:prod
environment:
SERVER_URL: ${SERVER_URL}
PG_DATABASE_URL: "postgresql://twenty:${PG_DATABASE_PASSWORD}@twenty-db:5432/twenty"
REDIS_URL: "redis://twenty-redis:6379"
APP_SECRET: ${APP_SECRET}
DISABLE_DB_MIGRATIONS: "true"
NODE_ENV: production
LOG_LEVEL: info
volumes:
- twenty-server-data:/app/docker-data:Z
init: true
volumes:
twenty-db-data:
twenty-server-data:
twenty-redis-data:
@@ -1,15 +0,0 @@
[Unit]
Description=TwentyCRM Container Stack via Podman-Compose
After=network.target
[Service]
Type=simple
WorkingDirectory=/opt/apps/twenty
EnvironmentFile=/opt/apps/twenty/.env
ExecStart=/usr/bin/podman-compose -f podman-compose.yml up -d
ExecStop=/usr/bin/podman-compose -f podman-compose.yml down
RemainAfterExit=yes
[Install]
WantedBy=default.target
@@ -8,8 +8,6 @@ COPY ./yarn.lock .
COPY ./.yarnrc.yml .
COPY ./.yarn/releases /app/.yarn/releases
COPY ./tools/eslint-rules /app/tools/eslint-rules
COPY ./packages/twenty-ui/package.json /app/packages/twenty-ui/
COPY ./packages/twenty-shared/package.json /app/packages/twenty-shared/
COPY ./packages/twenty-website/package.json /app/packages/twenty-website/package.json
RUN yarn
@@ -19,7 +17,6 @@ ENV KEYSTATIC_GITHUB_CLIENT_SECRET="<fake build value>"
ENV KEYSTATIC_SECRET="<fake build value>"
ENV NEXT_PUBLIC_KEYSTATIC_GITHUB_APP_SLUG="<fake build value>"
COPY ./packages/twenty-ui /app/packages/twenty-ui
COPY ./packages/twenty-website /app/packages/twenty-website
RUN npx nx build twenty-website
+4 -2
View File
@@ -64,6 +64,8 @@ WORKDIR /app/packages/twenty-server
ARG REACT_APP_SERVER_BASE_URL
ENV REACT_APP_SERVER_BASE_URL $REACT_APP_SERVER_BASE_URL
ARG SENTRY_RELEASE
ENV SENTRY_RELEASE $SENTRY_RELEASE
ARG APP_VERSION
ENV APP_VERSION $APP_VERSION
@@ -77,8 +79,8 @@ COPY --chown=1000 --from=twenty-front-build /app/packages/twenty-front/build /ap
LABEL org.opencontainers.image.source=https://github.com/twentyhq/twenty
LABEL org.opencontainers.image.description="This image provides a consistent and reproducible environment for the backend and frontend, ensuring it deploys faster and runs the same way regardless of the deployment environment."
RUN mkdir -p /app/.local-storage /app/packages/twenty-server/.local-storage && \
chown -R 1000:1000 /app
RUN mkdir /app/.local-storage
RUN chown -R 1000 /app
# Use non root user with uid 1000
USER 1000
+14 -20
View File
@@ -1,34 +1,28 @@
#!/bin/sh
set -e
setup_and_migrate_db() {
if [ "${DISABLE_DB_MIGRATIONS}" = "true" ]; then
echo "Database setup and migrations are disabled, skipping..."
return
fi
# Check if the initialization has already been done and that we enabled automatic migration
if [ "${DISABLE_DB_MIGRATIONS}" != "true" ] && [ ! -f /app/docker-data/db_status ]; then
echo "Running database setup and migrations..."
# Creating the database if it doesn't exist
PGUSER=$(echo $PG_DATABASE_URL | awk -F '//' '{print $2}' | awk -F ':' '{print $1}')
PGPASS=$(echo $PG_DATABASE_URL | awk -F ':' '{print $3}' | awk -F '@' '{print $1}')
PGHOST=$(echo $PG_DATABASE_URL | awk -F '@' '{print $2}' | awk -F ':' '{print $1}')
PGPORT=$(echo $PG_DATABASE_URL | awk -F ':' '{print $4}' | awk -F '/' '{print $1}')
PGDATABASE=$(echo $PG_DATABASE_URL | awk -F ':' '{print $4}' | awk -F '/' '{print $2}')
PGPASSWORD=${PGPASS} psql -h ${PGHOST} -p ${PGPORT} -U ${PGUSER} -d postgres -tc "SELECT 1 FROM pg_database WHERE datname = '${PGDATABASE}'" | grep -q 1 || \
PGPASSWORD=${PGPASS} psql -h ${PGHOST} -p ${PGPORT} -U ${PGUSER} -d postgres -c "CREATE DATABASE \"${PGDATABASE}\""
# Creating the database if it doesn't exist
db_count=$(PGPASSWORD=${PGPASS} psql -h ${PGHOST} -p ${PGPORT} -U ${PGUSER} -d postgres -tAc "SELECT COUNT(*) FROM pg_database WHERE datname = '${PGDATABASE}'")
if [ "$db_count" = "0" ]; then
echo "Database ${PGDATABASE} does not exist, creating..."
PGPASSWORD=${PGPASS} psql -h ${PGHOST} -p ${PGPORT} -U ${PGUSER} -d postgres -c "CREATE DATABASE \"${PGDATABASE}\""
# Run setup and migration scripts
NODE_OPTIONS="--max-old-space-size=1500" tsx ./scripts/setup-db.ts
yarn database:migrate:prod
fi
# Run setup and migration scripts
NODE_OPTIONS="--max-old-space-size=1500" tsx ./scripts/setup-db.ts
yarn database:migrate:prod
yarn command:prod upgrade
echo "Successfully migrated DB!"
}
setup_and_migrate_db
# Mark initialization as done
echo "Successfuly migrated DB!"
touch /app/docker-data/db_status
fi
# Continue with the original Docker command
exec "$@"
+1 -1
View File
@@ -2,5 +2,5 @@
FRONTEND_BASE_URL=http://localhost:3001
BACKEND_BASE_URL=http://localhost:3000
DEFAULT_LOGIN=tim@apple.dev
DEFAULT_PASSWORD=tim@apple.dev
DEFAULT_PASSWORD=Applecar2025
WEBSITE_URL=https://twenty.com
@@ -17,15 +17,12 @@ export class WorkflowVisualizerPage {
readonly deactivateWorkflowButton: Locator;
readonly addTriggerButton: Locator;
readonly commandMenu: Locator;
readonly stepHeaderInCommandMenu: Locator;
readonly workflowNameLabel: Locator;
readonly triggerNode: Locator;
readonly background: Locator;
readonly useAsDraftButton: Locator;
readonly overrideDraftButton: Locator;
readonly discardDraftButton: Locator;
readonly seeRunsButton: Locator;
readonly goBackInCommandMenu: Locator;
#actionNames: Record<WorkflowActionType, string> = {
'create-record': 'Create Record',
@@ -33,7 +30,6 @@ export class WorkflowVisualizerPage {
'delete-record': 'Delete Record',
code: 'Code',
'send-email': 'Send Email',
form: 'Form',
};
#createdActionNames: Record<WorkflowActionType, string> = {
@@ -42,7 +38,6 @@ export class WorkflowVisualizerPage {
'delete-record': 'Delete Record',
code: 'Code - Serverless Function',
'send-email': 'Send Email',
form: 'Form',
};
#triggerNames: Record<WorkflowTriggerType, string> = {
@@ -73,9 +68,6 @@ export class WorkflowVisualizerPage {
});
this.addTriggerButton = page.getByText('Add a Trigger');
this.commandMenu = page.getByTestId('command-menu');
this.stepHeaderInCommandMenu = this.commandMenu.getByTestId(
'workflow-step-header',
);
this.workflowNameLabel = page
.getByTestId('top-bar-title')
.getByText(this.workflowName);
@@ -88,10 +80,6 @@ export class WorkflowVisualizerPage {
this.discardDraftButton = page.getByRole('button', {
name: 'Discard Draft',
});
this.seeRunsButton = page.getByRole('link', { name: 'See runs' });
this.goBackInCommandMenu = this.commandMenu
.getByRole('button')
.and(this.commandMenu.getByTestId('command-menu-go-back-button'));
}
async createOneWorkflow() {
@@ -126,7 +114,7 @@ export class WorkflowVisualizerPage {
.getByTestId(`row-id-${this.workflowId}`)
.getByRole('link', { name: this.workflowName });
await workflowLink.click({ force: true });
await workflowLink.click();
await this.waitForWorkflowVisualizerLoad();
}
@@ -258,64 +246,29 @@ export class WorkflowVisualizerPage {
await expect(this.#page.getByTestId('command-menu')).not.toBeVisible();
}
async goToWorkflowsIndexPage() {
await this.#page.goto('/objects/workflows');
}
async setWorkflowsOpenInMode(mode: 'side-panel' | 'record-page') {
const recordTableOptionsButton = this.#page.getByText('Options');
await recordTableOptionsButton.click();
const layoutButton = this.#page.getByText('Layout');
await layoutButton.click();
const openInButton = this.#page.getByText('Open in');
await openInButton.click();
if (mode === 'side-panel') {
const openInSidePanelOption = this.#page.getByRole('option', {
name: 'Side Panel',
});
await openInSidePanelOption.click();
} else {
const openInRecordPageOption = this.#page.getByRole('option', {
name: 'Record Page',
});
await openInRecordPageOption.click();
}
// Close the dropdown
await recordTableOptionsButton.click();
}
}
export const test = base.extend<{
workflowVisualizer: WorkflowVisualizerPage;
}>({
workflowVisualizer: async ({ page }, use) => {
const workflowVisualizer = new WorkflowVisualizerPage({
page,
workflowName: 'Test Workflow',
});
export const test = base.extend<{ workflowVisualizer: WorkflowVisualizerPage }>(
{
workflowVisualizer: async ({ page }, use) => {
const workflowVisualizer = new WorkflowVisualizerPage({
page,
workflowName: 'Test Workflow',
});
await workflowVisualizer.createOneWorkflow();
await workflowVisualizer.goToWorkflowVisualizerPage();
await workflowVisualizer.createOneWorkflow();
await workflowVisualizer.goToWorkflowVisualizerPage();
await use(workflowVisualizer);
await use(workflowVisualizer);
await deleteWorkflow({
page,
workflowId: workflowVisualizer.workflowId,
});
await destroyWorkflow({
page,
workflowId: workflowVisualizer.workflowId,
});
await workflowVisualizer.goToWorkflowsIndexPage();
await workflowVisualizer.setWorkflowsOpenInMode('record-page');
await deleteWorkflow({
page,
workflowId: workflowVisualizer.workflowId,
});
await destroyWorkflow({
page,
workflowId: workflowVisualizer.workflowId,
});
},
},
});
);
@@ -110,10 +110,6 @@ export class SettingsPage {
await this.releasesLink.click();
}
async logout() {
await this.page.getByText('Logout').click();
}
async toggleAdvancedSettings() {
await this.advancedToggle.click();
}
@@ -19,7 +19,7 @@ export const deleteWorkflow = async ({
operationName: 'DeleteOneWorkflow',
variables: { idToDelete: workflowId },
query:
'mutation DeleteOneWorkflow($idToDelete: UUID!) {\n deleteWorkflow(id: $idToDelete) {\n __typename\n deletedAt\n id\n }\n}',
'mutation DeleteOneWorkflow($idToDelete: ID!) {\n deleteWorkflow(id: $idToDelete) {\n __typename\n deletedAt\n id\n }\n}',
},
});
};
@@ -19,7 +19,7 @@ export const destroyWorkflow = async ({
operationName: 'DestroyOneWorkflow',
variables: { idToDestroy: workflowId },
query:
'mutation DestroyOneWorkflow($idToDestroy: UUID!) {\n destroyWorkflow(id: $idToDestroy) {\n id\n __typename\n }\n}',
'mutation DestroyOneWorkflow($idToDestroy: ID!) {\n destroyWorkflow(id: $idToDestroy) {\n id\n __typename\n }\n}',
},
});
};
@@ -9,5 +9,4 @@ export type WorkflowActionType =
| 'update-record'
| 'delete-record'
| 'code'
| 'send-email'
| 'form';
| 'send-email';
+1
View File
@@ -1,5 +1,6 @@
{
"name": "twenty-e2e-testing",
"version": "0.50.6",
"description": "",
"author": "",
"private": true,
@@ -10,9 +10,7 @@ test('Create workflow', async ({ page }) => {
const workflowsLink = page.getByRole('link', { name: 'Workflows' });
await workflowsLink.click();
const createWorkflowButton = page.getByRole('button', {
name: 'Create new workflow',
});
const createWorkflowButton = page.getByRole('button', { name: 'New record' });
const [createWorkflowResponse] = await Promise.all([
page.waitForResponse(async (response) => {
@@ -28,7 +26,7 @@ test('Create workflow', async ({ page }) => {
createWorkflowButton.click(),
]);
const recordName = page.getByTestId('top-bar-title').getByText('Untitled');
const recordName = page.getByTestId('top-bar-title').getByTestId('tooltip');
await recordName.click();
const nameInput = page.getByTestId('top-bar-title').getByRole('textbox');
@@ -1,152 +1,60 @@
import { expect } from '@playwright/test';
import { test } from '../lib/fixtures/blank-workflow';
test('The workflow run visualizer shows the executed draft version without the last draft changes', async ({
workflowVisualizer,
page,
}) => {
await workflowVisualizer.createInitialTrigger('manual');
test.fixme(
'The workflow run visualizer shows the executed draft version without the last draft changes',
async ({ workflowVisualizer, page }) => {
await workflowVisualizer.createInitialTrigger('manual');
const manualTriggerAvailabilitySelect = page.getByRole('button', {
name: 'When record(s) are selected',
});
const manualTriggerAvailabilitySelect = page.getByRole('button', {
name: 'When record(s) are selected',
});
await manualTriggerAvailabilitySelect.click();
await manualTriggerAvailabilitySelect.click();
const alwaysAvailableOption = page.getByText(
'When no record(s) are selected',
);
const alwaysAvailableOption = page.getByText(
'When no record(s) are selected',
);
await alwaysAvailableOption.click();
await alwaysAvailableOption.click();
await workflowVisualizer.closeSidePanel();
await workflowVisualizer.closeSidePanel();
const { createdStepId: firstStepId } =
await workflowVisualizer.createStep('create-record');
const { createdStepId: firstStepId } =
await workflowVisualizer.createStep('create-record');
await workflowVisualizer.closeSidePanel();
await workflowVisualizer.closeSidePanel();
const launchTestButton = page.getByLabel(workflowVisualizer.workflowName);
const launchTestButton = page.getByLabel('Test Workflow');
await launchTestButton.click();
await launchTestButton.click();
const goToExecutionPageLink = page.getByRole('link', {
name: 'View execution details',
});
const executionPageUrl = await goToExecutionPageLink.getAttribute('href');
expect(executionPageUrl).not.toBeNull();
const goToExecutionPageLink = page.getByRole('link', {
name: 'View execution details',
});
const executionPageUrl = await goToExecutionPageLink.getAttribute('href');
expect(executionPageUrl).not.toBeNull();
await workflowVisualizer.deleteStep(firstStepId);
await workflowVisualizer.deleteStep(firstStepId);
await page.goto(executionPageUrl!);
await page.goto(executionPageUrl!);
const workflowRunName = page
.getByText(`#1 - ${workflowVisualizer.workflowName}`)
.nth(1);
const workflowRunName = page.getByText('Execution of v1');
await expect(workflowRunName).toBeVisible();
await expect(workflowRunName).toBeVisible();
const executedFirstStepNode = workflowVisualizer.getStepNode(firstStepId);
const flowTab = page.getByText('Flow', { exact: true });
await expect(executedFirstStepNode).toBeVisible();
await flowTab.click();
await executedFirstStepNode.click();
const executedFirstStepNode = workflowVisualizer.getStepNode(firstStepId);
await expect(workflowVisualizer.stepHeaderInCommandMenu).toContainText(
'Create Record',
);
});
await expect(executedFirstStepNode).toBeVisible();
test('Workflow Runs with a pending form step can be opened in the side panel and then in full screen', async ({
workflowVisualizer,
page,
}) => {
await workflowVisualizer.createInitialTrigger('manual');
await executedFirstStepNode.click();
const manualTriggerAvailabilitySelect = page.getByRole('button', {
name: 'When record(s) are selected',
});
await manualTriggerAvailabilitySelect.click();
const alwaysAvailableOption = page.getByText(
'When no record(s) are selected',
);
await alwaysAvailableOption.click();
await workflowVisualizer.closeSidePanel();
const { createdStepId: firstStepId } =
await workflowVisualizer.createStep('form');
await workflowVisualizer.closeSidePanel();
const launchTestButton = page.getByLabel(workflowVisualizer.workflowName);
await launchTestButton.click();
const goToExecutionPageLink = page.getByRole('link', {
name: 'View execution details',
});
await expect(goToExecutionPageLink).toBeVisible();
await workflowVisualizer.seeRunsButton.click();
const workflowRunName = `#1 - ${workflowVisualizer.workflowName}`;
const workflowRunNameCell = page.getByRole('cell', { name: workflowRunName });
await expect(workflowRunNameCell).toBeVisible();
await workflowVisualizer.setWorkflowsOpenInMode('side-panel');
// 1. Exit the dropdown
await workflowRunNameCell.click();
// 2. Actually open the workflow run in the side panel
await workflowRunNameCell.click();
await expect(workflowVisualizer.stepHeaderInCommandMenu).toContainText(
'Form',
);
await workflowVisualizer.goBackInCommandMenu.click();
const workflowRunNameInCommandMenu =
workflowVisualizer.commandMenu.getByText(workflowRunName);
await expect(workflowRunNameInCommandMenu).toBeVisible();
await workflowVisualizer.triggerNode.click();
await expect(workflowVisualizer.stepHeaderInCommandMenu).toContainText(
'Launch manually',
);
await workflowVisualizer.goBackInCommandMenu.click();
const formStep = workflowVisualizer.getStepNode(firstStepId);
await formStep.click();
await workflowVisualizer.goBackInCommandMenu.click();
const openInFullScreenButton = workflowVisualizer.commandMenu.getByRole(
'button',
{ name: 'Open' },
);
await openInFullScreenButton.click();
const workflowRunNameInShowPage = page
.getByText(`#1 - ${workflowVisualizer.workflowName}`)
.nth(1);
await expect(workflowRunNameInShowPage).toBeVisible();
// Expect the side panel to be opened by default on the form.
await expect(workflowVisualizer.stepHeaderInCommandMenu).toContainText(
'Form',
);
});
await expect(
workflowVisualizer.commandMenu.getByRole('textbox').first(),
).toHaveValue('Create Record');
},
);
@@ -8,19 +8,25 @@ test('Use an old version as draft', async ({ workflowVisualizer, page }) => {
await workflowVisualizer.background.click();
await workflowVisualizer.activateWorkflowButton.click();
await Promise.all([
expect(workflowVisualizer.workflowStatus).toHaveText('Active'),
await expect(workflowVisualizer.workflowStatus).toHaveText('Active');
workflowVisualizer.activateWorkflowButton.click(),
]);
await workflowVisualizer.createStep('delete-record');
await Promise.all([
expect(workflowVisualizer.workflowStatus).toHaveText('Draft'),
await expect(workflowVisualizer.workflowStatus).toHaveText('Draft');
workflowVisualizer.createStep('delete-record'),
]);
await workflowVisualizer.closeSidePanel();
await workflowVisualizer.background.click();
await workflowVisualizer.activateWorkflowButton.click();
await Promise.all([
expect(workflowVisualizer.workflowStatus).toHaveText('Active'),
await expect(workflowVisualizer.workflowStatus).toHaveText('Active');
workflowVisualizer.activateWorkflowButton.click(),
]);
await expect(workflowVisualizer.triggerNode).toContainText(
'Record is Created',
@@ -35,8 +41,6 @@ test('Use an old version as draft', async ({ workflowVisualizer, page }) => {
const workflowsLink = page.getByRole('link', { name: 'Workflows' });
await workflowsLink.click();
await workflowVisualizer.setWorkflowsOpenInMode('record-page');
const recordTableRowForWorkflow = page.getByRole('row', {
name: workflowVisualizer.workflowName,
});
@@ -44,7 +48,7 @@ test('Use an old version as draft', async ({ workflowVisualizer, page }) => {
const linkToWorkflow = recordTableRowForWorkflow.getByRole('link', {
name: workflowVisualizer.workflowName,
});
await expect(linkToWorkflow).toBeVisible();
expect(linkToWorkflow).toBeVisible();
const linkToFirstWorkflowVersion = recordTableRowForWorkflow.getByRole(
'link',
@@ -53,7 +57,7 @@ test('Use an old version as draft', async ({ workflowVisualizer, page }) => {
},
);
await linkToFirstWorkflowVersion.click({ force: true });
await linkToFirstWorkflowVersion.click();
await expect(workflowVisualizer.workflowStatus).toHaveText('Archived');
await expect(workflowVisualizer.useAsDraftButton).toBeVisible();
@@ -65,9 +69,11 @@ test('Use an old version as draft', async ({ workflowVisualizer, page }) => {
]);
await expect(workflowVisualizer.getAllStepNodes()).toHaveCount(1);
await workflowVisualizer.useAsDraftButton.click();
await Promise.all([
page.waitForURL(`/object/workflow/${workflowVisualizer.workflowId}`),
await page.waitForURL(`/object/workflow/${workflowVisualizer.workflowId}`);
workflowVisualizer.useAsDraftButton.click(),
]);
await expect(workflowVisualizer.workflowStatus).toHaveText('Draft');
await expect(workflowVisualizer.useAsDraftButton).not.toBeVisible();
@@ -90,13 +96,17 @@ test('Use an old version as draft while having a pending draft version', async (
await workflowVisualizer.background.click();
await workflowVisualizer.activateWorkflowButton.click();
await Promise.all([
expect(workflowVisualizer.workflowStatus).toHaveText('Active'),
await expect(workflowVisualizer.workflowStatus).toHaveText('Active');
workflowVisualizer.activateWorkflowButton.click(),
]);
await workflowVisualizer.createStep('delete-record');
await Promise.all([
expect(workflowVisualizer.workflowStatus).toHaveText('Draft'),
await expect(workflowVisualizer.workflowStatus).toHaveText('Draft');
workflowVisualizer.createStep('delete-record'),
]);
await expect(workflowVisualizer.triggerNode).toContainText(
'Record is Created',
@@ -120,7 +130,7 @@ test('Use an old version as draft while having a pending draft version', async (
const linkToWorkflow = recordTableRowForWorkflow.getByRole('link', {
name: workflowVisualizer.workflowName,
});
await expect(linkToWorkflow).toBeVisible();
expect(linkToWorkflow).toBeVisible();
const linkToFirstWorkflowVersion = recordTableRowForWorkflow.getByRole(
'link',
@@ -129,7 +139,7 @@ test('Use an old version as draft while having a pending draft version', async (
},
);
await linkToFirstWorkflowVersion.click({ force: true });
await linkToFirstWorkflowVersion.click();
await expect(workflowVisualizer.workflowStatus).toHaveText('Active');
await expect(workflowVisualizer.useAsDraftButton).toBeVisible();
@@ -141,13 +151,17 @@ test('Use an old version as draft while having a pending draft version', async (
]);
await expect(workflowVisualizer.getAllStepNodes()).toHaveCount(1);
await workflowVisualizer.useAsDraftButton.click();
await Promise.all([
expect(workflowVisualizer.overrideDraftButton).toBeVisible(),
await expect(workflowVisualizer.overrideDraftButton).toBeVisible();
workflowVisualizer.useAsDraftButton.click(),
]);
await workflowVisualizer.overrideDraftButton.click();
await Promise.all([
page.waitForURL(`/object/workflow/${workflowVisualizer.workflowId}`),
await page.waitForURL(`/object/workflow/${workflowVisualizer.workflowId}`);
workflowVisualizer.overrideDraftButton.click(),
]);
await expect(workflowVisualizer.workflowStatus).toHaveText('Draft');
await expect(workflowVisualizer.useAsDraftButton).not.toBeVisible();
+1
View File
@@ -1,5 +1,6 @@
{
"name": "twenty-emails",
"version": "0.50.6",
"description": "",
"author": "",
"private": true,
+2 -1
View File
@@ -54,7 +54,8 @@ export const emailTheme = {
},
background: {
colors: { highlight: grayScale.gray15 },
button: grayScale.gray60,
radialGradient: `radial-gradient(50% 62.62% at 50% 0%, #505050 0%, ${grayScale.gray60} 100%)`,
radialGradientHover: `radial-gradient(76.32% 95.59% at 50% 0%, #505050 0%, ${grayScale.gray60} 100%)`,
transparent: {
medium: 'rgba(0, 0, 0, 0.08)',
light: 'rgba(0, 0, 0, 0.04)',
@@ -3,16 +3,15 @@ import { Button } from '@react-email/components';
import { emailTheme } from 'src/common-style';
const callToActionStyle = {
display: 'inline-flex',
display: 'flex',
padding: '8px 32px',
borderRadius: emailTheme.border.radius.md,
border: `1px solid ${emailTheme.background.transparent.light}`,
background: emailTheme.background.button,
background: emailTheme.background.radialGradient,
boxShadow: `0px 2px 4px 0px ${emailTheme.background.transparent.light}, 0px 0px 4px 0px ${emailTheme.background.transparent.medium}`,
color: emailTheme.font.colors.inverted,
fontSize: emailTheme.font.size.md,
fontWeight: emailTheme.font.weight.bold,
width: 'auto',
};
type CallToActionProps = {
@@ -7,7 +7,7 @@ const logoStyle = {
export const Logo = () => {
return (
<Img
src="https://app.twenty.com/images/icons/windows11/Square150x150Logo.scale-100.png"
src="https://app.twenty.com/icons/windows11/Square150x150Logo.scale-100.png"
alt="Twenty logo"
width="40"
height="40"
@@ -42,13 +42,10 @@ export const CleanSuspendedWorkspaceEmail = ({
<br />
<Trans id="If you wish to use Twenty again, you can create a new workspace." />
</MainText>
<br />
<CallToAction
href="https://app.twenty.com/"
value={i18n._('Create a new workspace')}
/>
<br />
<br />
</BaseEmail>
);
};
@@ -21,6 +21,7 @@ export const PasswordResetLinkEmail = ({
return (
<BaseEmail locale={locale}>
<Title value={i18n._('Reset your password 🗝')} />
<CallToAction href={link} value={i18n._('Reset')} />
<MainText>
<Trans
id="This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:"
@@ -29,10 +30,6 @@ export const PasswordResetLinkEmail = ({
<br />
<Link href={link} value={link} />
</MainText>
<br />
<CallToAction href={link} value={i18n._('Reset')} />
<br />
<br />
</BaseEmail>
);
};
@@ -41,10 +41,7 @@ export const PasswordUpdateNotifyEmail = ({
<Trans id="If you did not initiate this change, please contact your workspace owner immediately." />
<br />
</MainText>
<br />
<CallToAction value={i18n._('Connect to Twenty')} href={link} />
<br />
<br />
</BaseEmail>
);
};
@@ -18,13 +18,12 @@ export const SendEmailVerificationLinkEmail = ({
return (
<BaseEmail width={333} locale={locale}>
<Title value={i18n._('Confirm your email address')} />
<MainText>
<Trans id="Thanks for registering for an account on Twenty! Before we get started, we just need to confirm that this is you. Click below to verify your email address." />
</MainText>
<br />
<CallToAction href={link} value={i18n._('Verify Email')} />
<br />
<br />
<MainText>
<Trans id="Thanks for registering for an account on Twenty! Before we get started, we just need to confirm that this is you. Click above to verify your email address." />
</MainText>
</BaseEmail>
);
};
@@ -13,8 +13,6 @@ export const TestEmail = ({ locale }: TestEmailProps) => {
return (
<BaseEmail locale={locale}>
<Title value={i18n._('Test email')} />
<br />
<br />
</BaseEmail>
);
};
@@ -76,7 +76,6 @@ export const SendApprovedAccessDomainValidation = ({
{workspace.name ? <HighlightedText value={workspace.name} /> : <></>}
<CallToAction href={link} value={i18n._('Validate domain')} />
</HighlightedContainer>
<br />
</BaseEmail>
);
};
@@ -54,13 +54,10 @@ export const WarnSuspendedWorkspaceEmail = ({
values={{ remainingDays, dayOrDays }}
/>
</MainText>
<br />
<CallToAction
href="https://app.twenty.com/settings/billing"
value={i18n._('Update your subscription')}
/>
<br />
<br />
</BaseEmail>
);
};
+4 -9
View File
@@ -95,12 +95,12 @@ msgstr ""
#. js-lingui-explicit-id
#: src/emails/send-email-verification-link.email.tsx
msgid "Thanks for registering for an account on Twenty! Before we get started, we just need to confirm that this is you. Click below to verify your email address."
msgid "Verify Email"
msgstr ""
#. js-lingui-explicit-id
#: src/emails/send-email-verification-link.email.tsx
msgid "Verify Email"
msgid "Thanks for registering for an account on Twenty! Before we get started, we just need to confirm that this is you. Click above to verify your email address."
msgstr ""
#. js-lingui-explicit-id
@@ -130,12 +130,12 @@ msgstr ""
#. js-lingui-explicit-id
#: src/emails/password-reset-link.email.tsx
msgid "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:"
msgid "Reset"
msgstr ""
#. js-lingui-explicit-id
#: src/emails/password-reset-link.email.tsx
msgid "Reset"
msgid "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:"
msgstr ""
#. js-lingui-explicit-id
@@ -223,11 +223,6 @@ msgstr ""
msgid "San Francisco / Paris"
msgstr ""
#. js-lingui-explicit-id
#: src/emails/send-email-verification-link.email.tsx
#~ msgid "Thanks for registering for an account on Twenty! Before we get started, we just need to confirm that this is you. Click above to verify your email address."
#~ msgstr ""
#. js-lingui-id: 4WPI3S
#: src/emails/send-invite-link.email.tsx
#~ msgid "Accept invite"
+4 -9
View File
@@ -95,12 +95,12 @@ msgstr ""
#. js-lingui-explicit-id
#: src/emails/send-email-verification-link.email.tsx
msgid "Thanks for registering for an account on Twenty! Before we get started, we just need to confirm that this is you. Click below to verify your email address."
msgid "Verify Email"
msgstr ""
#. js-lingui-explicit-id
#: src/emails/send-email-verification-link.email.tsx
msgid "Verify Email"
msgid "Thanks for registering for an account on Twenty! Before we get started, we just need to confirm that this is you. Click above to verify your email address."
msgstr ""
#. js-lingui-explicit-id
@@ -130,12 +130,12 @@ msgstr ""
#. js-lingui-explicit-id
#: src/emails/password-reset-link.email.tsx
msgid "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:"
msgid "Reset"
msgstr ""
#. js-lingui-explicit-id
#: src/emails/password-reset-link.email.tsx
msgid "Reset"
msgid "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:"
msgstr ""
#. js-lingui-explicit-id
@@ -223,11 +223,6 @@ msgstr ""
msgid "San Francisco / Paris"
msgstr ""
#. js-lingui-explicit-id
#: src/emails/send-email-verification-link.email.tsx
#~ msgid "Thanks for registering for an account on Twenty! Before we get started, we just need to confirm that this is you. Click above to verify your email address."
#~ msgstr ""
#. js-lingui-id: 4WPI3S
#: src/emails/send-invite-link.email.tsx
#~ msgid "Accept invite"
+4 -9
View File
@@ -95,12 +95,12 @@ msgstr ""
#. js-lingui-explicit-id
#: src/emails/send-email-verification-link.email.tsx
msgid "Thanks for registering for an account on Twenty! Before we get started, we just need to confirm that this is you. Click below to verify your email address."
msgid "Verify Email"
msgstr ""
#. js-lingui-explicit-id
#: src/emails/send-email-verification-link.email.tsx
msgid "Verify Email"
msgid "Thanks for registering for an account on Twenty! Before we get started, we just need to confirm that this is you. Click above to verify your email address."
msgstr ""
#. js-lingui-explicit-id
@@ -130,12 +130,12 @@ msgstr ""
#. js-lingui-explicit-id
#: src/emails/password-reset-link.email.tsx
msgid "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:"
msgid "Reset"
msgstr ""
#. js-lingui-explicit-id
#: src/emails/password-reset-link.email.tsx
msgid "Reset"
msgid "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:"
msgstr ""
#. js-lingui-explicit-id
@@ -223,11 +223,6 @@ msgstr ""
msgid "San Francisco / Paris"
msgstr ""
#. js-lingui-explicit-id
#: src/emails/send-email-verification-link.email.tsx
#~ msgid "Thanks for registering for an account on Twenty! Before we get started, we just need to confirm that this is you. Click above to verify your email address."
#~ msgstr ""
#. js-lingui-id: 4WPI3S
#: src/emails/send-invite-link.email.tsx
#~ msgid "Accept invite"
+4 -9
View File
@@ -95,12 +95,12 @@ msgstr ""
#. js-lingui-explicit-id
#: src/emails/send-email-verification-link.email.tsx
msgid "Thanks for registering for an account on Twenty! Before we get started, we just need to confirm that this is you. Click below to verify your email address."
msgid "Verify Email"
msgstr ""
#. js-lingui-explicit-id
#: src/emails/send-email-verification-link.email.tsx
msgid "Verify Email"
msgid "Thanks for registering for an account on Twenty! Before we get started, we just need to confirm that this is you. Click above to verify your email address."
msgstr ""
#. js-lingui-explicit-id
@@ -130,12 +130,12 @@ msgstr ""
#. js-lingui-explicit-id
#: src/emails/password-reset-link.email.tsx
msgid "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:"
msgid "Reset"
msgstr ""
#. js-lingui-explicit-id
#: src/emails/password-reset-link.email.tsx
msgid "Reset"
msgid "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:"
msgstr ""
#. js-lingui-explicit-id
@@ -223,11 +223,6 @@ msgstr ""
msgid "San Francisco / Paris"
msgstr ""
#. js-lingui-explicit-id
#: src/emails/send-email-verification-link.email.tsx
#~ msgid "Thanks for registering for an account on Twenty! Before we get started, we just need to confirm that this is you. Click above to verify your email address."
#~ msgstr ""
#. js-lingui-id: 4WPI3S
#: src/emails/send-invite-link.email.tsx
#~ msgid "Accept invite"
+4 -9
View File
@@ -95,12 +95,12 @@ msgstr ""
#. js-lingui-explicit-id
#: src/emails/send-email-verification-link.email.tsx
msgid "Thanks for registering for an account on Twenty! Before we get started, we just need to confirm that this is you. Click below to verify your email address."
msgid "Verify Email"
msgstr ""
#. js-lingui-explicit-id
#: src/emails/send-email-verification-link.email.tsx
msgid "Verify Email"
msgid "Thanks for registering for an account on Twenty! Before we get started, we just need to confirm that this is you. Click above to verify your email address."
msgstr ""
#. js-lingui-explicit-id
@@ -130,12 +130,12 @@ msgstr ""
#. js-lingui-explicit-id
#: src/emails/password-reset-link.email.tsx
msgid "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:"
msgid "Reset"
msgstr ""
#. js-lingui-explicit-id
#: src/emails/password-reset-link.email.tsx
msgid "Reset"
msgid "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:"
msgstr ""
#. js-lingui-explicit-id
@@ -223,11 +223,6 @@ msgstr ""
msgid "San Francisco / Paris"
msgstr ""
#. js-lingui-explicit-id
#: src/emails/send-email-verification-link.email.tsx
#~ msgid "Thanks for registering for an account on Twenty! Before we get started, we just need to confirm that this is you. Click above to verify your email address."
#~ msgstr ""
#. js-lingui-id: 4WPI3S
#: src/emails/send-invite-link.email.tsx
#~ msgid "Accept invite"
+4 -9
View File
@@ -95,12 +95,12 @@ msgstr ""
#. js-lingui-explicit-id
#: src/emails/send-email-verification-link.email.tsx
msgid "Thanks for registering for an account on Twenty! Before we get started, we just need to confirm that this is you. Click below to verify your email address."
msgid "Verify Email"
msgstr ""
#. js-lingui-explicit-id
#: src/emails/send-email-verification-link.email.tsx
msgid "Verify Email"
msgid "Thanks for registering for an account on Twenty! Before we get started, we just need to confirm that this is you. Click above to verify your email address."
msgstr ""
#. js-lingui-explicit-id
@@ -130,12 +130,12 @@ msgstr ""
#. js-lingui-explicit-id
#: src/emails/password-reset-link.email.tsx
msgid "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:"
msgid "Reset"
msgstr ""
#. js-lingui-explicit-id
#: src/emails/password-reset-link.email.tsx
msgid "Reset"
msgid "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:"
msgstr ""
#. js-lingui-explicit-id
@@ -223,11 +223,6 @@ msgstr ""
msgid "San Francisco / Paris"
msgstr ""
#. js-lingui-explicit-id
#: src/emails/send-email-verification-link.email.tsx
#~ msgid "Thanks for registering for an account on Twenty! Before we get started, we just need to confirm that this is you. Click above to verify your email address."
#~ msgstr ""
#. js-lingui-id: 4WPI3S
#: src/emails/send-invite-link.email.tsx
#~ msgid "Accept invite"
+4 -9
View File
@@ -95,12 +95,12 @@ msgstr ""
#. js-lingui-explicit-id
#: src/emails/send-email-verification-link.email.tsx
msgid "Thanks for registering for an account on Twenty! Before we get started, we just need to confirm that this is you. Click below to verify your email address."
msgid "Verify Email"
msgstr ""
#. js-lingui-explicit-id
#: src/emails/send-email-verification-link.email.tsx
msgid "Verify Email"
msgid "Thanks for registering for an account on Twenty! Before we get started, we just need to confirm that this is you. Click above to verify your email address."
msgstr ""
#. js-lingui-explicit-id
@@ -130,12 +130,12 @@ msgstr ""
#. js-lingui-explicit-id
#: src/emails/password-reset-link.email.tsx
msgid "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:"
msgid "Reset"
msgstr ""
#. js-lingui-explicit-id
#: src/emails/password-reset-link.email.tsx
msgid "Reset"
msgid "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:"
msgstr ""
#. js-lingui-explicit-id
@@ -223,11 +223,6 @@ msgstr ""
msgid "San Francisco / Paris"
msgstr ""
#. js-lingui-explicit-id
#: src/emails/send-email-verification-link.email.tsx
#~ msgid "Thanks for registering for an account on Twenty! Before we get started, we just need to confirm that this is you. Click above to verify your email address."
#~ msgstr ""
#. js-lingui-id: 4WPI3S
#: src/emails/send-invite-link.email.tsx
#~ msgid "Accept invite"
+8 -13
View File
@@ -90,13 +90,13 @@ msgstr "Confirm your email address"
#. js-lingui-explicit-id
#: src/emails/send-email-verification-link.email.tsx
msgid "Thanks for registering for an account on Twenty! Before we get started, we just need to confirm that this is you. Click below to verify your email address."
msgstr "Thanks for registering for an account on Twenty! Before we get started, we just need to confirm that this is you. Click below to verify your email address."
msgid "Verify Email"
msgstr "Verify Email"
#. js-lingui-explicit-id
#: src/emails/send-email-verification-link.email.tsx
msgid "Verify Email"
msgstr "Verify Email"
msgid "Thanks for registering for an account on Twenty! Before we get started, we just need to confirm that this is you. Click above to verify your email address."
msgstr "Thanks for registering for an account on Twenty! Before we get started, we just need to confirm that this is you. Click above to verify your email address."
#. js-lingui-explicit-id
#: src/emails/password-update-notify.email.tsx
@@ -125,13 +125,13 @@ msgstr "Reset your password 🗝"
#. js-lingui-explicit-id
#: src/emails/password-reset-link.email.tsx
msgid "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:"
msgstr "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:"
msgid "Reset"
msgstr "Reset"
#. js-lingui-explicit-id
#: src/emails/password-reset-link.email.tsx
msgid "Reset"
msgstr "Reset"
msgid "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:"
msgstr "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:"
#. js-lingui-explicit-id
#: src/emails/clean-suspended-workspace.email.tsx
@@ -218,11 +218,6 @@ msgstr "Twenty.com, Public Benefit Corporation"
msgid "San Francisco / Paris"
msgstr "San Francisco / Paris"
#. js-lingui-explicit-id
#: src/emails/send-email-verification-link.email.tsx
#~ msgid "Thanks for registering for an account on Twenty! Before we get started, we just need to confirm that this is you. Click above to verify your email address."
#~ msgstr "Thanks for registering for an account on Twenty! Before we get started, we just need to confirm that this is you. Click above to verify your email address."
#. js-lingui-id: 4WPI3S
#: src/emails/send-invite-link.email.tsx
#~ msgid "Accept invite"
+4 -9
View File
@@ -95,12 +95,12 @@ msgstr ""
#. js-lingui-explicit-id
#: src/emails/send-email-verification-link.email.tsx
msgid "Thanks for registering for an account on Twenty! Before we get started, we just need to confirm that this is you. Click below to verify your email address."
msgid "Verify Email"
msgstr ""
#. js-lingui-explicit-id
#: src/emails/send-email-verification-link.email.tsx
msgid "Verify Email"
msgid "Thanks for registering for an account on Twenty! Before we get started, we just need to confirm that this is you. Click above to verify your email address."
msgstr ""
#. js-lingui-explicit-id
@@ -130,12 +130,12 @@ msgstr ""
#. js-lingui-explicit-id
#: src/emails/password-reset-link.email.tsx
msgid "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:"
msgid "Reset"
msgstr ""
#. js-lingui-explicit-id
#: src/emails/password-reset-link.email.tsx
msgid "Reset"
msgid "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:"
msgstr ""
#. js-lingui-explicit-id
@@ -223,11 +223,6 @@ msgstr ""
msgid "San Francisco / Paris"
msgstr ""
#. js-lingui-explicit-id
#: src/emails/send-email-verification-link.email.tsx
#~ msgid "Thanks for registering for an account on Twenty! Before we get started, we just need to confirm that this is you. Click above to verify your email address."
#~ msgstr ""
#. js-lingui-id: 4WPI3S
#: src/emails/send-invite-link.email.tsx
#~ msgid "Accept invite"
+4 -9
View File
@@ -95,12 +95,12 @@ msgstr ""
#. js-lingui-explicit-id
#: src/emails/send-email-verification-link.email.tsx
msgid "Thanks for registering for an account on Twenty! Before we get started, we just need to confirm that this is you. Click below to verify your email address."
msgid "Verify Email"
msgstr ""
#. js-lingui-explicit-id
#: src/emails/send-email-verification-link.email.tsx
msgid "Verify Email"
msgid "Thanks for registering for an account on Twenty! Before we get started, we just need to confirm that this is you. Click above to verify your email address."
msgstr ""
#. js-lingui-explicit-id
@@ -130,12 +130,12 @@ msgstr ""
#. js-lingui-explicit-id
#: src/emails/password-reset-link.email.tsx
msgid "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:"
msgid "Reset"
msgstr ""
#. js-lingui-explicit-id
#: src/emails/password-reset-link.email.tsx
msgid "Reset"
msgid "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:"
msgstr ""
#. js-lingui-explicit-id
@@ -223,11 +223,6 @@ msgstr ""
msgid "San Francisco / Paris"
msgstr ""
#. js-lingui-explicit-id
#: src/emails/send-email-verification-link.email.tsx
#~ msgid "Thanks for registering for an account on Twenty! Before we get started, we just need to confirm that this is you. Click above to verify your email address."
#~ msgstr ""
#. js-lingui-id: 4WPI3S
#: src/emails/send-invite-link.email.tsx
#~ msgid "Accept invite"
+4 -9
View File
@@ -95,12 +95,12 @@ msgstr ""
#. js-lingui-explicit-id
#: src/emails/send-email-verification-link.email.tsx
msgid "Thanks for registering for an account on Twenty! Before we get started, we just need to confirm that this is you. Click below to verify your email address."
msgid "Verify Email"
msgstr ""
#. js-lingui-explicit-id
#: src/emails/send-email-verification-link.email.tsx
msgid "Verify Email"
msgid "Thanks for registering for an account on Twenty! Before we get started, we just need to confirm that this is you. Click above to verify your email address."
msgstr ""
#. js-lingui-explicit-id
@@ -130,12 +130,12 @@ msgstr ""
#. js-lingui-explicit-id
#: src/emails/password-reset-link.email.tsx
msgid "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:"
msgid "Reset"
msgstr ""
#. js-lingui-explicit-id
#: src/emails/password-reset-link.email.tsx
msgid "Reset"
msgid "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:"
msgstr ""
#. js-lingui-explicit-id
@@ -223,11 +223,6 @@ msgstr ""
msgid "San Francisco / Paris"
msgstr ""
#. js-lingui-explicit-id
#: src/emails/send-email-verification-link.email.tsx
#~ msgid "Thanks for registering for an account on Twenty! Before we get started, we just need to confirm that this is you. Click above to verify your email address."
#~ msgstr ""
#. js-lingui-id: 4WPI3S
#: src/emails/send-invite-link.email.tsx
#~ msgid "Accept invite"
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+4 -9
View File
@@ -95,12 +95,12 @@ msgstr ""
#. js-lingui-explicit-id
#: src/emails/send-email-verification-link.email.tsx
msgid "Thanks for registering for an account on Twenty! Before we get started, we just need to confirm that this is you. Click below to verify your email address."
msgid "Verify Email"
msgstr ""
#. js-lingui-explicit-id
#: src/emails/send-email-verification-link.email.tsx
msgid "Verify Email"
msgid "Thanks for registering for an account on Twenty! Before we get started, we just need to confirm that this is you. Click above to verify your email address."
msgstr ""
#. js-lingui-explicit-id
@@ -130,12 +130,12 @@ msgstr ""
#. js-lingui-explicit-id
#: src/emails/password-reset-link.email.tsx
msgid "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:"
msgid "Reset"
msgstr ""
#. js-lingui-explicit-id
#: src/emails/password-reset-link.email.tsx
msgid "Reset"
msgid "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:"
msgstr ""
#. js-lingui-explicit-id
@@ -223,11 +223,6 @@ msgstr ""
msgid "San Francisco / Paris"
msgstr ""
#. js-lingui-explicit-id
#: src/emails/send-email-verification-link.email.tsx
#~ msgid "Thanks for registering for an account on Twenty! Before we get started, we just need to confirm that this is you. Click above to verify your email address."
#~ msgstr ""
#. js-lingui-id: 4WPI3S
#: src/emails/send-invite-link.email.tsx
#~ msgid "Accept invite"
+4 -9
View File
@@ -95,12 +95,12 @@ msgstr ""
#. js-lingui-explicit-id
#: src/emails/send-email-verification-link.email.tsx
msgid "Thanks for registering for an account on Twenty! Before we get started, we just need to confirm that this is you. Click below to verify your email address."
msgid "Verify Email"
msgstr ""
#. js-lingui-explicit-id
#: src/emails/send-email-verification-link.email.tsx
msgid "Verify Email"
msgid "Thanks for registering for an account on Twenty! Before we get started, we just need to confirm that this is you. Click above to verify your email address."
msgstr ""
#. js-lingui-explicit-id
@@ -130,12 +130,12 @@ msgstr ""
#. js-lingui-explicit-id
#: src/emails/password-reset-link.email.tsx
msgid "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:"
msgid "Reset"
msgstr ""
#. js-lingui-explicit-id
#: src/emails/password-reset-link.email.tsx
msgid "Reset"
msgid "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:"
msgstr ""
#. js-lingui-explicit-id
@@ -223,11 +223,6 @@ msgstr ""
msgid "San Francisco / Paris"
msgstr ""
#. js-lingui-explicit-id
#: src/emails/send-email-verification-link.email.tsx
#~ msgid "Thanks for registering for an account on Twenty! Before we get started, we just need to confirm that this is you. Click above to verify your email address."
#~ msgstr ""
#. js-lingui-id: 4WPI3S
#: src/emails/send-invite-link.email.tsx
#~ msgid "Accept invite"
+4 -9
View File
@@ -95,12 +95,12 @@ msgstr ""
#. js-lingui-explicit-id
#: src/emails/send-email-verification-link.email.tsx
msgid "Thanks for registering for an account on Twenty! Before we get started, we just need to confirm that this is you. Click below to verify your email address."
msgid "Verify Email"
msgstr ""
#. js-lingui-explicit-id
#: src/emails/send-email-verification-link.email.tsx
msgid "Verify Email"
msgid "Thanks for registering for an account on Twenty! Before we get started, we just need to confirm that this is you. Click above to verify your email address."
msgstr ""
#. js-lingui-explicit-id
@@ -130,12 +130,12 @@ msgstr ""
#. js-lingui-explicit-id
#: src/emails/password-reset-link.email.tsx
msgid "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:"
msgid "Reset"
msgstr ""
#. js-lingui-explicit-id
#: src/emails/password-reset-link.email.tsx
msgid "Reset"
msgid "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:"
msgstr ""
#. js-lingui-explicit-id
@@ -223,11 +223,6 @@ msgstr ""
msgid "San Francisco / Paris"
msgstr ""
#. js-lingui-explicit-id
#: src/emails/send-email-verification-link.email.tsx
#~ msgid "Thanks for registering for an account on Twenty! Before we get started, we just need to confirm that this is you. Click above to verify your email address."
#~ msgstr ""
#. js-lingui-id: 4WPI3S
#: src/emails/send-invite-link.email.tsx
#~ msgid "Accept invite"
+4 -9
View File
@@ -95,12 +95,12 @@ msgstr ""
#. js-lingui-explicit-id
#: src/emails/send-email-verification-link.email.tsx
msgid "Thanks for registering for an account on Twenty! Before we get started, we just need to confirm that this is you. Click below to verify your email address."
msgid "Verify Email"
msgstr ""
#. js-lingui-explicit-id
#: src/emails/send-email-verification-link.email.tsx
msgid "Verify Email"
msgid "Thanks for registering for an account on Twenty! Before we get started, we just need to confirm that this is you. Click above to verify your email address."
msgstr ""
#. js-lingui-explicit-id
@@ -130,12 +130,12 @@ msgstr ""
#. js-lingui-explicit-id
#: src/emails/password-reset-link.email.tsx
msgid "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:"
msgid "Reset"
msgstr ""
#. js-lingui-explicit-id
#: src/emails/password-reset-link.email.tsx
msgid "Reset"
msgid "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:"
msgstr ""
#. js-lingui-explicit-id
@@ -223,11 +223,6 @@ msgstr ""
msgid "San Francisco / Paris"
msgstr ""
#. js-lingui-explicit-id
#: src/emails/send-email-verification-link.email.tsx
#~ msgid "Thanks for registering for an account on Twenty! Before we get started, we just need to confirm that this is you. Click above to verify your email address."
#~ msgstr ""
#. js-lingui-id: 4WPI3S
#: src/emails/send-invite-link.email.tsx
#~ msgid "Accept invite"
+4 -9
View File
@@ -95,12 +95,12 @@ msgstr ""
#. js-lingui-explicit-id
#: src/emails/send-email-verification-link.email.tsx
msgid "Thanks for registering for an account on Twenty! Before we get started, we just need to confirm that this is you. Click below to verify your email address."
msgid "Verify Email"
msgstr ""
#. js-lingui-explicit-id
#: src/emails/send-email-verification-link.email.tsx
msgid "Verify Email"
msgid "Thanks for registering for an account on Twenty! Before we get started, we just need to confirm that this is you. Click above to verify your email address."
msgstr ""
#. js-lingui-explicit-id
@@ -130,12 +130,12 @@ msgstr ""
#. js-lingui-explicit-id
#: src/emails/password-reset-link.email.tsx
msgid "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:"
msgid "Reset"
msgstr ""
#. js-lingui-explicit-id
#: src/emails/password-reset-link.email.tsx
msgid "Reset"
msgid "This link is only valid for the next {duration}. If the link does not work, you can use the login verification link directly:"
msgstr ""
#. js-lingui-explicit-id
@@ -223,11 +223,6 @@ msgstr ""
msgid "San Francisco / Paris"
msgstr ""
#. js-lingui-explicit-id
#: src/emails/send-email-verification-link.email.tsx
#~ msgid "Thanks for registering for an account on Twenty! Before we get started, we just need to confirm that this is you. Click above to verify your email address."
#~ msgstr ""
#. js-lingui-id: 4WPI3S
#: src/emails/send-invite-link.email.tsx
#~ msgid "Accept invite"

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