diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index 73816f1..0000000 --- a/.editorconfig +++ /dev/null @@ -1,21 +0,0 @@ -# Root editor config file -root = true - -# Common settings -[*] -end_of_line = lf -insert_final_newline = true -trim_trailing_whitespace = true -charset = utf-8 - -# python, js indentation settings -[{*.py,*.js,*.vue,*.css,*.scss,*.html}] -indent_style = tab -indent_size = 4 -max_line_length = 99 - -# JSON files - mostly doctype schema files -[{*.json}] -insert_final_newline = false -indent_style = space -indent_size = 1 diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index 523328a..0000000 --- a/.eslintrc +++ /dev/null @@ -1,124 +0,0 @@ -{ - "env": { - "browser": true, - "node": true, - "es2022": true - }, - "parserOptions": { - "sourceType": "module" - }, - "extends": "eslint:recommended", - "rules": { - "indent": "off", - "brace-style": "off", - "no-mixed-spaces-and-tabs": "off", - "no-useless-escape": "off", - "space-unary-ops": ["error", { "words": true }], - "linebreak-style": "off", - "quotes": ["off"], - "semi": "off", - "camelcase": "off", - "no-unused-vars": "off", - "no-console": ["warn"], - "no-extra-boolean-cast": ["off"], - "no-control-regex": ["off"], - }, - "root": true, - "globals": { - "frappe": true, - "Vue": true, - "SetVueGlobals": true, - "__": true, - "repl": true, - "Class": true, - "locals": true, - "cint": true, - "cstr": true, - "cur_frm": true, - "cur_dialog": true, - "cur_page": true, - "cur_list": true, - "cur_tree": true, - "msg_dialog": true, - "is_null": true, - "in_list": true, - "has_common": true, - "has_words": true, - "validate_email": true, - "open_web_template_values_editor": true, - "validate_name": true, - "validate_phone": true, - "validate_url": true, - "get_number_format": true, - "format_number": true, - "format_currency": true, - "comment_when": true, - "open_url_post": true, - "toTitle": true, - "lstrip": true, - "rstrip": true, - "strip": true, - "strip_html": true, - "replace_all": true, - "flt": true, - "precision": true, - "CREATE": true, - "AMEND": true, - "CANCEL": true, - "copy_dict": true, - "get_number_format_info": true, - "strip_number_groups": true, - "print_table": true, - "Layout": true, - "web_form_settings": true, - "$c": true, - "$a": true, - "$i": true, - "$bg": true, - "$y": true, - "$c_obj": true, - "refresh_many": true, - "refresh_field": true, - "toggle_field": true, - "get_field_obj": true, - "get_query_params": true, - "unhide_field": true, - "hide_field": true, - "set_field_options": true, - "getCookie": true, - "getCookies": true, - "get_url_arg": true, - "md5": true, - "$": true, - "jQuery": true, - "moment": true, - "hljs": true, - "Awesomplete": true, - "Sortable": true, - "gemoji": true, - "Showdown": true, - "Taggle": true, - "Gantt": true, - "Slick": true, - "Webcam": true, - "PhotoSwipe": true, - "PhotoSwipeUI_Default": true, - "io": true, - "JsBarcode": true, - "L": true, - "Chart": true, - "DataTable": true, - "Cypress": true, - "cy": true, - "it": true, - "describe": true, - "expect": true, - "context": true, - "before": true, - "beforeEach": true, - "after": true, - "qz": true, - "localforage": true, - "extend_cscript": true - } -} diff --git a/.gitattributes b/.gitattributes index acef6c5..e8abd5f 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,8 @@ -*.qmd linguist-language=Markdown linguist-detectable=true +# Quarto source files in docs should be treated as source files, +# not as documentation-only files and without assigning a Markdown label. +docs/**/*.qmd linguist-detectable=true +docs/**/*.qmd linguist-documentation=false + +# Apply the same rule to any other .qmd in the repo. +*.qmd linguist-detectable=true +*.qmd linguist-documentation=false diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml new file mode 100644 index 0000000..0ec483f --- /dev/null +++ b/.github/workflows/cd.yml @@ -0,0 +1,42 @@ +name: CD + +on: + push: + branches: + - main + pull_request: + types: [labeled] + +permissions: + contents: read + +env: + TWENTY_DEPLOY_URL: http://localhost:2020 + +concurrency: + group: cd-${{ github.ref }} + cancel-in-progress: true + +jobs: + deploy-and-install: + if: >- + github.event_name == 'push' || + (github.event_name == 'pull_request' && github.event.label.name == 'deploy') + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} + + - name: Deploy + uses: twentyhq/twenty/.github/actions/deploy-twenty-app@main + with: + api-url: ${{ env.TWENTY_DEPLOY_URL }} + api-key: ${{ secrets.TWENTY_DEPLOY_API_KEY }} + + - name: Install + uses: twentyhq/twenty/.github/actions/install-twenty-app@main + with: + api-url: ${{ env.TWENTY_DEPLOY_URL }} + api-key: ${{ secrets.TWENTY_DEPLOY_API_KEY }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1877a9e..95e85ef 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,103 +3,55 @@ name: CI on: push: branches: - - version-16 - pull_request: + - main + pull_request: {} + +permissions: + contents: read + +env: + TWENTY_VERSION: latest concurrency: - group: version-16-aura-${{ github.event.number }} + group: ci-${{ github.ref }} cancel-in-progress: true jobs: - tests: + test: runs-on: ubuntu-latest - strategy: - fail-fast: false - name: Server - - services: - redis-cache: - image: redis:alpine - ports: - - 13000:6379 - redis-queue: - image: redis:alpine - ports: - - 11000:6379 - mariadb: - image: mariadb:11.8 - env: - MYSQL_ROOT_PASSWORD: root - ports: - - 3306:3306 - options: --health-cmd="mariadb-admin ping" --health-interval=5s --health-timeout=2s --health-retries=3 - steps: - - name: Clone - uses: actions/checkout@v6 + - name: Checkout + uses: actions/checkout@v4 - - name: Find tests - run: | - echo "Finding tests" - grep -rn "def test" > /dev/null - - - name: Setup Python - uses: actions/setup-python@v6 + - name: Spawn Twenty test instance + id: twenty + uses: twentyhq/twenty/.github/actions/spawn-twenty-app-dev-test@main with: - python-version: '3.14' + twenty-version: ${{ env.TWENTY_VERSION }} - - name: Setup Node - uses: actions/setup-node@v6 - with: - node-version: 24 - check-latest: true + - name: Enable Corepack + run: corepack enable - - name: Cache pip - uses: actions/cache@v4 + - name: Setup Node.js + uses: actions/setup-node@v4 with: - path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml', '**/setup.py', '**/setup.cfg') }} - restore-keys: | - ${{ runner.os }}-pip- - ${{ runner.os }}- + node-version-file: '.nvmrc' + cache: yarn - - name: Get yarn cache directory path - id: yarn-cache-dir-path - run: 'echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT' + - name: Install dependencies + run: yarn install --immutable - - uses: actions/cache@v4 - id: yarn-cache - with: - path: ${{ steps.yarn-cache-dir-path.outputs.dir }} - key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} - restore-keys: | - ${{ runner.os }}-yarn- + - name: Lint + run: yarn lint - - name: Install MariaDB Client - run: | - sudo apt update - sudo apt-get install mariadb-client + - name: Typecheck + run: yarn typecheck - - name: Setup - run: | - pip install frappe-bench - bench init --skip-redis-config-generation --skip-assets --python "$(which python)" ~/frappe-bench - - - name: Install - working-directory: /home/runner/frappe-bench - run: | - bench get-app aura $GITHUB_WORKSPACE - bench setup requirements --dev - bench new-site --db-root-password root --admin-password admin test_site - bench --site test_site install-app aura - bench build - env: - CI: 'Yes' + - name: Unit tests + run: yarn test:unit - - name: Run Tests - working-directory: /home/runner/frappe-bench - run: | - bench --site test_site set-config allow_tests true - bench --site test_site run-tests --app aura + - name: Run integration tests + run: yarn test env: - TYPE: server + TWENTY_API_URL: ${{ steps.twenty.outputs.server-url }} + TWENTY_API_KEY: ${{ steps.twenty.outputs.api-key }} diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml deleted file mode 100644 index 3c810e5..0000000 --- a/.github/workflows/linter.yml +++ /dev/null @@ -1,60 +0,0 @@ -name: Linters - -on: - pull_request: - workflow_dispatch: - -permissions: - contents: read - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - linter: - name: 'Frappe Linter' - runs-on: ubuntu-latest - if: github.event_name == 'pull_request' - - steps: - - uses: actions/checkout@v6 - - uses: actions/setup-python@v6 - with: - python-version: '3.14' - cache: pip - - uses: pre-commit/action@v3.0.0 - - - name: Download Semgrep rules - run: git clone --depth 1 https://github.com/frappe/semgrep-rules.git frappe-semgrep-rules - - - name: Run Semgrep rules - run: | - pip install semgrep - semgrep ci --config ./frappe-semgrep-rules/rules --config r/python.lang.correctness - - deps-vulnerable-check: - name: 'Vulnerable Dependency Check' - runs-on: ubuntu-latest - - steps: - - uses: actions/setup-python@v6 - with: - python-version: '3.14' - - - uses: actions/checkout@v6 - - - name: Cache pip - uses: actions/cache@v5 - with: - path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml', '**/setup.py') }} - restore-keys: | - ${{ runner.os }}-pip- - ${{ runner.os }}- - - - name: Install and run pip-audit - run: | - pip install pip-audit - cd ${GITHUB_WORKSPACE} - pip-audit --desc on . diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..f6b564b --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,52 @@ +# Publishes the app to npm with provenance (npm trusted publishing). +# +# One-time setup: +# 1. On npmjs.com open your package > Settings > Trusted Publisher and register +# this repository and this workflow file (publish.yml). +# See https://docs.npmjs.com/trusted-publishers +# 2. Push a version tag (e.g. `git tag v1.0.0 && git push --tags`) or run the +# workflow manually from the Actions tab. +# +# Publishing with provenance certifies which GitHub repository the package is +# built from. This is also how you claim ownership of your app in a Twenty +# marketplace: the claim flow verifies you own the GitHub account or +# organization the provenance points to. +name: Publish + +on: + push: + tags: + - 'v*' + workflow_dispatch: + +permissions: + contents: read + # Required for npm trusted publishing (OIDC provenance). + id-token: write + +jobs: + publish: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 24 + registry-url: https://registry.npmjs.org + + - name: Update npm + # Trusted publishing requires npm 11.5.1 or later. + run: npm install -g npm@latest + + - name: Install dependencies + run: yarn install --immutable + + - name: Publish to npm + # Uncomment if publishing from a private source repo: npm rejects + # OIDC provenance for private repos + # env: + # TWENTY_APP_PUBLISH_DISABLE_PROVENANCE: 'true' + run: yarn twenty app:publish diff --git a/.gitignore b/.gitignore index 746a5ed..6ae857f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,58 +1,46 @@ -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class -*.pyc -*.py~ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. -# Distribution / packaging -.Python -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -*.egg-info/ -.installed.cfg -*.egg -tags -MANIFEST +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/sdks +!.yarn/versions -# Environments -.env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ +# codegen +generated -# Dependency directories -node_modules/ -jspm_packages/ +# testing +/coverage -# IDEs and editors -.vscode/ -.vs/ -.idea/ -.kdev4/ -*.kdev4 -*.DS_Store -*.swp -*.comp.js -.wnf-lang-status -*debug.log +# dev +/dist/ -# Helix Editor -.helix/ +.twenty -# Aider AI Chat -.aider* +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files (can opt-in for committing if needed) +.env* + +# typescript +*.tsbuildinfo +*.d.ts # Quarto docs docs/.quarto/ diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..341cb50 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +24.5.0 diff --git a/.oxlintrc.json b/.oxlintrc.json new file mode 100644 index 0000000..369b9cf --- /dev/null +++ b/.oxlintrc.json @@ -0,0 +1,37 @@ +{ + "$schema": "./node_modules/oxlint/configuration_schema.json", + "plugins": ["typescript"], + "categories": { + "correctness": "off" + }, + "ignorePatterns": ["node_modules", "dist"], + "rules": { + "no-unused-vars": "off", + + "typescript/no-unused-vars": [ + "warn", + { + "argsIgnorePattern": "^_" + } + ], + "typescript/no-explicit-any": "off" + }, + "overrides": [ + { + "files": ["**/*.logic-function.ts", "**/logic-functions/**/*.ts"], + "rules": { + "no-restricted-imports": [ + "error", + { + "patterns": [ + { + "group": ["twenty-shared", "twenty-shared/*"], + "message": "Logic functions must not import from twenty-shared directly. Import runtime types and helpers from `twenty-sdk/logic-function` instead so the logic-function bundle stays minimal." + } + ] + } + ] + } + } + ] +} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml deleted file mode 100644 index 8517366..0000000 --- a/.pre-commit-config.yaml +++ /dev/null @@ -1,69 +0,0 @@ -exclude: 'node_modules|.git' -default_stages: [pre-commit] -fail_fast: false - - -repos: - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v6.0.0 - hooks: - - id: trailing-whitespace - files: "aura.*" - exclude: ".*json$|.*txt$|.*csv|.*md|.*svg" - - id: check-merge-conflict - - id: check-ast - - id: check-json - - id: check-toml - - id: check-yaml - - id: debug-statements - - - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.14.10 - hooks: - - id: ruff - name: "Run ruff import sorter" - args: ["--select=I", "--fix"] - - - id: ruff - name: "Run ruff linter" - - - id: ruff-format - name: "Run ruff formatter" - - - repo: https://github.com/pre-commit/mirrors-prettier - rev: v2.7.1 - hooks: - - id: prettier - types_or: [javascript, vue, scss] - # Ignore any files that might contain jinja / bundles - exclude: | - (?x)^( - aura/public/dist/.*| - .*node_modules.*| - .*boilerplate.*| - aura/templates/includes/.*| - aura/public/js/lib/.* - )$ - - - - repo: https://github.com/pre-commit/mirrors-eslint - rev: v8.44.0 - hooks: - - id: eslint - types_or: [javascript] - args: ['--quiet'] - # Ignore any files that might contain jinja / bundles - exclude: | - (?x)^( - aura/public/dist/.*| - cypress/.*| - .*node_modules.*| - .*boilerplate.*| - aura/templates/includes/.*| - aura/public/js/lib/.* - )$ - -ci: - autoupdate_schedule: weekly - skip: [] - submodules: false diff --git a/.yarnrc.yml b/.yarnrc.yml new file mode 100644 index 0000000..3186f3f --- /dev/null +++ b/.yarnrc.yml @@ -0,0 +1 @@ +nodeLinker: node-modules diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..7629e26 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,70 @@ +## Base documentation + +- Getting started: + - https://docs.twenty.com/developers/extend/apps/getting-started/quick-start.md + - https://docs.twenty.com/developers/extend/apps/getting-started/concepts.md + - https://docs.twenty.com/developers/extend/apps/getting-started/project-structure.md + - https://docs.twenty.com/developers/extend/apps/getting-started/local-server.md + - https://docs.twenty.com/developers/extend/apps/getting-started/scaffolding.md + - https://docs.twenty.com/developers/extend/apps/getting-started/troubleshooting.md +- Config: + - https://docs.twenty.com/developers/extend/apps/config/overview.md + - https://docs.twenty.com/developers/extend/apps/config/application.md + - https://docs.twenty.com/developers/extend/apps/config/roles.md + - https://docs.twenty.com/developers/extend/apps/config/install-hooks.md + - https://docs.twenty.com/developers/extend/apps/config/public-assets.md +- Data: + - https://docs.twenty.com/developers/extend/apps/data/overview.md + - https://docs.twenty.com/developers/extend/apps/data/objects.md + - https://docs.twenty.com/developers/extend/apps/data/extending-objects.md + - https://docs.twenty.com/developers/extend/apps/data/relations.md +- Logic: + - https://docs.twenty.com/developers/extend/apps/logic/overview.md + - https://docs.twenty.com/developers/extend/apps/logic/logic-functions.md + - https://docs.twenty.com/developers/extend/apps/logic/skills-and-agents.md + - https://docs.twenty.com/developers/extend/apps/logic/connections.md +- Layout: + - https://docs.twenty.com/developers/extend/apps/layout/overview.md + - https://docs.twenty.com/developers/extend/apps/layout/views.md + - https://docs.twenty.com/developers/extend/apps/layout/navigation-menu-items.md + - https://docs.twenty.com/developers/extend/apps/layout/page-layouts.md + - https://docs.twenty.com/developers/extend/apps/layout/front-components.md + - https://docs.twenty.com/developers/extend/apps/layout/command-menu-items.md +- Operations: + - https://docs.twenty.com/developers/extend/apps/operations/overview.md + - https://docs.twenty.com/developers/extend/apps/operations/cli.md + - https://docs.twenty.com/developers/extend/apps/operations/testing.md + - https://docs.twenty.com/developers/extend/apps/operations/publishing.md +- Rich app example: https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/examples/postcard + +## UUID requirement + +- All generated UUIDs must be valid UUID v4. + +## Common Pitfalls + +- Creating a view without a navigationMenuItem associated. This will make the view available on the left sidebar. +- Creating a front-end component that has a scroll instead of being responsive to its fixed widget height and width, unless it is specifically meant to be used in a canvas tab. + +## Best practice + +It's highly recommended to create new app entities using `yarn twenty dev:add`. These are the options: + +| Entity type | Command | Generated file | +| -------------------- | ---------------------------------------- | ------------------------------------- | +| Object | `yarn twenty dev:add object` | `src/objects/.ts` | +| Field | `yarn twenty dev:add field` | `src/fields/.ts` | +| Logic function | `yarn twenty dev:add logicFunction` | `src/logic-functions/.ts` | +| Front component | `yarn twenty dev:add frontComponent` | `src/front-components/.tsx` | +| Role | `yarn twenty dev:add role` | `src/roles/.ts` | +| Skill | `yarn twenty dev:add skill` | `src/skills/.ts` | +| Agent | `yarn twenty dev:add agent` | `src/agents/.ts` | +| View | `yarn twenty dev:add view` | `src/views/.ts` | +| Navigation menu item | `yarn twenty dev:add navigationMenuItem` | `src/navigation-menu-items/.ts` | +| Page layout | `yarn twenty dev:add pageLayout` | `src/page-layouts/.ts` | +| Page layout tab | `yarn twenty dev:add pageLayoutTab` | `src/page-layout-tabs/.ts` | +| Command menu item | `yarn twenty dev:add commandMenuItem` | `src/command-menu-items/.ts` | +| View field | `yarn twenty dev:add viewField` | `src/view-fields/.ts` | +| Connection provider | `yarn twenty dev:add connectionProvider` | `src/connection-providers/.ts` | + +This helps automatically generate required IDs etc. diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..d88f1af --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,7 @@ +# Changelog + +All notable changes to this application are documented in this file. + +## 0.1.0 + +- Initial application scaffolded with [`create-twenty-app`](https://www.npmjs.com/package/create-twenty-app) diff --git a/CLAUDE.md b/CLAUDE.md index c2bd916..7629e26 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,405 +1,70 @@ -# CLAUDE.md - -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. - -## 1. Project overview - -**AURA** is the internal partner and sponsorship management system for **Hyperloop UPV**. - -AURA is built on: - -* Frappe Framework -* Frappe CRM -* A custom Frappe application named `aura` - -The primary purpose of AURA is **institutional memory and continuity between yearly -generations of the Hyperloop UPV Partners team**. AURA is not a generic sales CRM — its main -goal is that a new partner agent can open a company and understand its complete relationship -with Hyperloop UPV without needing to search through old spreadsheets, emails, or ask previous -team members. - -The key UX principle is: - -> The Organization page must behave as the complete historical file of a company. - -The full domain model, sponsorship workflow, and data shapes AURA is built around are in -**[`docs/aura-domain-spec.md`](docs/aura-domain-spec.md)** — read it before implementing any -DocType, field, or workflow logic. This file (CLAUDE.md) holds the operating rules; the spec -file holds the business concepts. - ---- - -## 2. Current repository state - -This repository is currently at the **planning stage** — there is no Frappe bench or `apps/` -directory yet (no `apps/frappe`, `apps/crm`, or `apps/aura`). Only this CLAUDE.md and the -`docs/` folder exist. - -Once the bench and `aura` app are scaffolded, add here: - -* how to start the bench (`bench start`); -* how to run the AURA app's tests (`bench --site run-tests --app aura`); -* how to run migrations (`bench --site migrate`); -* any linting/formatting commands adopted for the app. - -Do not assume these commands exist yet — verify the bench is actually present before running -them. - -### `docs/` folder - -`docs/` is a **Quarto project** (`_quarto.yml`, `template.qmd`, `headfoot.tex`, logo assets) -used to generate PDF **progress reports for the project owner's boss** about AURA's -development status. It is unrelated to the AURA application itself — don't treat its contents -as app code or app documentation. - ---- - -## 3. Architecture - -AURA must extend Frappe CRM rather than replace it. - -The architecture is: - -```text -Frappe Framework -├── Frappe CRM -└── AURA -``` - -The installed applications should conceptually remain: - -```text -apps/ -├── frappe/ -├── crm/ -└── aura/ -``` - -### Important rule - -**Never modify Frappe CRM core files unless explicitly requested.** - -Do not implement AURA features directly inside: - -```text -apps/crm/ -``` - -AURA-specific behavior must live in: - -```text -apps/aura/ -``` - -Use supported Frappe extension mechanisms whenever possible: - -* custom DocTypes; -* Custom Fields; -* Property Setters; -* fixtures; -* hooks; -* Python controllers; -* scheduler events; -* permissions; -* supported frontend extension points. - -Avoid forks or direct modifications of Frappe CRM unless there is no reasonable alternative and -the trade-off has been explicitly discussed. - ---- - -## 4. Customization strategy - -AURA must be maintainable across Frappe CRM upgrades. - -When modifying existing CRM objects: - -### If an existing object only needs more information - -Use: - -```text -Custom Field -``` - -Example: - -```text -CRM Deal -+ Season -+ Partner Tier -``` - -### If an entirely new business concept is required - -Use: - -```text -Custom DocType -``` - -Example: - -```text -Season -Partner Assignment -Contribution -Handover -``` - -### If an existing standard field is irrelevant - -Prefer hiding it rather than deleting it. - -Examples that may be unnecessary for AURA include generic CRM fields such as: - -```text -Territory -Annual Revenue -``` - -Use supported customization mechanisms such as Property Setters / Customize Form. - -Do not remove standard database fields from Frappe CRM merely because the current UI does not -need them. - ---- - -## 5. Configuration and source control - -Use this mental model: - -```text -AURA repository -= how AURA works - -Database -= what happened in AURA -``` - -### Must be reproducible from the repository - -Examples: - -* custom DocTypes; -* Python logic; -* hooks; -* scheduler logic; -* Custom Fields; -* Property Setters; -* required workflows; -* required sponsorship statuses; -* roles and permissions when appropriate; -* fixtures; -* global branding assets; -* CSS/SCSS; -* stable configuration required on every AURA installation. - -### Must NOT be committed as application configuration - -Examples: - -* real organizations; -* contacts; -* emails; -* actual tasks; -* agreements; -* production partner data; -* passwords; -* API keys; -* OAuth tokens; -* encryption keys; -* database passwords. - -If a configuration change made through the UI is required to reproduce AURA on a clean site, -export/version it appropriately. - -Use Frappe mechanisms such as: - -```text -Export Customizations -fixtures -hooks.py -developer_mode -``` - -where appropriate. - ---- - -## 6. Data integrity principles - -Prefer structured data over arbitrary strings when the information has known semantics. - -Examples: - -Prefer: - -```text -Season → Link to Season -``` - -over: - -```text -Season → "26-27" -``` - -Prefer: - -```text -Partner Tier → Select -``` - -over unrestricted text. - -Prefer: - -```text -Organization → Link to CRM Organization -``` - -over storing `"DHL"` as text. - -Avoid duplicating data across Organization and Deal unless there is a deliberate -denormalization strategy. - -Historical values must remain historically correct. Do not overwrite historical sponsorship -information merely because the current season changes. - ---- - -## 7. UI philosophy - -AURA should reduce cognitive load. - -Do not preserve generic CRM fields merely because they exist. The user interface should -emphasize information relevant to Hyperloop UPV Partners. Fields that do not provide value -should normally be hidden rather than deleted from the underlying CRM. - -Do not build a custom frontend merely for cosmetic reasons. - -Prefer: - -1. standard Frappe CRM UI; -2. standard Desk UI; -3. supported customization; -4. small targeted extensions. - -Only implement substantial custom frontend code when the workflow genuinely requires it. - ---- - -## 8. Branding - -The product name is: - -```text -AURA -``` - -Users should think of the system as AURA, not simply "the CRM". - -Global branding should eventually include: AURA name, logo, favicon, common visual identity, -global colors where maintainable. Branding that defines AURA should preferably be reproducible -from the `aura` repository. - -Do not prioritize deep visual customization over correct workflows and data integrity. - ---- - -## 9. Development principles - -When implementing a feature: - -1. Check whether Frappe or Frappe CRM already provides it. -2. Prefer configuration over custom code where configuration is sufficient. -3. Prefer AURA app code over modifying CRM core. -4. Preserve historical information. -5. Keep the solution reproducible from source control. -6. Avoid unnecessary frontend development. -7. Avoid overengineering. -8. Design for future team members who did not build the system. -9. Explain non-obvious architectural decisions in code comments or documentation. -10. Keep migration and future Frappe upgrades in mind. - -## 10. Coding principles - -Prefer clear, conventional Frappe code. Use descriptive names. Avoid clever abstractions unless -they reduce meaningful duplication. Keep business logic out of UI code whenever possible. Use -Python for backend business logic. When behavior becomes stable and important, prefer -application code over ad-hoc Server Scripts. - -Before introducing a new dependency, explain why existing Frappe functionality is insufficient. - -Never put secrets in source code. Never commit production data. - ---- - -## 11. Before making structural changes - -For significant architectural changes, first inspect the existing implementation. Do not -assume a DocType, field or configuration does not already exist. - -Before creating something new, check for: - -* existing Frappe CRM functionality; -* existing AURA DocTypes; -* existing Custom Fields; -* existing hooks; -* fixtures; -* existing naming conventions. - -Avoid creating duplicate concepts. - ---- - -## 12. Current implementation priority - -The current priority is building the core sponsorship data model before advanced integrations. - -Preferred order: - -```text -1. Understand existing Frappe CRM entities -2. Organizations -3. Contacts -4. Deals -5. Season -6. Sponsorship status model -7. Partner Tier -8. Historical Partner Assignment -9. Organization historical view -10. Tasks and activities -11. Handover -12. Contributions -13. Email integration -14. Drive integration -15. Automations -16. Reporting -17. Branding polish -``` - -Do not jump directly to AI features or complex integrations while the underlying relationship -history is incomplete. See `docs/aura-domain-spec.md` for what each of these concepts means in -detail, and the acceptance criterion that defines when AURA is "done" for a given company. - ---- - -## 13. Working with the user - -The project owner is comfortable programming but is still learning Frappe and CRM concepts. - -When discussing implementation: - -* explain Frappe-specific concepts when first used; -* do not assume familiarity with CRM terminology; -* distinguish clearly between standard Frappe CRM behavior and AURA-specific behavior; -* explain why a proposed data model is appropriate before introducing significant complexity; -* favor incremental implementation; -* when there are multiple viable approaches, explain the trade-offs and recommend one. - -Do not redesign already-decided architecture without a concrete technical reason. - -When unsure whether something should be standard Frappe configuration or custom AURA code, -prefer the most maintainable solution and explain the choice. +## Base documentation + +- Getting started: + - https://docs.twenty.com/developers/extend/apps/getting-started/quick-start.md + - https://docs.twenty.com/developers/extend/apps/getting-started/concepts.md + - https://docs.twenty.com/developers/extend/apps/getting-started/project-structure.md + - https://docs.twenty.com/developers/extend/apps/getting-started/local-server.md + - https://docs.twenty.com/developers/extend/apps/getting-started/scaffolding.md + - https://docs.twenty.com/developers/extend/apps/getting-started/troubleshooting.md +- Config: + - https://docs.twenty.com/developers/extend/apps/config/overview.md + - https://docs.twenty.com/developers/extend/apps/config/application.md + - https://docs.twenty.com/developers/extend/apps/config/roles.md + - https://docs.twenty.com/developers/extend/apps/config/install-hooks.md + - https://docs.twenty.com/developers/extend/apps/config/public-assets.md +- Data: + - https://docs.twenty.com/developers/extend/apps/data/overview.md + - https://docs.twenty.com/developers/extend/apps/data/objects.md + - https://docs.twenty.com/developers/extend/apps/data/extending-objects.md + - https://docs.twenty.com/developers/extend/apps/data/relations.md +- Logic: + - https://docs.twenty.com/developers/extend/apps/logic/overview.md + - https://docs.twenty.com/developers/extend/apps/logic/logic-functions.md + - https://docs.twenty.com/developers/extend/apps/logic/skills-and-agents.md + - https://docs.twenty.com/developers/extend/apps/logic/connections.md +- Layout: + - https://docs.twenty.com/developers/extend/apps/layout/overview.md + - https://docs.twenty.com/developers/extend/apps/layout/views.md + - https://docs.twenty.com/developers/extend/apps/layout/navigation-menu-items.md + - https://docs.twenty.com/developers/extend/apps/layout/page-layouts.md + - https://docs.twenty.com/developers/extend/apps/layout/front-components.md + - https://docs.twenty.com/developers/extend/apps/layout/command-menu-items.md +- Operations: + - https://docs.twenty.com/developers/extend/apps/operations/overview.md + - https://docs.twenty.com/developers/extend/apps/operations/cli.md + - https://docs.twenty.com/developers/extend/apps/operations/testing.md + - https://docs.twenty.com/developers/extend/apps/operations/publishing.md +- Rich app example: https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/examples/postcard + +## UUID requirement + +- All generated UUIDs must be valid UUID v4. + +## Common Pitfalls + +- Creating a view without a navigationMenuItem associated. This will make the view available on the left sidebar. +- Creating a front-end component that has a scroll instead of being responsive to its fixed widget height and width, unless it is specifically meant to be used in a canvas tab. + +## Best practice + +It's highly recommended to create new app entities using `yarn twenty dev:add`. These are the options: + +| Entity type | Command | Generated file | +| -------------------- | ---------------------------------------- | ------------------------------------- | +| Object | `yarn twenty dev:add object` | `src/objects/.ts` | +| Field | `yarn twenty dev:add field` | `src/fields/.ts` | +| Logic function | `yarn twenty dev:add logicFunction` | `src/logic-functions/.ts` | +| Front component | `yarn twenty dev:add frontComponent` | `src/front-components/.tsx` | +| Role | `yarn twenty dev:add role` | `src/roles/.ts` | +| Skill | `yarn twenty dev:add skill` | `src/skills/.ts` | +| Agent | `yarn twenty dev:add agent` | `src/agents/.ts` | +| View | `yarn twenty dev:add view` | `src/views/.ts` | +| Navigation menu item | `yarn twenty dev:add navigationMenuItem` | `src/navigation-menu-items/.ts` | +| Page layout | `yarn twenty dev:add pageLayout` | `src/page-layouts/.ts` | +| Page layout tab | `yarn twenty dev:add pageLayoutTab` | `src/page-layout-tabs/.ts` | +| Command menu item | `yarn twenty dev:add commandMenuItem` | `src/command-menu-items/.ts` | +| View field | `yarn twenty dev:add viewField` | `src/view-fields/.ts` | +| Connection provider | `yarn twenty dev:add connectionProvider` | `src/connection-providers/.ts` | + +This helps automatically generate required IDs etc. diff --git a/README.md b/README.md index 16cfec4..0b5463e 100644 --- a/README.md +++ b/README.md @@ -1,40 +1,34 @@ -### AURA +# My Twenty App -Advanced Unified Relations Application +Describe your app in one or two sentences. -### Installation +## Features -You can install this app using the [bench](https://github.com/frappe/bench) CLI: +List the top things your app does, for example: -```bash -cd $PATH_TO_YOUR_BENCH -bench get-app $URL_OF_THIS_REPO --branch version-16 -bench install-app aura -``` +- Feature one +- Feature two +- Feature three -### Contributing +## Getting started -This app uses `pre-commit` for code formatting and linting. Please [install pre-commit](https://pre-commit.com/#installation) and enable it for this repository: +Setup instructions live in [SETUP.md](SETUP.md). -```bash -cd apps/aura -pre-commit install -``` +## Publishing -Pre-commit is configured to use the following tools for checking and formatting your code: +The `Publish` workflow (`.github/workflows/publish.yml`) publishes the app to npm with provenance using [npm trusted publishing](https://docs.npmjs.com/trusted-publishers). To publish: -- ruff -- eslint -- prettier -- pyupgrade -### CI +1. On npmjs.com register this repository as a trusted publisher of your package, pointing at the `publish.yml` workflow. +2. Bump the version in `package.json`, then push a version tag (e.g. `git tag v1.0.0 && git push --tags`) or run the workflow manually from the Actions tab. -This app can use GitHub Actions for CI. The following workflows are configured: +Publishing with provenance is also how you prove ownership when claiming your app in a Twenty marketplace. -- CI: Installs this app and runs unit tests on every push to `develop` branch. -- Linters: Runs [Frappe Semgrep Rules](https://github.com/frappe/semgrep-rules) and [pip-audit](https://pypi.org/project/pip-audit/) on every pull request. +## Changelog +Notable changes are documented in [CHANGELOG.md](CHANGELOG.md). -### License +## Learn more -mit +- [Twenty Apps documentation](https://docs.twenty.com/developers/extend/apps/getting-started/quick-start) +- [twenty-sdk CLI reference](https://www.npmjs.com/package/twenty-sdk) +- [Discord](https://discord.gg/cx5n4Jzs57) diff --git a/SETUP.md b/SETUP.md new file mode 100644 index 0000000..b226d91 --- /dev/null +++ b/SETUP.md @@ -0,0 +1,44 @@ +# Setup + +Follow these steps to get your app running locally. + +## Prerequisites + +- Node.js (version specified in `.nvmrc`) +- Yarn 4 +- Docker (to run the local Twenty server) + +## Steps + +1. Install dependencies: + + ```bash + yarn install + ``` + +2. Start the local Twenty server: + + ```bash + yarn twenty docker:start + ``` + + Check the server status at any time with `yarn twenty docker:status`. + +3. Start the development server and sync your app: + + ```bash + yarn twenty dev + ``` + +4. Open [http://localhost:2020](http://localhost:2020) and log in with the default development credentials: `tim@apple.dev` / `tim@apple.dev`. + +## Verifying your setup + +- `yarn lint` - Lint the project with oxlint +- `yarn typecheck` - Type-check the project +- `yarn test:unit` - Run unit tests +- `yarn test` - Run integration tests + +## Troubleshooting + +See the [troubleshooting guide](https://docs.twenty.com/developers/extend/apps/getting-started/troubleshooting) or ask on [Discord](https://discord.gg/cx5n4Jzs57). diff --git a/aura/__init__.py b/aura/__init__.py deleted file mode 100644 index f102a9c..0000000 --- a/aura/__init__.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = "0.0.1" diff --git a/aura/aura/__init__.py b/aura/aura/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/aura/aura/doctype/__init__.py b/aura/aura/doctype/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/aura/aura/doctype/organization_sector/__init__.py b/aura/aura/doctype/organization_sector/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/aura/aura/doctype/organization_sector/organization_sector.json b/aura/aura/doctype/organization_sector/organization_sector.json deleted file mode 100644 index c49d0e0..0000000 --- a/aura/aura/doctype/organization_sector/organization_sector.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "actions": [], - "allow_bulk_edit": 1, - "allow_rename": 1, - "creation": "2026-08-20 22:53:22.830675", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "sector" - ], - "fields": [ - { - "fieldname": "sector", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Sector", - "options": "Sector", - "reqd": 1 - } - ], - "grid_page_length": 50, - "index_web_pages_for_search": 1, - "istable": 1, - "links": [], - "modified": "2026-08-20 22:54:34.476950", - "modified_by": "Administrator", - "module": "AURA", - "name": "Organization Sector", - "owner": "Administrator", - "permissions": [], - "row_format": "Dynamic", - "rows_threshold_for_grid_search": 20, - "sort_field": "creation", - "sort_order": "DESC", - "states": [] -} \ No newline at end of file diff --git a/aura/aura/doctype/organization_sector/organization_sector.py b/aura/aura/doctype/organization_sector/organization_sector.py deleted file mode 100644 index e211623..0000000 --- a/aura/aura/doctype/organization_sector/organization_sector.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2026, Javier Ribal del Río and contributors -# For license information, please see license.txt - -# import frappe -from frappe.model.document import Document - - -class OrganizationSector(Document): - pass diff --git a/aura/aura/doctype/partner_tier/__init__.py b/aura/aura/doctype/partner_tier/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/aura/aura/doctype/partner_tier/partner_tier.js b/aura/aura/doctype/partner_tier/partner_tier.js deleted file mode 100644 index e1d6b86..0000000 --- a/aura/aura/doctype/partner_tier/partner_tier.js +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) 2026, Javier Ribal del Río and contributors -// For license information, please see license.txt - -// frappe.ui.form.on("Partner Tier", { -// refresh(frm) { - -// }, -// }); diff --git a/aura/aura/doctype/partner_tier/partner_tier.json b/aura/aura/doctype/partner_tier/partner_tier.json deleted file mode 100644 index b476826..0000000 --- a/aura/aura/doctype/partner_tier/partner_tier.json +++ /dev/null @@ -1,80 +0,0 @@ -{ - "actions": [], - "allow_bulk_edit": 1, - "allow_rename": 1, - "autoname": "field:tier_name", - "creation": "2026-08-21 18:16:31.872540", - "doctype": "DocType", - "engine": "InnoDB", - "field_order": [ - "tier_name", - "column_break_wcxl", - "parent_tier", - "section_break_spbb", - "description", - "task_templates" - ], - "fields": [ - { - "fieldname": "tier_name", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Tier Name", - "reqd": 1, - "unique": 1 - }, - { - "fieldname": "description", - "fieldtype": "Small Text", - "label": "Description" - }, - { - "fieldname": "column_break_wcxl", - "fieldtype": "Column Break" - }, - { - "fieldname": "parent_tier", - "fieldtype": "Link", - "label": "Parent Tier", - "options": "Partner Tier" - }, - { - "fieldname": "section_break_spbb", - "fieldtype": "Section Break" - }, - { - "fieldname": "task_templates", - "fieldtype": "Table", - "label": "Task Templates", - "options": "Partner Tier Task Template" - } - ], - "grid_page_length": 50, - "index_web_pages_for_search": 1, - "links": [], - "modified": "2026-08-21 19:30:20.546528", - "modified_by": "Administrator", - "module": "AURA", - "name": "Partner Tier", - "naming_rule": "By fieldname", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "share": 1, - "write": 1 - } - ], - "row_format": "Dynamic", - "rows_threshold_for_grid_search": 20, - "sort_field": "creation", - "sort_order": "DESC", - "states": [] -} diff --git a/aura/aura/doctype/partner_tier/partner_tier.py b/aura/aura/doctype/partner_tier/partner_tier.py deleted file mode 100644 index c2d26cc..0000000 --- a/aura/aura/doctype/partner_tier/partner_tier.py +++ /dev/null @@ -1,47 +0,0 @@ -import frappe -from frappe.model.document import Document - - -class PartnerTier(Document): - - def validate(self): - self.validate_parent_tier() - - def validate_parent_tier(self): - """ - Comprueba que la jerarquía de Partner Tiers no contenga ciclos. - - Ejemplos no permitidos: - - Gold -> Gold - - Gold -> Silver - Silver -> Gold - """ - - if not self.parent_tier: - return - - # No permitir que un tier sea su propio padre - if self.parent_tier == self.name: - frappe.throw( - "A Partner Tier cannot be its own parent." - ) - - visited = {self.name} - current_tier = self.parent_tier - - while current_tier: - if current_tier in visited: - frappe.throw( - "A circular reference has been detected " - "in the Partner Tier hierarchy." - ) - - visited.add(current_tier) - - current_tier = frappe.db.get_value( - "Partner Tier", - current_tier, - "parent_tier", - ) \ No newline at end of file diff --git a/aura/aura/doctype/partner_tier/test_partner_tier.py b/aura/aura/doctype/partner_tier/test_partner_tier.py deleted file mode 100644 index 1589988..0000000 --- a/aura/aura/doctype/partner_tier/test_partner_tier.py +++ /dev/null @@ -1,22 +0,0 @@ -# Copyright (c) 2026, Javier Ribal del Río and Contributors -# See license.txt - -# import frappe -from frappe.tests import IntegrationTestCase - - -# On IntegrationTestCase, the doctype test records and all -# link-field test record dependencies are recursively loaded -# Use these module variables to add/remove to/from that list -EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"] -IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"] - - - -class IntegrationTestPartnerTier(IntegrationTestCase): - """ - Integration tests for PartnerTier. - Use this class for testing interactions between multiple components. - """ - - pass diff --git a/aura/aura/doctype/partner_tier_task_template/__init__.py b/aura/aura/doctype/partner_tier_task_template/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/aura/aura/doctype/partner_tier_task_template/partner_tier_task_template.json b/aura/aura/doctype/partner_tier_task_template/partner_tier_task_template.json deleted file mode 100644 index 85f409e..0000000 --- a/aura/aura/doctype/partner_tier_task_template/partner_tier_task_template.json +++ /dev/null @@ -1,67 +0,0 @@ -{ - "actions": [], - "allow_bulk_edit": 1, - "allow_rename": 1, - "creation": "2026-08-21 18:24:50.660467", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "title_template", - "description_template", - "section_break_fefn", - "priority", - "column_break_ihec", - "due_date" - ], - "fields": [ - { - "fieldname": "title_template", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Title Template", - "reqd": 1 - }, - { - "fieldname": "description_template", - "fieldtype": "Small Text", - "label": "Description Template" - }, - { - "fieldname": "section_break_fefn", - "fieldtype": "Section Break" - }, - { - "fieldname": "priority", - "fieldtype": "Select", - "in_list_view": 1, - "label": "Priority", - "options": "Low\nMedium\nHigh" - }, - { - "fieldname": "column_break_ihec", - "fieldtype": "Column Break" - }, - { - "fieldname": "due_date", - "fieldtype": "Date", - "in_list_view": 1, - "label": "Due Date" - } - ], - "grid_page_length": 50, - "index_web_pages_for_search": 1, - "istable": 1, - "links": [], - "modified": "2026-08-21 18:38:10.298703", - "modified_by": "Administrator", - "module": "AURA", - "name": "Partner Tier Task Template", - "owner": "Administrator", - "permissions": [], - "row_format": "Dynamic", - "rows_threshold_for_grid_search": 20, - "sort_field": "creation", - "sort_order": "DESC", - "states": [] -} diff --git a/aura/aura/doctype/partner_tier_task_template/partner_tier_task_template.py b/aura/aura/doctype/partner_tier_task_template/partner_tier_task_template.py deleted file mode 100644 index 9b2659b..0000000 --- a/aura/aura/doctype/partner_tier_task_template/partner_tier_task_template.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2026, Javier Ribal del Río and contributors -# For license information, please see license.txt - -# import frappe -from frappe.model.document import Document - - -class PartnerTierTaskTemplate(Document): - pass diff --git a/aura/aura/doctype/season/__init__.py b/aura/aura/doctype/season/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/aura/aura/doctype/season/season.js b/aura/aura/doctype/season/season.js deleted file mode 100644 index 2c13929..0000000 --- a/aura/aura/doctype/season/season.js +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) 2026, Javier Ribal del Río and contributors -// For license information, please see license.txt - -// frappe.ui.form.on("Season", { -// refresh(frm) { - -// }, -// }); diff --git a/aura/aura/doctype/season/season.json b/aura/aura/doctype/season/season.json deleted file mode 100644 index 3fe4f10..0000000 --- a/aura/aura/doctype/season/season.json +++ /dev/null @@ -1,79 +0,0 @@ -{ - "actions": [], - "allow_bulk_edit": 1, - "allow_rename": 1, - "autoname": "field:season_name", - "creation": "2026-08-17 19:01:19.130072", - "doctype": "DocType", - "engine": "InnoDB", - "field_order": [ - "season_information_section", - "season_name", - "start_date", - "end_date", - "active" - ], - "fields": [ - { - "fieldname": "season_information_section", - "fieldtype": "Section Break", - "label": "Season Information" - }, - { - "fieldname": "season_name", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Season name", - "reqd": 1, - "unique": 1 - }, - { - "fieldname": "start_date", - "fieldtype": "Date", - "in_list_view": 1, - "label": "Start date", - "reqd": 1 - }, - { - "fieldname": "end_date", - "fieldtype": "Date", - "in_list_view": 1, - "label": "End date", - "reqd": 1 - }, - { - "default": "0", - "fieldname": "active", - "fieldtype": "Check", - "label": "Active" - } - ], - "grid_page_length": 50, - "index_web_pages_for_search": 1, - "links": [], - "modified": "2026-08-17 19:05:52.364656", - "modified_by": "Administrator", - "module": "AURA", - "name": "Season", - "naming_rule": "By fieldname", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "share": 1, - "write": 1 - } - ], - "row_format": "Dynamic", - "rows_threshold_for_grid_search": 20, - "sort_field": "creation", - "sort_order": "DESC", - "states": [] -} diff --git a/aura/aura/doctype/season/season.py b/aura/aura/doctype/season/season.py deleted file mode 100644 index f4f45d0..0000000 --- a/aura/aura/doctype/season/season.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2026, Javier Ribal del Río and contributors -# For license information, please see license.txt - -# import frappe -from frappe.model.document import Document - - -class Season(Document): - pass diff --git a/aura/aura/doctype/season/test_season.py b/aura/aura/doctype/season/test_season.py deleted file mode 100644 index f268f5e..0000000 --- a/aura/aura/doctype/season/test_season.py +++ /dev/null @@ -1,22 +0,0 @@ -# Copyright (c) 2026, Javier Ribal del Río and Contributors -# See license.txt - -# import frappe -from frappe.tests import IntegrationTestCase - - -# On IntegrationTestCase, the doctype test records and all -# link-field test record dependencies are recursively loaded -# Use these module variables to add/remove to/from that list -EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"] -IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"] - - - -class IntegrationTestSeason(IntegrationTestCase): - """ - Integration tests for Season. - Use this class for testing interactions between multiple components. - """ - - pass diff --git a/aura/aura/doctype/sector/__init__.py b/aura/aura/doctype/sector/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/aura/aura/doctype/sector/sector.js b/aura/aura/doctype/sector/sector.js deleted file mode 100644 index fafc307..0000000 --- a/aura/aura/doctype/sector/sector.js +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) 2026, Javier Ribal del Río and contributors -// For license information, please see license.txt - -// frappe.ui.form.on("Sector", { -// refresh(frm) { - -// }, -// }); diff --git a/aura/aura/doctype/sector/sector.json b/aura/aura/doctype/sector/sector.json deleted file mode 100644 index 8720554..0000000 --- a/aura/aura/doctype/sector/sector.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "actions": [], - "allow_bulk_edit": 1, - "allow_rename": 1, - "autoname": "field:sector_name", - "creation": "2026-08-20 22:35:24.370629", - "doctype": "DocType", - "engine": "InnoDB", - "field_order": [ - "sector_name" - ], - "fields": [ - { - "fieldname": "sector_name", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Sector Name", - "reqd": 1, - "unique": 1 - } - ], - "grid_page_length": 50, - "index_web_pages_for_search": 1, - "links": [], - "modified": "2026-08-20 22:48:27.003616", - "modified_by": "Administrator", - "module": "AURA", - "name": "Sector", - "naming_rule": "By fieldname", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "share": 1, - "write": 1 - } - ], - "row_format": "Dynamic", - "rows_threshold_for_grid_search": 20, - "sort_field": "creation", - "sort_order": "DESC", - "states": [] -} diff --git a/aura/aura/doctype/sector/sector.py b/aura/aura/doctype/sector/sector.py deleted file mode 100644 index 7648080..0000000 --- a/aura/aura/doctype/sector/sector.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2026, Javier Ribal del Río and contributors -# For license information, please see license.txt - -# import frappe -from frappe.model.document import Document - - -class Sector(Document): - pass diff --git a/aura/aura/doctype/sector/test_sector.py b/aura/aura/doctype/sector/test_sector.py deleted file mode 100644 index 25139ce..0000000 --- a/aura/aura/doctype/sector/test_sector.py +++ /dev/null @@ -1,22 +0,0 @@ -# Copyright (c) 2026, Javier Ribal del Río and Contributors -# See license.txt - -# import frappe -from frappe.tests import IntegrationTestCase - - -# On IntegrationTestCase, the doctype test records and all -# link-field test record dependencies are recursively loaded -# Use these module variables to add/remove to/from that list -EXTRA_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"] -IGNORE_TEST_RECORD_DEPENDENCIES = [] # eg. ["User"] - - - -class IntegrationTestSector(IntegrationTestCase): - """ - Integration tests for Sector. - Use this class for testing interactions between multiple components. - """ - - pass diff --git a/aura/config/__init__.py b/aura/config/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/aura/fixtures/crm_deal_status.json b/aura/fixtures/crm_deal_status.json deleted file mode 100644 index 4f7d6e5..0000000 --- a/aura/fixtures/crm_deal_status.json +++ /dev/null @@ -1,90 +0,0 @@ -[ - { - "color": "yellow", - "deal_status": "Negotiation", - "docstatus": 0, - "doctype": "CRM Deal Status", - "modified": "2026-08-17 21:41:36.621036", - "name": "Negotiation", - "position": 4, - "probability": 70.0, - "type": "Ongoing" - }, - { - "color": "blue", - "deal_status": "Contacted", - "docstatus": 0, - "doctype": "CRM Deal Status", - "modified": "2026-08-22 23:41:55.744154", - "name": "Contacted", - "position": 0, - "probability": 0.0, - "type": "Ongoing" - }, - { - "color": "green", - "deal_status": "Partner", - "docstatus": 0, - "doctype": "CRM Deal Status", - "modified": "2026-08-22 23:42:39.880201", - "name": "Partner", - "position": 0, - "probability": 0.0, - "type": "Won" - }, - { - "color": "teal", - "deal_status": "Renewal", - "docstatus": 0, - "doctype": "CRM Deal Status", - "modified": "2026-08-22 23:43:01.412410", - "name": "Renewal", - "position": 0, - "probability": 0.0, - "type": "Open" - }, - { - "color": "gray", - "deal_status": "Inactive", - "docstatus": 0, - "doctype": "CRM Deal Status", - "modified": "2026-08-22 23:43:13.164821", - "name": "Inactive", - "position": 0, - "probability": 0.0, - "type": "On Hold" - }, - { - "color": "red", - "deal_status": "No Response", - "docstatus": 0, - "doctype": "CRM Deal Status", - "modified": "2026-08-22 23:43:31.902046", - "name": "No Response", - "position": 0, - "probability": 0.0, - "type": "Lost" - }, - { - "color": "purple", - "deal_status": "Not a Partner", - "docstatus": 0, - "doctype": "CRM Deal Status", - "modified": "2026-08-22 23:46:54.722217", - "name": "Not a Partner", - "position": 0, - "probability": 0.0, - "type": "Lost" - }, - { - "color": "orange", - "deal_status": "Potential", - "docstatus": 0, - "doctype": "CRM Deal Status", - "modified": "2026-08-22 23:41:08.173440", - "name": "Potential", - "position": 0, - "probability": 0.0, - "type": "Open" - } -] \ No newline at end of file diff --git a/aura/fixtures/crm_fields_layout.json b/aura/fixtures/crm_fields_layout.json deleted file mode 100644 index dafd08d..0000000 --- a/aura/fixtures/crm_fields_layout.json +++ /dev/null @@ -1,83 +0,0 @@ -[ - { - "docstatus": 0, - "doctype": "CRM Fields Layout", - "dt": "CRM Deal", - "layout": "[{\"name\":\"first_tab\",\"sections\":[{\"name\":\"organization_section\",\"hidden\":true,\"editable\":false,\"columns\":[{\"name\":\"column_GpMP\",\"fields\":[\"organization\"]},{\"name\":\"column_FPTn\",\"fields\":[]}]},{\"name\":\"organization_details_section\",\"editable\":false,\"columns\":[{\"name\":\"column_S3tQ\",\"fields\":[\"organization_name\"]},{\"name\":\"column_KqV1\",\"fields\":[\"website\"]},{\"name\":\"column_1r67\",\"fields\":[]}]},{\"name\":\"contact_section\",\"hidden\":true,\"editable\":false,\"columns\":[{\"name\":\"column_CeXr\",\"fields\":[\"contact\"]},{\"name\":\"column_yHbk\",\"fields\":[]}]},{\"name\":\"contact_details_section\",\"editable\":false,\"columns\":[{\"name\":\"column_ZTWr\",\"fields\":[\"first_name\",\"email\"]},{\"name\":\"column_tabr\",\"fields\":[\"last_name\",\"mobile_no\"]},{\"name\":\"column_Qjdx\",\"fields\":[\"gender\"]}]},{\"name\":\"deal_section\",\"columns\":[{\"name\":\"column_mdps\",\"fields\":[\"status\"]},{\"name\":\"column_H40H\",\"fields\":[\"deal_owner\",\"custom_season\"]}]}]}]", - "modified": "2026-08-23 18:41:39.608270", - "name": "CRM Deal-Quick Entry", - "type": "Quick Entry" - }, - { - "docstatus": 0, - "doctype": "CRM Fields Layout", - "dt": "Contact", - "layout": "[{\"name\":\"first_tab\",\"sections\":[{\"name\":\"full_name_section\",\"hideBorder\":true,\"columns\":[{\"name\":\"column_cSxf\",\"fields\":[\"first_name\"]},{\"name\":\"column_yBc7\",\"fields\":[\"last_name\"]}]},{\"name\":\"email_section\",\"hideBorder\":true,\"columns\":[{\"name\":\"column_tH3L\",\"fields\":[\"email_id\"]}]},{\"name\":\"mobile_gender_section\",\"hideBorder\":true,\"columns\":[{\"name\":\"column_lrfI\",\"fields\":[\"mobile_no\"]},{\"name\":\"column_Tx3n\",\"fields\":[\"gender\"]}]},{\"name\":\"organization_section\",\"hideBorder\":true,\"columns\":[{\"name\":\"column_S0J8\",\"fields\":[\"company_name\"]}]},{\"name\":\"designation_section\",\"hideBorder\":true,\"columns\":[{\"name\":\"column_bsO8\",\"fields\":[\"department\"]}]}]}]", - "modified": "2026-08-19 19:00:06.925682", - "name": "Contact-Quick Entry", - "type": "Quick Entry" - }, - { - "docstatus": 0, - "doctype": "CRM Fields Layout", - "dt": "CRM Organization", - "layout": "[{\"name\":\"first_tab\",\"sections\":[{\"name\":\"organization_section\",\"columns\":[{\"name\":\"column_zOuv\",\"fields\":[\"organization_name\"]},{\"label\":\"\",\"name\":\"column_LlpS\",\"fields\":[\"organization_logo\"]}]},{\"name\":\"website_revenue_section\",\"hideBorder\":true,\"columns\":[{\"name\":\"column_I5Dy\",\"fields\":[\"website\",\"custom_nif\",\"custom_upv\"]},{\"name\":\"column_Rgss\",\"fields\":[\"custom_sector\",\"custom_speciality\"]}]},{\"name\":\"address_section\",\"hideBorder\":true,\"columns\":[{\"name\":\"column_O2dk\",\"fields\":[\"custom_country\",\"address\"]}]},{\"label\":\"\",\"name\":\"section_howJ\",\"opened\":true,\"columns\":[{\"name\":\"column_yQZQ\",\"fields\":[\"custom_observations\"]}]}]}]", - "modified": "2026-08-20 23:46:37.025703", - "name": "CRM Organization-Quick Entry", - "type": "Quick Entry" - }, - { - "docstatus": 0, - "doctype": "CRM Fields Layout", - "dt": "CRM Call Log", - "layout": "[{\"name\":\"first_tab\",\"sections\":[{\"name\":\"details_section\",\"columns\":[{\"name\":\"column_uMSG\",\"fields\":[\"type\",\"medium\",\"status\"]},{\"name\":\"column_wiZT\",\"fields\":[\"custom_agent\",\"custom_contact\",\"note\"]}]},{\"label\":\"\",\"name\":\"section_eqCQ\",\"opened\":true,\"columns\":[{\"name\":\"column_vPex\",\"fields\":[\"start_time\",\"end_time\"]},{\"label\":\"\",\"name\":\"column_Gnki\",\"fields\":[\"duration\"]}]},{\"label\":\"\",\"name\":\"section_nyPi\",\"opened\":true,\"columns\":[{\"name\":\"column_leuK\",\"fields\":[]}]}]}]", - "modified": "2026-08-22 16:08:15.915742", - "name": "CRM Call Log-Quick Entry", - "type": "Quick Entry" - }, - { - "docstatus": 0, - "doctype": "CRM Fields Layout", - "dt": "CRM Task", - "layout": "[{\"name\":\"first_tab\",\"sections\":[{\"name\":\"details_section\",\"columns\":[{\"name\":\"column_X9sG\",\"fields\":[\"title\",\"description\"]}]},{\"name\":\"assignment_section\",\"columns\":[{\"name\":\"column_9XjK\",\"fields\":[\"priority\",\"due_date\",\"custom_ventaja_partner\"]},{\"name\":\"column_7s8n\",\"fields\":[\"assigned_to\",\"status\"]}],\"hideBorder\":true}]}]", - "modified": "2026-08-22 11:13:52.881284", - "name": "CRM Task-Quick Entry", - "type": "Quick Entry" - }, - { - "docstatus": 0, - "doctype": "CRM Fields Layout", - "dt": "CRM Deal", - "layout": "[{\"label\":\"Deal Details\",\"name\":\"organization_section\",\"opened\":true,\"columns\":[{\"name\":\"column_na2Q\",\"fields\":[\"organization\",\"closed_date\",\"status\",\"custom_partner_tier\",\"custom_season\"]}],\"editingLabel\":false},{\"label\":\"Contacts\",\"name\":\"contacts_section\",\"opened\":true,\"editable\":false,\"contacts\":[]}]", - "modified": "2026-08-22 17:00:54.615886", - "name": "CRM Deal-Side Panel", - "type": "Side Panel" - }, - { - "docstatus": 0, - "doctype": "CRM Fields Layout", - "dt": "Contact", - "layout": "[{\"label\":\"Details\",\"name\":\"details_section\",\"opened\":true,\"columns\":[{\"name\":\"column_eIWl\",\"fields\":[\"first_name\",\"last_name\",\"gender\",\"company_name\",\"department\",\"email_id\",\"mobile_no\"]}],\"showEditButton\":true,\"visible\":7}]", - "modified": "2026-08-19 19:22:11.256992", - "name": "Contact-Side Panel", - "type": "Side Panel" - }, - { - "docstatus": 0, - "doctype": "CRM Fields Layout", - "dt": "CRM Organization", - "layout": "[{\"label\":\"Details\",\"name\":\"details_section\",\"opened\":true,\"columns\":[{\"name\":\"column_IJOV\",\"fields\":[\"organization_name\",\"custom_sector\",\"custom_speciality\",\"website\",\"custom_nif\",\"custom_country\",\"address\",\"custom_upv\",\"custom_observations\"]}]}]", - "modified": "2026-08-20 23:46:57.292721", - "name": "CRM Organization-Side Panel", - "type": "Side Panel" - }, - { - "docstatus": 0, - "doctype": "CRM Fields Layout", - "dt": "CRM Deal", - "layout": "[{\"name\":\"first_tab\",\"sections\":[{\"label\":\"Details\",\"name\":\"details_section\",\"opened\":true,\"columns\":[{\"name\":\"column_z9XL\",\"fields\":[\"organization\",\"custom_season\"]},{\"name\":\"column_gM4w\",\"fields\":[\"deal_owner\"]},{\"name\":\"column_gWmE\",\"fields\":[\"custom_partner_tier\",\"closed_date\"]}]},{\"label\":\"Products\",\"name\":\"section_jHhQ\",\"opened\":true,\"columns\":[{\"name\":\"column_xiNF\",\"fields\":[\"products\"]}],\"editingLabel\":false,\"hideLabel\":true},{\"label\":\"New Section\",\"name\":\"section_WNOQ\",\"opened\":true,\"columns\":[{\"name\":\"column_ziBW\",\"fields\":[\"total\"]},{\"label\":\"\",\"name\":\"column_wuwA\",\"fields\":[]}],\"hideBorder\":true,\"hideLabel\":true}]}]", - "modified": "2026-08-22 13:15:10.987797", - "name": "CRM Deal-Data Fields", - "type": "Data Fields" - } -] \ No newline at end of file diff --git a/aura/fixtures/crm_form_script.json b/aura/fixtures/crm_form_script.json deleted file mode 100644 index a85e8fa..0000000 --- a/aura/fixtures/crm_form_script.json +++ /dev/null @@ -1,13 +0,0 @@ -[ - { - "docstatus": 0, - "doctype": "CRM Form Script", - "dt": "CRM Deal", - "enabled": 1, - "is_standard": 0, - "modified": "2026-08-22 11:14:24.009976", - "name": "AURA - Generar Ventajas", - "script": "class CRMDeal {\n onLoad() {\n if (this.doc.__newDocument) return;\n\n this.actions = this.actions || [];\n\n this.actions.push({\n label: \"Generar ventajas\",\n\n onClick: (close) => {\n if (close) close();\n\n createDialog({\n title: \"Generar ventajas\",\n\n message:\n \"Se crearán las tareas correspondientes al Partner Tier \" +\n \"asignado a este patrocinio. \" +\n \"Si las ventajas ya se generaron anteriormente, \" +\n \"se volverán a crear. ¿Quieres continuar?\",\n\n actions: [\n {\n label: \"Generar ventajas\",\n variant: \"solid\",\n\n onClick: async ({ close: dialogClose }) => {\n dialogClose();\n\n try {\n const result = await call(\n \"aura.services.partner_tasks.generate_partner_tasks\",\n {\n deal_name: this.doc.name,\n }\n );\n\n if (result?.success) {\n toast.success(result.message);\n } else {\n toast.error(\n result?.message ||\n \"No se han podido generar las ventajas.\"\n );\n }\n\n } catch (error) {\n console.error(\n \"Error generando ventajas AURA:\",\n error\n );\n\n toast.error(\n \"No se han podido generar las ventajas: \" +\n (\n error?.message ||\n \"se ha producido un error inesperado.\"\n )\n );\n }\n },\n },\n\n {\n label: \"Cancelar\",\n variant: \"outline\",\n\n onClick: ({ close: dialogClose }) => {\n dialogClose();\n },\n },\n ],\n });\n },\n });\n }\n}", - "view": "Form" - } -] \ No newline at end of file diff --git a/aura/fixtures/custom_field.json b/aura/fixtures/custom_field.json deleted file mode 100644 index 7c53b82..0000000 --- a/aura/fixtures/custom_field.json +++ /dev/null @@ -1,684 +0,0 @@ -[ - { - "alignment": "", - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "button_color": "", - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "doctype": "Custom Field", - "dt": "CRM Deal", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "custom_season", - "fieldtype": "Link", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "deal_owner", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Season", - "length": 0, - "link_filters": null, - "mandatory_depends_on": null, - "mask": 0, - "modified": "2026-08-19 18:48:30.218720", - "module": "AURA", - "name": "CRM Deal-custom_season", - "no_copy": 0, - "non_negative": 0, - "options": "Season", - "permlevel": 0, - "placeholder": null, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "alignment": "", - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "button_color": "", - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "default": "0", - "depends_on": null, - "description": null, - "docstatus": 0, - "doctype": "Custom Field", - "dt": "CRM Task", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "custom_ventaja_partner", - "fieldtype": "Check", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "status", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Ventaja Partner", - "length": 0, - "link_filters": null, - "mandatory_depends_on": null, - "mask": 0, - "modified": "2026-08-22 09:22:18.097497", - "module": "AURA", - "name": "CRM Task-custom_ventaja_partner", - "no_copy": 0, - "non_negative": 0, - "options": null, - "permlevel": 0, - "placeholder": null, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "alignment": "", - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "button_color": "", - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "doctype": "Custom Field", - "dt": "CRM Products", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "custom_delivered", - "fieldtype": "Check", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "rate", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Delivered", - "length": 0, - "link_filters": null, - "mandatory_depends_on": null, - "mask": 0, - "modified": "2026-08-22 12:26:52.719213", - "module": "AURA", - "name": "CRM Products-custom_delivered", - "no_copy": 0, - "non_negative": 0, - "options": null, - "permlevel": 0, - "placeholder": null, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "alignment": "", - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "button_color": "", - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "doctype": "Custom Field", - "dt": "CRM Deal", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "custom_partner_tier", - "fieldtype": "Link", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "custom_season", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Partner Tier", - "length": 0, - "link_filters": null, - "mandatory_depends_on": null, - "mask": 0, - "modified": "2026-08-21 18:44:34.946075", - "module": "AURA", - "name": "CRM Deal-custom_partner_tier", - "no_copy": 0, - "non_negative": 0, - "options": "Partner Tier", - "permlevel": 0, - "placeholder": null, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "alignment": "", - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "button_color": "", - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "doctype": "Custom Field", - "dt": "CRM Organization", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "custom_country", - "fieldtype": "Link", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "address", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Country", - "length": 0, - "link_filters": null, - "mandatory_depends_on": null, - "mask": 0, - "modified": "2026-08-19 20:24:54.274250", - "module": "AURA", - "name": "CRM Organization-custom_country", - "no_copy": 0, - "non_negative": 0, - "options": "Country", - "permlevel": 0, - "placeholder": null, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "alignment": "", - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "button_color": "", - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "doctype": "Custom Field", - "dt": "CRM Organization", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "custom_nif", - "fieldtype": "Data", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "custom_country", - "is_system_generated": 0, - "is_virtual": 0, - "label": "NIF", - "length": 0, - "link_filters": null, - "mandatory_depends_on": null, - "mask": 0, - "modified": "2026-08-19 20:41:51.159348", - "module": "AURA", - "name": "CRM Organization-custom_nif", - "no_copy": 0, - "non_negative": 0, - "options": null, - "permlevel": 0, - "placeholder": null, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 1, - "unique": 0, - "width": null - }, - { - "alignment": "", - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "button_color": "", - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "doctype": "Custom Field", - "dt": "CRM Organization", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "custom_upv", - "fieldtype": "Check", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "custom_nif", - "is_system_generated": 0, - "is_virtual": 0, - "label": "UPV", - "length": 0, - "link_filters": null, - "mandatory_depends_on": null, - "mask": 0, - "modified": "2026-08-19 20:42:12.512281", - "module": "AURA", - "name": "CRM Organization-custom_upv", - "no_copy": 0, - "non_negative": 0, - "options": null, - "permlevel": 0, - "placeholder": null, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "alignment": "", - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "button_color": "", - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "doctype": "Custom Field", - "dt": "CRM Organization", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "custom_observations", - "fieldtype": "Long Text", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "custom_upv", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Observations", - "length": 0, - "link_filters": null, - "mandatory_depends_on": null, - "mask": 0, - "modified": "2026-08-19 20:58:20.355443", - "module": "AURA", - "name": "CRM Organization-custom_observations", - "no_copy": 0, - "non_negative": 0, - "options": null, - "permlevel": 0, - "placeholder": null, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "alignment": "", - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "button_color": "", - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "doctype": "Custom Field", - "dt": "CRM Organization", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "custom_speciality", - "fieldtype": "Data", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "custom_observations", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Speciality", - "length": 0, - "link_filters": null, - "mandatory_depends_on": null, - "mask": 0, - "modified": "2026-08-20 23:27:20.966035", - "module": "AURA", - "name": "CRM Organization-custom_speciality", - "no_copy": 0, - "non_negative": 0, - "options": null, - "permlevel": 0, - "placeholder": null, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 1, - "unique": 0, - "width": null - }, - { - "alignment": "", - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "button_color": "", - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "doctype": "Custom Field", - "dt": "CRM Call Log", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "custom_agent", - "fieldtype": "Link", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_preview": 0, - "in_standard_filter": 1, - "insert_after": "note", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Agent", - "length": 0, - "link_filters": null, - "mandatory_depends_on": null, - "mask": 0, - "modified": "2026-08-22 13:57:03.375225", - "module": "AURA", - "name": "CRM Call Log-custom_agent", - "no_copy": 0, - "non_negative": 0, - "options": "User", - "permlevel": 0, - "placeholder": null, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "alignment": "", - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "button_color": "", - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "doctype": "Custom Field", - "dt": "CRM Call Log", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "custom_contact", - "fieldtype": "Link", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_preview": 0, - "in_standard_filter": 1, - "insert_after": "custom_agent", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Contact", - "length": 0, - "link_filters": null, - "mandatory_depends_on": null, - "mask": 0, - "modified": "2026-08-22 13:56:56.767403", - "module": "AURA", - "name": "CRM Call Log-custom_contact", - "no_copy": 0, - "non_negative": 0, - "options": "Contact", - "permlevel": 0, - "placeholder": null, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "show_dashboard": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - } -] \ No newline at end of file diff --git a/aura/fixtures/property_setter.json b/aura/fixtures/property_setter.json deleted file mode 100644 index 091f8c6..0000000 --- a/aura/fixtures/property_setter.json +++ /dev/null @@ -1,98 +0,0 @@ -[ - { - "default_value": null, - "doc_type": "CRM Deal", - "docstatus": 0, - "doctype": "Property Setter", - "doctype_or_field": "DocType", - "field_name": null, - "is_system_generated": 0, - "modified": "2026-08-21 18:44:07.144258", - "module": null, - "name": "CRM Deal-main-field_order", - "property": "field_order", - "property_type": "Data", - "row_name": null, - "value": "[\"organization_tab\", \"naming_series\", \"organization\", \"next_step\", \"column_break_ijan\", \"status\", \"deal_owner\", \"custom_season\", \"custom_partner_tier\", \"section_break_jgpm\", \"probability\", \"expected_deal_value\", \"deal_value\", \"column_break_kpxa\", \"expected_closure_date\", \"closed_date\", \"contacts_tab\", \"contacts\", \"contact\", \"lead_details_tab\", \"lead\", \"source\", \"column_break_wsde\", \"lead_name\", \"organization_details_section\", \"organization_name\", \"website\", \"no_of_employees\", \"job_title\", \"column_break_xbyf\", \"territory\", \"currency\", \"exchange_rate\", \"annual_revenue\", \"industry\", \"person_section\", \"salutation\", \"first_name\", \"last_name\", \"column_break_xjmy\", \"email\", \"mobile_no\", \"phone\", \"gender\", \"products_tab\", \"products\", \"section_break_ccbj\", \"total\", \"column_break_udbq\", \"net_total\", \"sla_tab\", \"sla\", \"sla_creation\", \"column_break_pfvq\", \"sla_status\", \"communication_status\", \"response_details_section\", \"response_by\", \"column_break_hpvj\", \"first_response_time\", \"first_responded_on\", \"section_break_mwvg\", \"rolling_responses\", \"section_break_jgdr\", \"last_response_time\", \"column_break_evzj\", \"last_responded_on\", \"log_tab\", \"status_change_log\", \"lost_details_tab\", \"lost_reason\", \"lost_notes\"]" - }, - { - "default_value": null, - "doc_type": "CRM Products", - "docstatus": 0, - "doctype": "Property Setter", - "doctype_or_field": "DocType", - "field_name": null, - "is_system_generated": 0, - "modified": "2026-08-22 12:25:01.576752", - "module": null, - "name": "CRM Products-main-field_order", - "property": "field_order", - "property_type": "Data", - "row_name": null, - "value": "[\"product_code\", \"column_break_gvbc\", \"product_name\", \"section_break_fnvf\", \"qty\", \"column_break_ajac\", \"rate\", \"custom_delivered\", \"section_break_olqb\", \"discount_percentage\", \"column_break_uvra\", \"discount_amount\", \"section_break_cnpb\", \"column_break_pozr\", \"amount\", \"column_break_ejqw\", \"net_amount\"]" - }, - { - "default_value": null, - "doc_type": "CRM Products", - "docstatus": 0, - "doctype": "Property Setter", - "doctype_or_field": "DocField", - "field_name": "product_code", - "is_system_generated": 0, - "modified": "2026-08-22 12:25:12.926463", - "module": null, - "name": "CRM Products-product_code-in_list_view", - "property": "in_list_view", - "property_type": "Check", - "row_name": null, - "value": "0" - }, - { - "default_value": null, - "doc_type": "CRM Products", - "docstatus": 0, - "doctype": "Property Setter", - "doctype_or_field": "DocField", - "field_name": "product_code", - "is_system_generated": 0, - "modified": "2026-08-22 12:25:12.961184", - "module": null, - "name": "CRM Products-product_code-hidden", - "property": "hidden", - "property_type": "Check", - "row_name": null, - "value": "1" - }, - { - "default_value": null, - "doc_type": "CRM Deal", - "docstatus": 0, - "doctype": "Property Setter", - "doctype_or_field": "DocType", - "field_name": null, - "is_system_generated": 0, - "modified": "2026-08-22 17:48:53.121105", - "module": null, - "name": "CRM Deal-main-naming_rule", - "property": "naming_rule", - "property_type": "Data", - "row_name": null, - "value": "Expression" - }, - { - "default_value": null, - "doc_type": "CRM Deal", - "docstatus": 0, - "doctype": "Property Setter", - "doctype_or_field": "DocType", - "field_name": null, - "is_system_generated": 0, - "modified": "2026-08-23 18:04:38.658212", - "module": null, - "name": "CRM Deal-main-autoname", - "property": "autoname", - "property_type": "Data", - "row_name": null, - "value": "format:{custom_season}-{organization}" - } -] \ No newline at end of file diff --git a/aura/hooks.py b/aura/hooks.py deleted file mode 100644 index 062e030..0000000 --- a/aura/hooks.py +++ /dev/null @@ -1,303 +0,0 @@ -app_name = "aura" -app_title = "AURA" -app_publisher = "Javier Ribal del Río" -app_description = "Advanced Unified Relations Application" -app_email = "partners@hyperloopupv.com" -app_license = "mit" - -# Apps -# ------------------ - -# required_apps = [] - -# Each item in the list will be shown as an app in the apps page -# add_to_apps_screen = [ -# { -# "name": "aura", -# "logo": "/assets/aura/logo.png", -# "title": "AURA", -# "route": "/aura", -# "has_permission": "aura.api.permission.has_app_permission" -# } -# ] - -# Includes in -# ------------------ - -# include js, css files in header of desk.html -# app_include_css = "/assets/aura/css/aura.css" -# app_include_js = "/assets/aura/js/aura.js" - -# include js, css files in header of web template -# web_include_css = "/assets/aura/css/aura.css" -# web_include_js = "/assets/aura/js/aura.js" - -# include custom scss in every website theme (without file extension ".scss") -# website_theme_scss = "aura/public/scss/website" - -# include js, css files in header of web form -# webform_include_js = {"doctype": "public/js/doctype.js"} -# webform_include_css = {"doctype": "public/css/doctype.css"} - -# include js in page -# page_js = {"page" : "public/js/file.js"} - -# include js in doctype views -# doctype_js = {"doctype" : "public/js/doctype.js"} -# doctype_list_js = {"doctype" : "public/js/doctype_list.js"} -# doctype_tree_js = {"doctype" : "public/js/doctype_tree.js"} -# doctype_calendar_js = {"doctype" : "public/js/doctype_calendar.js"} - -# Svg Icons -# ------------------ -# include app icons in desk -# app_include_icons = "aura/public/icons.svg" - -# Home Pages -# ---------- - -# application home page (will override Website Settings) -# home_page = "login" - -# website user home page (by Role) -# role_home_page = { -# "Role": "home_page" -# } - -# Generators -# ---------- - -# automatically create page for each record of this doctype -# website_generators = ["Web Page"] - -# automatically load and sync documents of this doctype from downstream apps -# importable_doctypes = [doctype_1] - -# Jinja -# ---------- - -# add methods and filters to jinja environment -# jinja = { -# "methods": "aura.utils.jinja_methods", -# "filters": "aura.utils.jinja_filters" -# } - -# Installation -# ------------ - -# before_install = "aura.install.before_install" -# after_install = "aura.install.after_install" - -# Uninstallation -# ------------ - -# before_uninstall = "aura.uninstall.before_uninstall" -# after_uninstall = "aura.uninstall.after_uninstall" - -# Integration Setup -# ------------------ -# To set up dependencies/integrations with other apps -# Name of the app being installed is passed as an argument - -# before_app_install = "aura.utils.before_app_install" -# after_app_install = "aura.utils.after_app_install" - -# Integration Cleanup -# ------------------- -# To clean up dependencies/integrations with other apps -# Name of the app being uninstalled is passed as an argument - -# before_app_uninstall = "aura.utils.before_app_uninstall" -# after_app_uninstall = "aura.utils.after_app_uninstall" - -# Build -# ------------------ -# To hook into the build process - -# after_build = "aura.build.after_build" - -# Desk Notifications -# ------------------ -# See frappe.core.notifications.get_notification_config - -# notification_config = "aura.notifications.get_notification_config" - -# Permissions -# ----------- -# Permissions evaluated in scripted ways - -# permission_query_conditions = { -# "Event": "frappe.desk.doctype.event.event.get_permission_query_conditions", -# } -# -# has_permission = { -# "Event": "frappe.desk.doctype.event.event.has_permission", -# } - -# Document Events -# --------------- -# Hook on document methods and events - -# doc_events = { -# "*": { -# "on_update": "method", -# "on_cancel": "method", -# "on_trash": "method" -# } -# } - -# Scheduled Tasks -# --------------- - -# scheduler_events = { -# "all": [ -# "aura.tasks.all" -# ], -# "daily": [ -# "aura.tasks.daily" -# ], -# "hourly": [ -# "aura.tasks.hourly" -# ], -# "weekly": [ -# "aura.tasks.weekly" -# ], -# "monthly": [ -# "aura.tasks.monthly" -# ], -# } - -# Testing -# ------- - -# before_tests = "aura.install.before_tests" - -# Extend DocType Class -# ------------------------------ -# -# Specify custom mixins to extend the standard doctype controller. -# extend_doctype_class = { -# "Task": "aura.custom.task.CustomTaskMixin" -# } - -# Overriding Methods -# ------------------------------ -# -# override_whitelisted_methods = { -# "frappe.desk.doctype.event.event.get_events": "aura.event.get_events" -# } -# -# each overriding function accepts a `data` argument; -# generated from the base implementation of the doctype dashboard, -# along with any modifications made in other Frappe apps -# override_doctype_dashboards = { -# "Task": "aura.task.get_dashboard_data" -# } - -# exempt linked doctypes from being automatically cancelled -# -# auto_cancel_exempted_doctypes = ["Auto Repeat"] - -# Ignore links to specified DocTypes when deleting documents -# ----------------------------------------------------------- - -# ignore_links_on_delete = ["Communication", "ToDo"] - -# Request Events -# ---------------- -# before_request = ["aura.utils.before_request"] -# after_request = ["aura.utils.after_request"] - -# Job Events -# ---------- -# before_job = ["aura.utils.before_job"] -# after_job = ["aura.utils.after_job"] - -# User Data Protection -# -------------------- - -# user_data_fields = [ -# { -# "doctype": "{doctype_1}", -# "filter_by": "{filter_by}", -# "redact_fields": ["{field_1}", "{field_2}"], -# "partial": 1, -# }, -# { -# "doctype": "{doctype_2}", -# "filter_by": "{filter_by}", -# "partial": 1, -# }, -# { -# "doctype": "{doctype_3}", -# "strict": False, -# }, -# { -# "doctype": "{doctype_4}" -# } -# ] - -# Authentication and authorization -# -------------------------------- - -# auth_hooks = [ -# "aura.auth.validate" -# ] - -# Automatically update python controller files with type annotations for this app. -# export_python_type_annotations = True - -# default_log_clearing_doctypes = { -# "Logging DocType Name": 30 # days to retain logs -# } - -# Translation -# ------------ -# List of apps whose translatable strings should be excluded from this app's translations. -# ignore_translatable_strings_from = [] - -fixtures = [ - { - "dt": "Custom Field", - "filters": [ - ["module", "=", "AURA"] - ], - }, - { - "dt": "Property Setter", - "filters": [ - ["doc_type", "in", ["CRM Products","CRM Deal",]] - ], - }, - { - "dt": "CRM Deal Status", - "filters": [ - [ - "name", - "in", - [ - "Potential", - "Contacted", - "Negotiation", - "Partner", - "Renewal", - "Inactive", - "No Response", - "Not a Partner", - ], - ] - ], - }, - { - "dt": "CRM Fields Layout", - "filters": [ - ["dt", "in", ["CRM Deal", "CRM Organization", "Contact", "CRM Task","CRM Products", "CRM Call Log"]] - ], - }, - { - "dt": "CRM Form Script", - "filters": [ - ["name", "=", "AURA - Generar ventajas"] - ], - }, -] \ No newline at end of file diff --git a/aura/modules.txt b/aura/modules.txt deleted file mode 100644 index 6b31ad2..0000000 --- a/aura/modules.txt +++ /dev/null @@ -1 +0,0 @@ -AURA \ No newline at end of file diff --git a/aura/patches.txt b/aura/patches.txt deleted file mode 100644 index f15c3a9..0000000 --- a/aura/patches.txt +++ /dev/null @@ -1,6 +0,0 @@ -[pre_model_sync] -# Patches added in this section will be executed before doctypes are migrated -# Read docs to understand patches: https://frappeframework.com/docs/v14/user/en/database-migrations - -[post_model_sync] -# Patches added in this section will be executed after doctypes are migrated \ No newline at end of file diff --git a/aura/patches/__init__.py b/aura/patches/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/aura/public/.gitkeep b/aura/public/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/aura/services/__init__.py b/aura/services/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/aura/services/partner_tasks.py b/aura/services/partner_tasks.py deleted file mode 100644 index 72f2ed5..0000000 --- a/aura/services/partner_tasks.py +++ /dev/null @@ -1,351 +0,0 @@ -import frappe - - -PLACEHOLDER_NAME = "{name}" - - -def get_task_templates(tier_name): - """ - Devuelve todas las plantillas correspondientes a un Partner Tier, - incluyendo las heredadas de sus tiers inferiores. - - El orden es desde el tier más básico hasta el seleccionado. - - Ejemplo: - Gold -> Silver -> Collaborator - - devuelve: - Collaborator tasks - Silver tasks - Gold tasks - """ - - tiers = [] - visited = set() - current_tier = tier_name - - while current_tier: - if current_tier in visited: - frappe.throw( - f"Se ha detectado una referencia circular " - f"en los Partner Tiers: {current_tier}" - ) - - visited.add(current_tier) - - tier = frappe.get_doc( - "Partner Tier", - current_tier, - ) - - tiers.append(tier) - - current_tier = tier.parent_tier - - # Actualmente: - # Gold -> Silver -> Collaborator - # - # Queremos: - # Collaborator -> Silver -> Gold - tiers.reverse() - - templates = [] - - for tier in tiers: - for template in tier.task_templates: - templates.append(template) - - return templates - - -def _get_organization_name(deal): - """ - Obtiene el nombre legible de la organización asociada al Deal. - """ - - if not deal.organization: - frappe.throw( - f"El Deal {deal.name} no tiene ninguna organización asociada." - ) - - organization_name = frappe.db.get_value( - "CRM Organization", - deal.organization, - "organization_name", - ) - - return organization_name or deal.organization - - -def _render_template(text, organization_name): - """ - Sustituye placeholders dentro de una plantilla. - - Actualmente: - {name} -> nombre de la organización - """ - - if not text: - return "" - - return text.replace( - PLACEHOLDER_NAME, - organization_name, - ) - - -def _get_partner_tier(deal): - """ - Obtiene el Partner Tier asignado al CRM Deal. - """ - - meta = frappe.get_meta("CRM Deal") - - tier_fields = [ - field - for field in meta.fields - if field.fieldtype == "Link" - and field.options == "Partner Tier" - ] - - if not tier_fields: - frappe.throw( - "CRM Deal no tiene ningún campo Link a Partner Tier." - ) - - tier_field = tier_fields[0] - - tier_name = deal.get( - tier_field.fieldname - ) - - if not tier_name: - frappe.throw( - f"El Deal {deal.name} no tiene ningún Partner Tier asignado." - ) - - return tier_name - - -def create_partner_task(deal_name, template): - """ - Crea una CRM Task a partir de una Partner Tier Task Template. - - Reglas: - - {name} se sustituye por el nombre de la organización. - - El título SIEMPRE empieza por [Nombre organización]. - - La tarea queda vinculada al CRM Deal. - - Ventaja Partner queda marcada. - - Si ya existe para ese Deal una ventaja con el mismo título - que NO está Done, no se vuelve a crear. - - Si la tarea anterior está Done, se puede volver a crear. - - Si la tarea anterior fue eliminada, se puede volver a crear. - """ - - deal = frappe.get_doc( - "CRM Deal", - deal_name, - ) - - organization_name = _get_organization_name( - deal - ) - - rendered_title = _render_template( - template.title_template, - organization_name, - ) - - rendered_description = _render_template( - template.description_template, - organization_name, - ) - - # Este prefijo es independiente del placeholder {name}. - # - # SIEMPRE: - # [Siemens] Título de la tarea - title = ( - f"[{organization_name}] " - f"{rendered_title}" - ).strip() - - # Evitar duplicados activos. - # - # Si ya existe una ventaja para este mismo Deal, - # con el mismo título, y NO está Done, - # no creamos otra. - existing_task = frappe.db.exists( - "CRM Task", - { - "reference_doctype": "CRM Deal", - "reference_docname": deal.name, - "title": title, - "custom_ventaja_partner": 1, - "status": ["!=", "Done"], - }, - ) - - if existing_task: - return None - - task = frappe.get_doc({ - "doctype": "CRM Task", - - "title": title, - - "description": rendered_description, - - "status": "Todo", - - "priority": template.priority or "Medium", - - "due_date": template.due_date, - - "reference_doctype": "CRM Deal", - - "reference_docname": deal.name, - - "custom_ventaja_partner": 1, - }) - - task.insert() - - return task - - -def create_partner_tasks(deal_name): - """ - Crea todas las tareas correspondientes al Partner Tier - de un Deal, incluyendo las tareas heredadas. - - Solo devuelve las tareas que realmente se han creado. - - Si una ventaja equivalente ya existe y no está Done, - se ignora. - """ - - deal = frappe.get_doc( - "CRM Deal", - deal_name, - ) - - tier_name = _get_partner_tier( - deal - ) - - templates = get_task_templates( - tier_name - ) - - created_tasks = [] - - for template in templates: - task = create_partner_task( - deal_name, - template, - ) - - if task: - created_tasks.append(task) - - return created_tasks - - -@frappe.whitelist() -def generate_partner_tasks(deal_name): - """ - Endpoint utilizado desde el botón 'Generar ventajas' - del CRM Deal. - - Devuelve un resultado estructurado para que el frontend - pueda informar claramente al usuario. - """ - - deal = frappe.get_doc( - "CRM Deal", - deal_name, - ) - - # El usuario debe poder acceder al Deal. - deal.check_permission("read") - - # Validación: organización - if not deal.organization: - return { - "success": False, - "message": ( - "No se han generado las ventajas: " - "el Deal no tiene una organización asignada." - ), - } - - # Validación: Partner Tier - try: - tier_name = _get_partner_tier( - deal - ) - - except frappe.ValidationError: - return { - "success": False, - "message": ( - "No se han generado las ventajas: " - "el Deal no tiene un Partner Tier asignado." - ), - } - - # Validación: el tier tiene tareas - templates = get_task_templates( - tier_name - ) - - if not templates: - return { - "success": False, - "message": ( - f"No se han generado las ventajas: " - f"el tier {tier_name} no tiene tareas configuradas." - ), - } - - # Generación real - tasks = create_partner_tasks( - deal_name - ) - - organization_name = _get_organization_name( - deal - ) - - # Ninguna tarea nueva porque todas las ventajas - # ya tienen una tarea activa. - if not tasks: - return { - "success": True, - "message": ( - f"No se han creado nuevas ventajas para " - f"{organization_name}: todas las ventajas del tier " - f"{tier_name} ya tienen una tarea activa." - ), - "created_count": 0, - "organization": organization_name, - "tier": tier_name, - "tasks": [], - } - - return { - "success": True, - "message": ( - f"Se han generado correctamente " - f"{len(tasks)} nuevas ventajas para " - f"{organization_name} ({tier_name})." - ), - "created_count": len(tasks), - "organization": organization_name, - "tier": tier_name, - "tasks": [ - task.name - for task in tasks - ], - } \ No newline at end of file diff --git a/aura/services/partner_tier.py b/aura/services/partner_tier.py deleted file mode 100644 index e32b8e9..0000000 --- a/aura/services/partner_tier.py +++ /dev/null @@ -1,65 +0,0 @@ -import frappe - - -def get_tier_task_templates(tier_name): - """ - Devuelve todas las plantillas de tareas asociadas a un Partner Tier, - incluyendo las heredadas de sus tiers padre. - - El orden es desde el tier más básico hasta el seleccionado. - - Ejemplo: - Gold -> Silver -> Collaborator - - devuelve: - Collaborator tasks - Silver tasks - Gold tasks - """ - - tiers = [] - visited = set() - - current_tier = tier_name - - while current_tier: - if current_tier in visited: - frappe.throw( - f"Se ha detectado un ciclo en los Partner Tiers: {current_tier}" - ) - - visited.add(current_tier) - - tier = frappe.get_doc("Partner Tier", current_tier) - tiers.append(tier) - - current_tier = tier.parent_tier - - # Ahora mismo tenemos: - # Gold, Silver, Collaborator - # - # Lo invertimos para obtener: - # Collaborator, Silver, Gold - - - print(tiers) - - tiers.reverse() - - templates = [] - - for tier in tiers: - print(tier) - print(tier.task_templates) - for template in tier.task_templates: - templates.append( - { - "tier": tier.name, - "title_template": template.title_template, - "description_template": template.description_template, - "priority": template.priority, - "due_date": template.due_date, - } - ) - - return templates \ No newline at end of file diff --git a/aura/templates/__init__.py b/aura/templates/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/aura/templates/pages/__init__.py b/aura/templates/pages/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/license.txt b/license.txt index 8aa2645..0f88046 100644 --- a/license.txt +++ b/license.txt @@ -1,6 +1,6 @@ MIT License -Copyright (c) [year] [fullname] +Copyright (c) 2026 Javier Ribal del Río Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/package.json b/package.json new file mode 100644 index 0000000..94d0266 --- /dev/null +++ b/package.json @@ -0,0 +1,35 @@ +{ + "name": "aura-twenty", + "version": "0.1.0", + "license": "MIT", + "engines": { + "node": "^24.5.0", + "npm": "please-use-yarn", + "yarn": ">=4.0.2" + }, + "keywords": [], + "packageManager": "yarn@4.13.0", + "scripts": { + "twenty": "twenty", + "lint": "oxlint -c .oxlintrc.json .", + "lint:fix": "oxlint --fix -c .oxlintrc.json .", + "typecheck": "tsgo --noEmit -p tsconfig.spec.json", + "test": "vitest run", + "test:watch": "vitest", + "test:unit": "vitest run --config vitest.unit.config.ts" + }, + "devDependencies": { + "@types/node": "^24.7.2", + "@types/react": "^19.0.0", + "@typescript/native-preview": "^7.0.0-dev.20260116.1", + "oxlint": "^0.16.0", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "twenty-client-sdk": "2.35.1", + "twenty-sdk": "2.35.1", + "twenty-ui": "1.0.0-alpha.1", + "typescript": "^5.9.3", + "vite-tsconfig-paths": "^4.2.1", + "vitest": "^4.0.0" + } +} diff --git a/public/logo.svg b/public/logo.svg new file mode 100644 index 0000000..639ff19 --- /dev/null +++ b/public/logo.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/pyproject.toml b/pyproject.toml deleted file mode 100644 index 6a5f9a2..0000000 --- a/pyproject.toml +++ /dev/null @@ -1,65 +0,0 @@ -[project] -name = "aura" -authors = [ - { name = "Javier Ribal del Río", email = "partners@hyperloopupv.com"} -] -description = "Advanced Unified Relations Application" -requires-python = ">=3.14" -readme = "README.md" -dynamic = ["version"] -dependencies = [ - # "frappe~=16.0.0" # Installed and managed by bench. -] - -[build-system] -requires = ["flit_core >=3.4,<4"] -build-backend = "flit_core.buildapi" - -# These dependencies are only installed when developer mode is enabled -[tool.bench.dev-dependencies] -# package_name = "~=1.1.0" - -# These apt dependencies will be installed from Ubuntu repositories when you host your app on Frappe Cloud -[deploy.dependencies.apt] -packages = [] - -[tool.ruff] -line-length = 110 -target-version = "py314" - -[tool.ruff.lint] -select = [ - "F", - "E", - "W", - "I", - "UP", - "B", - "RUF", -] -ignore = [ - "B017", # assertRaises(Exception) - should be more specific - "B018", # useless expression, not assigned to anything - "B023", # function doesn't bind loop variable - will have last iteration's value - "B904", # raise inside except without from - "E101", # indentation contains mixed spaces and tabs - "E402", # module level import not at top of file - "E501", # line too long - "E741", # ambiguous variable name - "F401", # "unused" imports - "F403", # can't detect undefined names from * import - "F405", # can't detect undefined names from * import - "F722", # syntax error in forward type annotation - "W191", # indentation contains tabs - "UP030", # Use implicit references for positional format fields (translations) - "UP031", # Use format specifiers instead of percent format - "UP032", # Use f-string instead of `format` call (translations) - "UP037", # quoted annotations - "UP040", # Use type aliases instead of type annotations -] -typing-modules = ["frappe.types.DF"] - -[tool.ruff.format] -quote-style = "double" -indent-style = "tab" -docstring-code-format = true diff --git a/repo-README.md b/repo-README.md new file mode 100644 index 0000000..16cfec4 --- /dev/null +++ b/repo-README.md @@ -0,0 +1,40 @@ +### AURA + +Advanced Unified Relations Application + +### Installation + +You can install this app using the [bench](https://github.com/frappe/bench) CLI: + +```bash +cd $PATH_TO_YOUR_BENCH +bench get-app $URL_OF_THIS_REPO --branch version-16 +bench install-app aura +``` + +### Contributing + +This app uses `pre-commit` for code formatting and linting. Please [install pre-commit](https://pre-commit.com/#installation) and enable it for this repository: + +```bash +cd apps/aura +pre-commit install +``` + +Pre-commit is configured to use the following tools for checking and formatting your code: + +- ruff +- eslint +- prettier +- pyupgrade +### CI + +This app can use GitHub Actions for CI. The following workflows are configured: + +- CI: Installs this app and runs unit tests on every push to `develop` branch. +- Linters: Runs [Frappe Semgrep Rules](https://github.com/frappe/semgrep-rules) and [pip-audit](https://pypi.org/project/pip-audit/) on every pull request. + + +### License + +mit diff --git a/src/__tests__/application-config.test.ts b/src/__tests__/application-config.test.ts new file mode 100644 index 0000000..fdc1c62 --- /dev/null +++ b/src/__tests__/application-config.test.ts @@ -0,0 +1,14 @@ +import { + APP_DESCRIPTION, + APP_DISPLAY_NAME, + APPLICATION_UNIVERSAL_IDENTIFIER, +} from 'src/constants/universal-identifiers'; +import { describe, expect, it } from 'vitest'; + +describe('application identifiers', () => { + it('should expose the application metadata constants', () => { + expect(APP_DISPLAY_NAME).toBeTruthy(); + expect(typeof APP_DESCRIPTION).toBe('string'); + expect(APPLICATION_UNIVERSAL_IDENTIFIER).toBeTruthy(); + }); +}); diff --git a/src/__tests__/global-setup.ts b/src/__tests__/global-setup.ts new file mode 100644 index 0000000..e0f8c23 --- /dev/null +++ b/src/__tests__/global-setup.ts @@ -0,0 +1,87 @@ +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; + +import { appDevOnce, appUninstall } from 'twenty-sdk/cli'; + +const APP_PATH = process.cwd(); +const CONFIG_DIR = path.join(os.homedir(), '.twenty'); + +function validateEnv(): { apiUrl: string; apiKey: string } { + const apiUrl = process.env.TWENTY_API_URL; + const apiKey = process.env.TWENTY_API_KEY; + + if (!apiUrl || !apiKey) { + throw new Error( + 'TWENTY_API_URL and TWENTY_API_KEY must be set.\n' + + 'Start a local server: yarn twenty docker:start\n' + + 'Or set them in vitest env config.', + ); + } + + return { apiUrl, apiKey }; +} + +async function checkServer(apiUrl: string) { + let response: Response; + + try { + response = await fetch(`${apiUrl}/healthz`); + } catch { + throw new Error( + `Twenty server is not reachable at ${apiUrl}. ` + + 'Make sure the server is running before executing integration tests.', + ); + } + + if (!response.ok) { + throw new Error(`Server at ${apiUrl} returned ${response.status}`); + } +} + +function writeConfig(apiUrl: string, apiKey: string) { + const payload = JSON.stringify( + { + remotes: { + local: { apiUrl, apiKey, accessToken: apiKey }, + }, + defaultRemote: 'local', + }, + null, + 2, + ); + + fs.mkdirSync(CONFIG_DIR, { recursive: true }); + fs.writeFileSync(path.join(CONFIG_DIR, 'config.test.json'), payload); +} + +export async function setup() { + const { apiUrl, apiKey } = validateEnv(); + + await checkServer(apiUrl); + + writeConfig(apiUrl, apiKey); + + await appUninstall({ appPath: APP_PATH }).catch(() => {}); + + const result = await appDevOnce({ + appPath: APP_PATH, + onProgress: (message: string) => console.log(`[dev] ${message}`), + }); + + if (!result.success) { + throw new Error( + `Dev sync failed: ${result.error?.message ?? 'Unknown error'}`, + ); + } +} + +export async function teardown() { + const uninstallResult = await appUninstall({ appPath: APP_PATH }); + + if (!uninstallResult.success) { + console.warn( + `App uninstall failed: ${uninstallResult.error?.message ?? 'Unknown error'}`, + ); + } +} diff --git a/src/__tests__/schema.integration-test.ts b/src/__tests__/schema.integration-test.ts new file mode 100644 index 0000000..31007e2 --- /dev/null +++ b/src/__tests__/schema.integration-test.ts @@ -0,0 +1,46 @@ +import { CoreApiClient } from 'twenty-client-sdk/core'; +import { MetadataApiClient } from 'twenty-client-sdk/metadata'; +import { APPLICATION_UNIVERSAL_IDENTIFIER } from 'src/constants/universal-identifiers'; +import { describe, expect, it } from 'vitest'; + +describe('App installation', () => { + it('should find the installed app in the applications list', async () => { + const client = new MetadataApiClient(); + + const result = await client.query({ + findManyApplications: { + id: true, + name: true, + universalIdentifier: true, + }, + }); + + const app = result.findManyApplications.find( + (a: { universalIdentifier: string }) => + a.universalIdentifier === APPLICATION_UNIVERSAL_IDENTIFIER, + ); + + expect(app).toBeDefined(); + }); +}); + +describe('CoreApiClient', () => { + it('should support CRUD on standard objects', async () => { + const client = new CoreApiClient(); + + const created = await client.mutation({ + createNote: { + __args: { data: { title: 'Integration test note' } }, + id: true, + }, + }); + expect(created.createNote.id).toBeDefined(); + + await client.mutation({ + destroyNote: { + __args: { id: created.createNote.id }, + id: true, + }, + }); + }); +}); diff --git a/src/application-config.ts b/src/application-config.ts new file mode 100644 index 0000000..088f9d1 --- /dev/null +++ b/src/application-config.ts @@ -0,0 +1,13 @@ +import { defineApplication } from 'twenty-sdk/define'; + +import { + APP_DESCRIPTION, + APP_DISPLAY_NAME, + APPLICATION_UNIVERSAL_IDENTIFIER, +} from 'src/constants/universal-identifiers'; + +export default defineApplication({ + universalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER, + displayName: APP_DISPLAY_NAME, + description: APP_DESCRIPTION, +}); diff --git a/src/constants/universal-identifiers.ts b/src/constants/universal-identifiers.ts new file mode 100644 index 0000000..41b9fe2 --- /dev/null +++ b/src/constants/universal-identifiers.ts @@ -0,0 +1,11 @@ +export const APP_DISPLAY_NAME = 'AURA'; +export const APP_DESCRIPTION = 'Advanced Unified Relation App for Hyperloop UPV'; +export const APPLICATION_UNIVERSAL_IDENTIFIER = 'dc45f091-21c5-4d98-b525-7935a19c6500'; +export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER = '0d65cf1a-722e-40dd-9de1-0b33d3a8a734'; +export const MAIN_PAGE_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER = + 'ae34a6e6-0399-4947-865d-420661ad34cb'; +export const MAIN_PAGE_LAYOUT_UNIVERSAL_IDENTIFIER = '5cbe1f2a-a16b-4299-861e-e97effbf5609'; +export const MAIN_PAGE_LAYOUT_TAB_UNIVERSAL_IDENTIFIER = '95a9c5b6-5292-4c88-a1dd-58b6e0d215bf'; +export const MAIN_PAGE_WIDGET_UNIVERSAL_IDENTIFIER = '154165d3-c134-4445-91c5-2906ebd9bc49'; +export const MAIN_PAGE_NAVIGATION_MENU_ITEM_UNIVERSAL_IDENTIFIER = + '2ed9cf86-eb5d-4ac8-9d1e-cf0fa1a3446d'; diff --git a/src/default-role.ts b/src/default-role.ts new file mode 100644 index 0000000..a6abf65 --- /dev/null +++ b/src/default-role.ts @@ -0,0 +1,16 @@ +import { defineApplicationRole } from 'twenty-sdk/define'; + +import { + APP_DISPLAY_NAME, + DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, +} from 'src/constants/universal-identifiers'; + +export default defineApplicationRole({ + universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, + label: `${APP_DISPLAY_NAME} default function role`, + description: `${APP_DISPLAY_NAME} default function role`, + canReadAllObjectRecords: true, + canUpdateAllObjectRecords: true, + canSoftDeleteAllObjectRecords: true, + canDestroyAllObjectRecords: false, +}); diff --git a/src/fields/calls-on-company.field.ts b/src/fields/calls-on-company.field.ts new file mode 100644 index 0000000..f432d71 --- /dev/null +++ b/src/fields/calls-on-company.field.ts @@ -0,0 +1,30 @@ +import { + defineField, + FieldType, + RelationType, + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, +} from 'twenty-sdk/define'; + +import { CALL_UNIVERSAL_IDENTIFIER } from '../objects/call.object'; + +import { + CALLS_ON_COMPANY_FIELD_UNIVERSAL_IDENTIFIER, + COMPANY_ON_CALL_FIELD_UNIVERSAL_IDENTIFIER, +} from './company-on-call.field'; + +export default defineField({ + universalIdentifier: CALLS_ON_COMPANY_FIELD_UNIVERSAL_IDENTIFIER, + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier, + type: FieldType.RELATION, + name: 'calls', + label: 'Llamadas', + description: 'Llamadas e interacciones relacionadas con esta empresa.', + relationTargetObjectMetadataUniversalIdentifier: + CALL_UNIVERSAL_IDENTIFIER, + relationTargetFieldMetadataUniversalIdentifier: + COMPANY_ON_CALL_FIELD_UNIVERSAL_IDENTIFIER, + universalSettings: { + relationType: RelationType.ONE_TO_MANY, + }, +}); diff --git a/src/fields/calls-on-opportunity.field.ts b/src/fields/calls-on-opportunity.field.ts new file mode 100644 index 0000000..b214ca0 --- /dev/null +++ b/src/fields/calls-on-opportunity.field.ts @@ -0,0 +1,30 @@ +import { + defineField, + FieldType, + RelationType, + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, +} from 'twenty-sdk/define'; + +import { CALL_UNIVERSAL_IDENTIFIER } from '../objects/call.object'; + +import { + CALLS_ON_OPPORTUNITY_FIELD_UNIVERSAL_IDENTIFIER, + OPPORTUNITY_ON_CALL_FIELD_UNIVERSAL_IDENTIFIER, +} from './opportunity-on-call.field'; + +export default defineField({ + universalIdentifier: CALLS_ON_OPPORTUNITY_FIELD_UNIVERSAL_IDENTIFIER, + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.opportunity.universalIdentifier, + type: FieldType.RELATION, + name: 'calls', + label: 'Llamadas', + description: 'Llamadas e interacciones relacionadas con esta oportunidad.', + relationTargetObjectMetadataUniversalIdentifier: + CALL_UNIVERSAL_IDENTIFIER, + relationTargetFieldMetadataUniversalIdentifier: + OPPORTUNITY_ON_CALL_FIELD_UNIVERSAL_IDENTIFIER, + universalSettings: { + relationType: RelationType.ONE_TO_MANY, + }, +}); diff --git a/src/fields/calls-on-person.field.ts b/src/fields/calls-on-person.field.ts new file mode 100644 index 0000000..ea8f42d --- /dev/null +++ b/src/fields/calls-on-person.field.ts @@ -0,0 +1,30 @@ +import { + defineField, + FieldType, + RelationType, + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, +} from 'twenty-sdk/define'; + +import { CALL_UNIVERSAL_IDENTIFIER } from '../objects/call.object'; + +import { + CALLS_ON_PERSON_FIELD_UNIVERSAL_IDENTIFIER, + PERSON_ON_CALL_FIELD_UNIVERSAL_IDENTIFIER, +} from './person-on-call.field'; + +export default defineField({ + universalIdentifier: CALLS_ON_PERSON_FIELD_UNIVERSAL_IDENTIFIER, + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.universalIdentifier, + type: FieldType.RELATION, + name: 'calls', + label: 'Llamadas', + description: 'Llamadas e interacciones relacionadas con este contacto.', + relationTargetObjectMetadataUniversalIdentifier: + CALL_UNIVERSAL_IDENTIFIER, + relationTargetFieldMetadataUniversalIdentifier: + PERSON_ON_CALL_FIELD_UNIVERSAL_IDENTIFIER, + universalSettings: { + relationType: RelationType.ONE_TO_MANY, + }, +}); diff --git a/src/fields/company-is-upv.field.ts b/src/fields/company-is-upv.field.ts new file mode 100644 index 0000000..e0ce304 --- /dev/null +++ b/src/fields/company-is-upv.field.ts @@ -0,0 +1,25 @@ +import { + defineField, + FieldType, + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, +} from 'twenty-sdk/define'; + +export const COMPANY_IS_UPV_FIELD_UNIVERSAL_IDENTIFIER = + '89088f89-ba62-49a0-9aae-0ffb521c54c4'; + +export default defineField({ + universalIdentifier: COMPANY_IS_UPV_FIELD_UNIVERSAL_IDENTIFIER, + + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier, + + type: FieldType.BOOLEAN, + + name: 'isUpv', + label: 'UPV', + + description: + 'Indica si la organización pertenece o está vinculada a la UPV.', + icon: 'IconBuildingCommunity', + defaultValue: false, +}); diff --git a/src/fields/company-nif.field.ts b/src/fields/company-nif.field.ts new file mode 100644 index 0000000..b3eb1a9 --- /dev/null +++ b/src/fields/company-nif.field.ts @@ -0,0 +1,23 @@ +import { + defineField, + FieldType, + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, +} from 'twenty-sdk/define'; + +export const COMPANY_NIF_FIELD_UNIVERSAL_IDENTIFIER = + 'cdff2d0b-96f6-43bd-9da9-b2de19b4a52b'; + +export default defineField({ + universalIdentifier: COMPANY_NIF_FIELD_UNIVERSAL_IDENTIFIER, + + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier, + + type: FieldType.TEXT, + + name: 'nif', + label: 'NIF', + icon: 'IconId', + description: + 'Número de identificación fiscal de la organización.', +}); diff --git a/src/fields/company-on-call.field.ts b/src/fields/company-on-call.field.ts new file mode 100644 index 0000000..65cafb5 --- /dev/null +++ b/src/fields/company-on-call.field.ts @@ -0,0 +1,34 @@ +import { + defineField, + FieldType, + OnDeleteAction, + RelationType, + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, +} from 'twenty-sdk/define'; + +import { CALL_UNIVERSAL_IDENTIFIER } from '../objects/call.object'; + +export const COMPANY_ON_CALL_FIELD_UNIVERSAL_IDENTIFIER = + 'e6491e22-a0e4-4ad7-b271-6c75a53c2b06'; + +export const CALLS_ON_COMPANY_FIELD_UNIVERSAL_IDENTIFIER = + '7fdd6802-38e8-4ba9-ba32-2a446f071ab4'; + +export default defineField({ + universalIdentifier: COMPANY_ON_CALL_FIELD_UNIVERSAL_IDENTIFIER, + objectUniversalIdentifier: CALL_UNIVERSAL_IDENTIFIER, + type: FieldType.RELATION, + name: 'company', + label: 'Empresa', + description: 'Empresa relacionada con la llamada o interacción.', + relationTargetObjectMetadataUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier, + relationTargetFieldMetadataUniversalIdentifier: + CALLS_ON_COMPANY_FIELD_UNIVERSAL_IDENTIFIER, + isNullable: true, + universalSettings: { + relationType: RelationType.MANY_TO_ONE, + onDelete: OnDeleteAction.SET_NULL, + joinColumnName: 'companyId', + }, +}); diff --git a/src/fields/company-sector.field.ts b/src/fields/company-sector.field.ts new file mode 100644 index 0000000..2ff6742 --- /dev/null +++ b/src/fields/company-sector.field.ts @@ -0,0 +1,98 @@ +import { + defineField, + FieldType, + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, +} from 'twenty-sdk/define'; + +export const COMPANY_SECTOR_FIELD_UNIVERSAL_IDENTIFIER = + 'd54dd493-df91-476d-9461-47022a1589ee'; + +export default defineField({ + universalIdentifier: COMPANY_SECTOR_FIELD_UNIVERSAL_IDENTIFIER, + + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier, + + type: FieldType.SELECT, + + name: 'sector', + label: 'Sector', + + description: + 'Sector principal de actividad de la organización.', + icon: 'IconCategory', + options: [ + { + value: 'ELECTROMAGNETICS', + label: 'Electromagnetics', + position: 0, + color: 'blue', + }, + { + value: 'ELECTRONICA', + label: 'Electrónica', + position: 1, + color: 'purple', + }, + { + value: 'EMPRESAS_LOCALES', + label: 'Empresas Locales', + position: 2, + color: 'green', + }, + { + value: 'EQUIPACIONES', + label: 'Equipaciones', + position: 3, + color: 'orange', + }, + { + value: 'MERCHANDISING', + label: 'Merchandising', + position: 4, + color: 'pink', + }, + { + value: 'MATERIALES', + label: 'Materiales', + position: 5, + color: 'yellow', + }, + { + value: 'LIQUIDO', + label: 'Líquido', + position: 6, + color: 'cyan', + }, + { + value: 'INFORMATICA', + label: 'Informática', + position: 7, + color: 'blue', + }, + { + value: 'LOGISTICA', + label: 'Logística', + position: 8, + color: 'gray', + }, + { + value: 'MECANIZADOS', + label: 'Mecanizados', + position: 9, + color: 'orange', + }, + { + value: 'IMPRESION_3D', + label: 'Impresión 3D', + position: 10, + color: 'purple', + }, + { + value: 'MISCELANEA', + label: 'Miscelánea', + position: 11, + color: 'gray', + }, + ], +}); diff --git a/src/fields/company-specialty.field.ts b/src/fields/company-specialty.field.ts new file mode 100644 index 0000000..73a397d --- /dev/null +++ b/src/fields/company-specialty.field.ts @@ -0,0 +1,23 @@ +import { + defineField, + FieldType, + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, +} from 'twenty-sdk/define'; + +export const COMPANY_SPECIALTY_FIELD_UNIVERSAL_IDENTIFIER = + '8cf64d40-4040-4b0c-a0ab-97236309a096'; + +export default defineField({ + universalIdentifier: COMPANY_SPECIALTY_FIELD_UNIVERSAL_IDENTIFIER, + + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier, + + type: FieldType.TEXT, + + name: 'specialty', + label: 'Especialidad', + icon: 'IconTools', + description: + 'Especialidad o ámbito técnico principal de la organización.', +}); diff --git a/src/fields/opportunity-on-call.field.ts b/src/fields/opportunity-on-call.field.ts new file mode 100644 index 0000000..de463ae --- /dev/null +++ b/src/fields/opportunity-on-call.field.ts @@ -0,0 +1,34 @@ +import { + defineField, + FieldType, + OnDeleteAction, + RelationType, + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, +} from 'twenty-sdk/define'; + +import { CALL_UNIVERSAL_IDENTIFIER } from '../objects/call.object'; + +export const OPPORTUNITY_ON_CALL_FIELD_UNIVERSAL_IDENTIFIER = + '6e3b2301-3367-4351-a914-591ae3a99e7f'; + +export const CALLS_ON_OPPORTUNITY_FIELD_UNIVERSAL_IDENTIFIER = + '870cf299-1b23-4af1-8b7e-8fa6bdda9d9d'; + +export default defineField({ + universalIdentifier: OPPORTUNITY_ON_CALL_FIELD_UNIVERSAL_IDENTIFIER, + objectUniversalIdentifier: CALL_UNIVERSAL_IDENTIFIER, + type: FieldType.RELATION, + name: 'opportunity', + label: 'Oportunidad', + description: 'Oportunidad relacionada con la llamada o interacción.', + relationTargetObjectMetadataUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.opportunity.universalIdentifier, + relationTargetFieldMetadataUniversalIdentifier: + CALLS_ON_OPPORTUNITY_FIELD_UNIVERSAL_IDENTIFIER, + isNullable: true, + universalSettings: { + relationType: RelationType.MANY_TO_ONE, + onDelete: OnDeleteAction.SET_NULL, + joinColumnName: 'opportunityId', + }, +}); diff --git a/src/fields/opportunity-products-json.field.ts b/src/fields/opportunity-products-json.field.ts new file mode 100644 index 0000000..1278302 --- /dev/null +++ b/src/fields/opportunity-products-json.field.ts @@ -0,0 +1,26 @@ +import { + defineField, + FieldType, + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, +} from 'twenty-sdk/define'; + +export const OPPORTUNITY_PRODUCTS_JSON_FIELD_UNIVERSAL_IDENTIFIER = + 'a3e850d0-3754-458b-ac90-e30e1bf967b8'; + +export default defineField({ + universalIdentifier: + OPPORTUNITY_PRODUCTS_JSON_FIELD_UNIVERSAL_IDENTIFIER, + + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.opportunity.universalIdentifier, + + type: FieldType.RAW_JSON, + + name: 'sponsorshipProducts', + label: 'Products', + + description: + 'Products and services obtained through the sponsorship, stored as structured JSON.', + + icon: 'IconPackage', +}); diff --git a/src/fields/opportunity-total-value.field.ts b/src/fields/opportunity-total-value.field.ts new file mode 100644 index 0000000..8146d6c --- /dev/null +++ b/src/fields/opportunity-total-value.field.ts @@ -0,0 +1,26 @@ +import { + defineField, + FieldType, + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, +} from 'twenty-sdk/define'; + +export const OPPORTUNITY_TOTAL_VALUE_FIELD_UNIVERSAL_IDENTIFIER = + '7e555de8-3841-4b7b-971d-a35c3f949bad'; + +export default defineField({ + universalIdentifier: + OPPORTUNITY_TOTAL_VALUE_FIELD_UNIVERSAL_IDENTIFIER, + + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.opportunity.universalIdentifier, + + type: FieldType.CURRENCY, + + name: 'totalValue', + label: 'Total Value', + + description: + 'Total value of all products and services obtained through the sponsorship.', + + icon: 'IconCurrencyEuro', +}); diff --git a/src/fields/person-on-call.field.ts b/src/fields/person-on-call.field.ts new file mode 100644 index 0000000..287f5bd --- /dev/null +++ b/src/fields/person-on-call.field.ts @@ -0,0 +1,34 @@ +import { + defineField, + FieldType, + OnDeleteAction, + RelationType, + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, +} from 'twenty-sdk/define'; + +import { CALL_UNIVERSAL_IDENTIFIER } from '../objects/call.object'; + +export const PERSON_ON_CALL_FIELD_UNIVERSAL_IDENTIFIER = + 'de413062-ff3a-42bd-a6b3-241f69f7203b'; + +export const CALLS_ON_PERSON_FIELD_UNIVERSAL_IDENTIFIER = + '26ecd011-b4e2-48d4-9948-63e6696d8550'; + +export default defineField({ + universalIdentifier: PERSON_ON_CALL_FIELD_UNIVERSAL_IDENTIFIER, + objectUniversalIdentifier: CALL_UNIVERSAL_IDENTIFIER, + type: FieldType.RELATION, + name: 'person', + label: 'Contacto', + description: 'Persona con la que se realizó la llamada o interacción.', + relationTargetObjectMetadataUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.universalIdentifier, + relationTargetFieldMetadataUniversalIdentifier: + CALLS_ON_PERSON_FIELD_UNIVERSAL_IDENTIFIER, + isNullable: true, + universalSettings: { + relationType: RelationType.MANY_TO_ONE, + onDelete: OnDeleteAction.SET_NULL, + joinColumnName: 'personId', + }, +}); diff --git a/src/fields/season-on-opportunity.field.ts b/src/fields/season-on-opportunity.field.ts new file mode 100644 index 0000000..700f1d4 --- /dev/null +++ b/src/fields/season-on-opportunity.field.ts @@ -0,0 +1,41 @@ +import { + defineField, + FieldType, + RelationType, + OnDeleteAction, + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, +} from 'twenty-sdk/define'; + +import { SEASON_UNIVERSAL_IDENTIFIER } from '../objects/season.object'; + +import { SEASON_SPONSORSHIPS_FIELD_ID } from './sponsorships-on-season.field'; + +export const OPPORTUNITY_SEASON_FIELD_ID = + '340b16d2-7d22-4e3c-b668-3e18035ad73a'; + +export default defineField({ + universalIdentifier: OPPORTUNITY_SEASON_FIELD_ID, + + objectUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.opportunity.universalIdentifier, + + type: FieldType.RELATION, + + name: 'season', + label: 'Season', + icon: 'IconCalendar', + + isNullable: true, + + relationTargetObjectMetadataUniversalIdentifier: + SEASON_UNIVERSAL_IDENTIFIER, + + relationTargetFieldMetadataUniversalIdentifier: + SEASON_SPONSORSHIPS_FIELD_ID, + + universalSettings: { + relationType: RelationType.MANY_TO_ONE, + onDelete: OnDeleteAction.RESTRICT, + joinColumnName: 'seasonId', + }, +}); \ No newline at end of file diff --git a/src/fields/sponsorships-on-season.field.ts b/src/fields/sponsorships-on-season.field.ts new file mode 100644 index 0000000..1687368 --- /dev/null +++ b/src/fields/sponsorships-on-season.field.ts @@ -0,0 +1,35 @@ +import { + defineField, + FieldType, + RelationType, + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, +} from 'twenty-sdk/define'; + +import { SEASON_UNIVERSAL_IDENTIFIER } from '../objects/season.object'; + +import { OPPORTUNITY_SEASON_FIELD_ID } from './season-on-opportunity.field'; + +export const SEASON_SPONSORSHIPS_FIELD_ID = + '6796d0ad-67d5-4877-a28b-66b88c112c02'; + +export default defineField({ + universalIdentifier: SEASON_SPONSORSHIPS_FIELD_ID, + + objectUniversalIdentifier: SEASON_UNIVERSAL_IDENTIFIER, + + type: FieldType.RELATION, + + name: 'sponsorships', + label: 'Sponsorships', + icon: 'IconBriefcase', + + relationTargetObjectMetadataUniversalIdentifier: + STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.opportunity.universalIdentifier, + + relationTargetFieldMetadataUniversalIdentifier: + OPPORTUNITY_SEASON_FIELD_ID, + + universalSettings: { + relationType: RelationType.ONE_TO_MANY, + }, +}); \ No newline at end of file diff --git a/src/front-components/main-page.tsx b/src/front-components/main-page.tsx new file mode 100644 index 0000000..0737013 --- /dev/null +++ b/src/front-components/main-page.tsx @@ -0,0 +1,291 @@ +import { defineFrontComponent } from 'twenty-sdk/define'; +import { useState } from 'react'; +import { Avatar } from 'twenty-ui/data-display'; +import { + IconBox, + IconHierarchy, + IconLayout, + IconSettingsAutomation, +} from 'twenty-ui/icon'; + +import { + APP_DISPLAY_NAME, + MAIN_PAGE_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER, +} from 'src/constants/universal-identifiers'; + +const DOCS_BASE_URL = 'https://docs.twenty.com/developers/extend/apps'; + +const CATEGORIES = [ + { + title: 'Data model', + color: '#73D08D', + items: [ + { label: 'CUSTOM OBJECT', href: `${DOCS_BASE_URL}/data/objects` }, + { + label: 'CUSTOM FIELDS', + href: `${DOCS_BASE_URL}/data/extending-objects`, + }, + ], + rotation: '2.4deg', + }, + { + title: 'Logic', + color: '#F4D345', + items: [ + { + label: 'TOOLS', + href: `${DOCS_BASE_URL}/logic/logic-functions`, + }, + { + label: 'LOGIC FUNCTION', + href: `${DOCS_BASE_URL}/logic/logic-functions`, + }, + { + label: 'SKILLS', + href: `${DOCS_BASE_URL}/logic/skills-and-agents`, + }, + ], + rotation: '0deg', + }, + { + title: 'Layout', + color: '#C4A2E0', + items: [ + { label: 'VIEWS', href: `${DOCS_BASE_URL}/layout/views` }, + { label: 'WIDGETS', href: `${DOCS_BASE_URL}/layout/page-layouts` }, + { + label: 'LAYOUT PAGES', + href: `${DOCS_BASE_URL}/layout/page-layouts`, + }, + { + label: 'COMMANDS', + href: `${DOCS_BASE_URL}/layout/command-menu-items`, + }, + ], + rotation: '-2.8deg', + }, +] as const; + +const ArrowUpRight = ({ color = '#999' }: { color?: string }) => ( + + + +); + +const CategoryCard = ({ + title, + color, + items, + rotation, +}: { + title: string; + color: string; + items: ReadonlyArray<{ label: string; href: string }>; + rotation: string; +}) => { + const [hoveredItem, setHoveredItem] = useState(null); + + const CategoryIcon = () => { + if (title === 'Data model') { + return ; + } + if (title === 'Logic') { + return ; + } + if (title === 'Layout') { + return ; + } + }; + + return ( + + ); +}; + +const MainPage = () => { + return ( + + ); +}; + +export default defineFrontComponent({ + universalIdentifier: MAIN_PAGE_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER, + name: APP_DISPLAY_NAME, + description: `${APP_DISPLAY_NAME} front component displaying the app logo and name`, + component: MainPage, +}); diff --git a/src/navigation-menu-items/main-page.navigation-menu-item.ts b/src/navigation-menu-items/main-page.navigation-menu-item.ts new file mode 100644 index 0000000..cccb056 --- /dev/null +++ b/src/navigation-menu-items/main-page.navigation-menu-item.ts @@ -0,0 +1,19 @@ +import { + defineNavigationMenuItem, + NavigationMenuItemType, +} from 'twenty-sdk/define'; + +import { + APP_DISPLAY_NAME, + MAIN_PAGE_LAYOUT_UNIVERSAL_IDENTIFIER, + MAIN_PAGE_NAVIGATION_MENU_ITEM_UNIVERSAL_IDENTIFIER, +} from 'src/constants/universal-identifiers'; + +export default defineNavigationMenuItem({ + universalIdentifier: MAIN_PAGE_NAVIGATION_MENU_ITEM_UNIVERSAL_IDENTIFIER, + name: APP_DISPLAY_NAME, + icon: 'IconFile', + position: -1, + type: NavigationMenuItemType.PAGE_LAYOUT, + pageLayoutUniversalIdentifier: MAIN_PAGE_LAYOUT_UNIVERSAL_IDENTIFIER, +}); diff --git a/src/objects/call.object.ts b/src/objects/call.object.ts new file mode 100644 index 0000000..383b24e --- /dev/null +++ b/src/objects/call.object.ts @@ -0,0 +1,179 @@ +import { + defineObject, + FieldType, +} from 'twenty-sdk/define'; + +export const CALL_UNIVERSAL_IDENTIFIER = + '6b8ca70c-9517-47bd-a900-f11fb8525bc4'; + +export default defineObject({ + universalIdentifier: CALL_UNIVERSAL_IDENTIFIER, + + nameSingular: 'call', + namePlural: 'calls', + + labelSingular: 'Llamada', + labelPlural: 'Llamadas', + + icon: 'IconPhone', + + fields: [ + { + universalIdentifier: 'fe86a73c-ec38-4874-8798-f7431b156d41', + + type: FieldType.TEXT, + + name: 'name', + label: 'Nombre', + + description: 'Nombre o título de la interacción.', + + icon: 'IconPhone', + }, + + { + universalIdentifier: '62809899-a6ce-4640-9009-96730a4763da', + + type: FieldType.DATE_TIME, + + name: 'calledAt', + label: 'Fecha y hora', + + description: 'Fecha y hora en la que tuvo lugar la interacción.', + + icon: 'IconCalendarTime', + }, + + { + universalIdentifier: 'a22cf587-c77e-4167-a06e-76879244656d', + + type: FieldType.SELECT, + + name: 'direction', + label: 'Dirección', + + description: 'Indica si el contacto fue iniciado por AURA o por la organización.', + + icon: 'IconArrowsExchange', + + options: [ + { + value: 'OUTGOING', + label: 'Saliente', + position: 0, + color: 'blue', + }, + { + value: 'INCOMING', + label: 'Entrante', + position: 1, + color: 'green', + }, + ], + }, + + { + universalIdentifier: '566d6018-189e-4144-812a-c24e3dd9535a', + + type: FieldType.SELECT, + + name: 'medium', + label: 'Medio', + + description: 'Medio utilizado para realizar el contacto.', + + icon: 'IconDeviceMobile', + + options: [ + { + value: 'PHONE', + label: 'Teléfono', + position: 0, + color: 'blue', + }, + { + value: 'WHATSAPP', + label: 'WhatsApp', + position: 1, + color: 'green', + }, + { + value: 'GOOGLE_MEET', + label: 'Google Meet', + position: 2, + color: 'purple', + }, + { + value: 'MICROSOFT_TEAMS', + label: 'Microsoft Teams', + position: 3, + color: 'blue', + }, + { + value: 'ZOOM', + label: 'Zoom', + position: 4, + color: 'cyan', + }, + { + value: 'IN_PERSON', + label: 'Presencial', + position: 5, + color: 'orange', + }, + { + value: 'OTHER', + label: 'Otro', + position: 6, + color: 'gray', + }, + ], + }, + + { + universalIdentifier: 'ed07b419-6d79-4811-99e1-b43fa942716c', + + type: FieldType.SELECT, + + name: 'outcome', + label: 'Resultado', + + description: 'Resultado del intento de contacto.', + + icon: 'IconPhoneCheck', + + options: [ + { + value: 'CONTACTED', + label: 'Contactado', + position: 0, + color: 'green', + }, + { + value: 'NO_ANSWER', + label: 'Sin respuesta', + position: 1, + color: 'orange', + }, + { + value: 'VOICEMAIL', + label: 'Buzón de voz', + position: 2, + color: 'yellow', + }, + { + value: 'BUSY', + label: 'Ocupado', + position: 3, + color: 'red', + }, + { + value: 'OTHER', + label: 'Otro', + position: 4, + color: 'gray', + }, + ], + }, + ], +}); diff --git a/src/objects/season.object.ts b/src/objects/season.object.ts new file mode 100644 index 0000000..5e59309 --- /dev/null +++ b/src/objects/season.object.ts @@ -0,0 +1,64 @@ +import { + defineObject, + FieldType, +} from 'twenty-sdk/define'; + +export const SEASON_UNIVERSAL_IDENTIFIER = + '42ac6973-2c6b-4c6b-af7e-7a5d23c7cd76'; + +export const SEASON_NAME_FIELD_ID = + '6556c4eb-330a-4303-96bb-ba67270d2252'; + +export const SEASON_START_DATE_FIELD_ID = + 'b2ac1dd9-1d09-408c-b205-b63dc308b787'; + +export const SEASON_END_DATE_FIELD_ID = + 'b5c9bcfb-c910-4a46-abf4-c2455d072354'; + +export const SEASON_ACTIVE_FIELD_ID = + 'fb0f1fe0-9fb5-4369-84dc-4bde8f376d64'; + +export default defineObject({ + universalIdentifier: SEASON_UNIVERSAL_IDENTIFIER, + + nameSingular: 'season', + namePlural: 'seasons', + + labelSingular: 'Season', + labelPlural: 'Seasons', + + description: 'Hyperloop UPV sponsorship seasons', + + icon: 'IconCalendar', + + fields: [ + { + universalIdentifier: SEASON_NAME_FIELD_ID, + name: 'name', + label: 'Name', + type: FieldType.TEXT, + }, + + { + universalIdentifier: SEASON_START_DATE_FIELD_ID, + name: 'startDate', + label: 'Start Date', + type: FieldType.DATE, + }, + + { + universalIdentifier: SEASON_END_DATE_FIELD_ID, + name: 'endDate', + label: 'End Date', + type: FieldType.DATE, + }, + + { + universalIdentifier: SEASON_ACTIVE_FIELD_ID, + name: 'active', + label: 'Active', + type: FieldType.BOOLEAN, + defaultValue: true, + }, + ], +}); \ No newline at end of file diff --git a/src/page-layouts/main-page.page-layout.ts b/src/page-layouts/main-page.page-layout.ts new file mode 100644 index 0000000..2301f49 --- /dev/null +++ b/src/page-layouts/main-page.page-layout.ts @@ -0,0 +1,37 @@ +import { definePageLayout, PageLayoutTabLayoutMode } from 'twenty-sdk/define'; + +import { + APP_DISPLAY_NAME, + MAIN_PAGE_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER, + MAIN_PAGE_LAYOUT_TAB_UNIVERSAL_IDENTIFIER, + MAIN_PAGE_LAYOUT_UNIVERSAL_IDENTIFIER, + MAIN_PAGE_WIDGET_UNIVERSAL_IDENTIFIER, +} from 'src/constants/universal-identifiers'; + +export default definePageLayout({ + universalIdentifier: MAIN_PAGE_LAYOUT_UNIVERSAL_IDENTIFIER, + name: APP_DISPLAY_NAME, + type: 'STANDALONE_PAGE', + tabs: [ + { + universalIdentifier: MAIN_PAGE_LAYOUT_TAB_UNIVERSAL_IDENTIFIER, + title: 'Overview', + position: 0, + icon: 'IconApps', + layoutMode: PageLayoutTabLayoutMode.VERTICAL_LIST, + widgets: [ + { + universalIdentifier: MAIN_PAGE_WIDGET_UNIVERSAL_IDENTIFIER, + title: ' ', + type: 'FRONT_COMPONENT', + gridPosition: { row: 0, column: 0, rowSpan: 12, columnSpan: 12 }, + configuration: { + configurationType: 'FRONT_COMPONENT', + frontComponentUniversalIdentifier: + MAIN_PAGE_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER, + }, + }, + ], + }, + ], +}); diff --git a/aura/aura/.frappe b/src/page-layouts/opportunity-calls.page-layout-tab.ts similarity index 100% rename from aura/aura/.frappe rename to src/page-layouts/opportunity-calls.page-layout-tab.ts diff --git a/src/page-layouts/opportunity-products.front-component.tsx b/src/page-layouts/opportunity-products.front-component.tsx new file mode 100644 index 0000000..d73932d --- /dev/null +++ b/src/page-layouts/opportunity-products.front-component.tsx @@ -0,0 +1,739 @@ +import { useEffect, useMemo, useState } from 'react'; + +import { RestApiClient } from 'twenty-client-sdk/rest'; +import { defineFrontComponent } from 'twenty-sdk/define'; +import { + enqueueSnackbar, + useSelectedRecordIds, +} from 'twenty-sdk/front-component'; + +export const OPPORTUNITY_PRODUCTS_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER = + '9fdd2795-3469-40aa-a646-badc31af9384'; + +type ProductLine = { + product: string; + unit: string; + price: string; +}; + +type StoredProductLine = { + product: string; + unit: number; + price: number; +}; + +type JsonRecord = Record; + +const EMPTY_LINE: ProductLine = { + product: '', + unit: '', + price: '', +}; + +const isRecord = (value: unknown): value is JsonRecord => + typeof value === 'object' && value !== null && !Array.isArray(value); + +const unwrapOpportunity = (response: unknown): JsonRecord => { + if (!isRecord(response)) { + return {}; + } + + if (isRecord(response.opportunity)) { + return response.opportunity; + } + + if (isRecord(response.data)) { + if (isRecord(response.data.opportunity)) { + return response.data.opportunity; + } + + return response.data; + } + + return response; +}; + +const normalizeProducts = (value: unknown): ProductLine[] => { + let parsedValue = value; + + if (typeof parsedValue === 'string') { + try { + parsedValue = JSON.parse(parsedValue); + } catch { + return []; + } + } + + if (!Array.isArray(parsedValue)) { + return []; + } + + return parsedValue + .filter(isRecord) + .map((item) => ({ + product: typeof item.product === 'string' ? item.product : '', + unit: + typeof item.unit === 'number' || typeof item.unit === 'string' + ? String(item.unit) + : '', + price: + typeof item.price === 'number' || typeof item.price === 'string' + ? String(item.price) + : '', + })); +}; + +const parseNumber = (value: string): number => { + const normalized = value.replace(',', '.').trim(); + + if (normalized === '') { + return 0; + } + + const parsed = Number(normalized); + + return Number.isFinite(parsed) ? parsed : 0; +}; + +const formatCurrency = (value: number): string => + `${new Intl.NumberFormat('es-ES', { + minimumFractionDigits: 2, + maximumFractionDigits: 2, + }).format(value)} €`; + +const OpportunityProductsEditor = () => { + const selectedRecordIds = useSelectedRecordIds(); + + const recordId = + selectedRecordIds.length === 1 ? selectedRecordIds[0] : null; + + const client = useMemo(() => new RestApiClient(), []); + + const [lines, setLines] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const [isSaving, setIsSaving] = useState(false); + const [hasChanges, setHasChanges] = useState(false); + const [errorMessage, setErrorMessage] = useState(null); + + const totalValue = lines.reduce( + (sum, line) => sum + parseNumber(line.price), + 0, + ); + + useEffect(() => { + let cancelled = false; + + const loadProducts = async () => { + if (!recordId) { + setIsLoading(false); + return; + } + + setIsLoading(true); + setErrorMessage(null); + + try { + const response = await client.get( + `/rest/opportunities/${recordId}`, + ); + + if (cancelled) { + return; + } + + const opportunity = unwrapOpportunity(response); + + setLines( + normalizeProducts(opportunity.sponsorshipProducts), + ); + + setHasChanges(false); + } catch (error) { + if (cancelled) { + return; + } + + console.error(error); + setErrorMessage('Could not load products.'); + } finally { + if (!cancelled) { + setIsLoading(false); + } + } + }; + + void loadProducts(); + + return () => { + cancelled = true; + }; + }, [client, recordId]); + + const updateLine = ( + index: number, + field: keyof ProductLine, + value: string, + ) => { + setLines((currentLines) => + currentLines.map((line, lineIndex) => + lineIndex === index + ? { ...line, [field]: value } + : line, + ), + ); + + setHasChanges(true); + }; + + const addLine = () => { + setLines((currentLines) => [ + ...currentLines, + { ...EMPTY_LINE }, + ]); + + setHasChanges(true); + }; + + const removeLine = (index: number) => { + setLines((currentLines) => + currentLines.filter((_, lineIndex) => lineIndex !== index), + ); + + setHasChanges(true); + }; + + const saveProducts = async () => { + if (!recordId || isSaving) { + return; + } + + setIsSaving(true); + setErrorMessage(null); + + const productsToSave: StoredProductLine[] = lines + .filter( + (line) => + line.product.trim() !== '' || + line.unit.trim() !== '' || + line.price.trim() !== '', + ) + .map((line) => ({ + product: line.product.trim(), + unit: parseNumber(line.unit), + price: parseNumber(line.price), + })); + + const calculatedTotal = productsToSave.reduce( + (sum, line) => sum + line.price, + 0, + ); + + try { + await client.patch( + `/rest/opportunities/${recordId}`, + { + sponsorshipProducts: productsToSave, + + totalValue: { + amountMicros: Math.round( + calculatedTotal * 1_000_000, + ), + currencyCode: 'EUR', + }, + }, + ); + + setLines( + productsToSave.map((line) => ({ + product: line.product, + unit: String(line.unit), + price: String(line.price), + })), + ); + + setHasChanges(false); + + await enqueueSnackbar({ + message: 'Products saved', + variant: 'success', + }); + } catch (error) { + console.error(error); + + setErrorMessage('Could not save products.'); + + await enqueueSnackbar({ + message: 'Could not save products', + variant: 'error', + }); + } finally { + setIsSaving(false); + } + }; + + const shellStyle = { + width: '100%', + boxSizing: 'border-box' as const, + padding: '20px 22px 24px', + fontFamily: 'sans-serif', + }; + + const mutedStyle = { + color: 'inherit', + opacity: 0.58, + }; + + const cardStyle = { + border: '1px solid rgba(127,127,127,0.20)', + borderRadius: '10px', + overflow: 'hidden', + background: 'rgba(127,127,127,0.025)', + }; + + const headerCellStyle = { + padding: '9px 12px', + textAlign: 'left' as const, + fontSize: '11px', + fontWeight: 600, + letterSpacing: '0.03em', + textTransform: 'uppercase' as const, + opacity: 0.58, + borderBottom: '1px solid rgba(127,127,127,0.18)', + }; + + const cellStyle = { + padding: '7px 10px', + borderBottom: '1px solid rgba(127,127,127,0.12)', + verticalAlign: 'middle' as const, + }; + + const inputStyle = { + width: '100%', + minHeight: '34px', + boxSizing: 'border-box' as const, + border: '1px solid transparent', + borderRadius: '6px', + padding: '7px 9px', + background: 'transparent', + color: 'inherit', + outline: 'none', + }; + + const secondaryButtonStyle = { + border: '1px solid rgba(127,127,127,0.26)', + borderRadius: '6px', + padding: '7px 10px', + background: 'transparent', + color: 'inherit', + cursor: 'pointer', + fontSize: '13px', + }; + + const primaryButtonStyle = { + border: '1px solid rgba(127,127,127,0.34)', + borderRadius: '6px', + padding: '8px 14px', + background: 'rgba(127,127,127,0.13)', + color: 'inherit', + cursor: 'pointer', + fontSize: '13px', + fontWeight: 600, + }; + + if (!recordId) { + return ( +
+ Open a single opportunity to edit its products. +
+ ); + } + + if (isLoading) { + return ( +
+ + Loading products… + +
+ ); + } + + return ( +
+
+
+
+ Sponsorship products +
+ +
+ Products and services contributed in this opportunity. +
+
+ + +
+ + {errorMessage ? ( +
+ {errorMessage} +
+ ) : null} + +
+ {lines.length === 0 ? ( +
+
+ No products added +
+ +
+ Add the products or services included in the sponsorship. +
+ + +
+ ) : ( + + + + + + + + + + + + + + + + {lines.map((line, index) => ( + + + + + + + + + + + + ))} + +
+ # + + Product + + Unit + + Price + +
+ {index + 1} + + + updateLine( + index, + 'product', + event.target.value, + ) + } + style={inputStyle} + /> + + + updateLine( + index, + 'unit', + event.target.value, + ) + } + style={{ + ...inputStyle, + textAlign: 'right', + }} + /> + +
+ + updateLine( + index, + 'price', + event.target.value, + ) + } + style={{ + ...inputStyle, + textAlign: 'right', + }} + /> + + + € + +
+
+ +
+ )} + +
+ + +
+ + Total Value + + + + {formatCurrency(totalValue)} + +
+
+
+ +
+ {hasChanges ? ( + + Unsaved changes + + ) : null} + + +
+
+ ); +}; + +export default defineFrontComponent({ + universalIdentifier: + OPPORTUNITY_PRODUCTS_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER, + + name: 'opportunity-products-editor', + + description: + 'Inline editor for sponsorship products on an opportunity.', + + component: OpportunityProductsEditor, +}); diff --git a/src/page-layouts/opportunity-products.page-layout-tab.ts b/src/page-layouts/opportunity-products.page-layout-tab.ts new file mode 100644 index 0000000..9bcc9b2 --- /dev/null +++ b/src/page-layouts/opportunity-products.page-layout-tab.ts @@ -0,0 +1,46 @@ +import { + definePageLayoutTab, + PageLayoutTabLayoutMode, + STANDARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIERS, +} from 'twenty-sdk/define'; + +import { + OPPORTUNITY_PRODUCTS_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER, +} from './opportunity-products.front-component'; + +export const OPPORTUNITY_PRODUCTS_PAGE_LAYOUT_TAB_UNIVERSAL_IDENTIFIER = + 'b3d16313-e49b-4b11-b390-821d1fc44db8'; + +export const OPPORTUNITY_PRODUCTS_WIDGET_UNIVERSAL_IDENTIFIER = + 'bb29e640-50f7-4aa8-b7e2-b78d22332991'; + +export default definePageLayoutTab({ + universalIdentifier: + OPPORTUNITY_PRODUCTS_PAGE_LAYOUT_TAB_UNIVERSAL_IDENTIFIER, + + pageLayoutUniversalIdentifier: + STANDARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIERS.opportunityRecordPage + .universalIdentifier, + + title: 'Products', + position: 60, + icon: 'IconPackage', + + layoutMode: PageLayoutTabLayoutMode.CANVAS, + + widgets: [ + { + universalIdentifier: + OPPORTUNITY_PRODUCTS_WIDGET_UNIVERSAL_IDENTIFIER, + + title: 'Products', + type: 'FRONT_COMPONENT', + + configuration: { + configurationType: 'FRONT_COMPONENT', + frontComponentUniversalIdentifier: + OPPORTUNITY_PRODUCTS_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER, + }, + }, + ], +}); diff --git a/src/src/fields/child-tiers-on-partner-tier.field.ts b/src/src/fields/child-tiers-on-partner-tier.field.ts new file mode 100644 index 0000000..c998ebf --- /dev/null +++ b/src/src/fields/child-tiers-on-partner-tier.field.ts @@ -0,0 +1,17 @@ +import { defineField, FieldType, RelationType } from 'twenty-sdk/define'; +import { PARTNER_TIER_UNIVERSAL_IDENTIFIER } from '../objects/partner-tier.object'; +import { PARENT_TIER_FIELD_ID, CHILD_TIERS_FIELD_ID } from './parent-tier-on-partner-tier.field'; + +export default defineField({ + universalIdentifier: CHILD_TIERS_FIELD_ID, + objectUniversalIdentifier: PARTNER_TIER_UNIVERSAL_IDENTIFIER, + type: FieldType.RELATION, + name: 'childTiers', + label: 'Child Tiers', + description: + 'Technical inverse of Parent Tier. Hidden from the AURA Partner Tier page.', + icon: 'IconArrowDown', + relationTargetObjectMetadataUniversalIdentifier: PARTNER_TIER_UNIVERSAL_IDENTIFIER, + relationTargetFieldMetadataUniversalIdentifier: PARENT_TIER_FIELD_ID, + universalSettings: { relationType: RelationType.ONE_TO_MANY }, +}); diff --git a/src/src/fields/opportunities-on-tier.field.ts b/src/src/fields/opportunities-on-tier.field.ts new file mode 100644 index 0000000..e1ddc78 --- /dev/null +++ b/src/src/fields/opportunities-on-tier.field.ts @@ -0,0 +1,15 @@ +import { defineField, FieldType, RelationType, STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS } from 'twenty-sdk/define'; +import { PARTNER_TIER_UNIVERSAL_IDENTIFIER } from '../objects/partner-tier.object'; +import { TIER_ON_OPPORTUNITY_FIELD_ID, OPPORTUNITIES_ON_TIER_FIELD_ID } from './tier-on-opportunity.field'; + +export default defineField({ + universalIdentifier: OPPORTUNITIES_ON_TIER_FIELD_ID, + objectUniversalIdentifier: PARTNER_TIER_UNIVERSAL_IDENTIFIER, + type: FieldType.RELATION, + name: 'opportunities', + label: 'Opportunities', + icon: 'IconTargetArrow', + relationTargetObjectMetadataUniversalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.opportunity.universalIdentifier, + relationTargetFieldMetadataUniversalIdentifier: TIER_ON_OPPORTUNITY_FIELD_ID, + universalSettings: { relationType: RelationType.ONE_TO_MANY }, +}); diff --git a/src/src/fields/parent-tier-on-partner-tier.field.ts b/src/src/fields/parent-tier-on-partner-tier.field.ts new file mode 100644 index 0000000..db8409d --- /dev/null +++ b/src/src/fields/parent-tier-on-partner-tier.field.ts @@ -0,0 +1,22 @@ +import { defineField, FieldType, OnDeleteAction, RelationType } from 'twenty-sdk/define'; +import { PARTNER_TIER_UNIVERSAL_IDENTIFIER } from '../objects/partner-tier.object'; + +export const PARENT_TIER_FIELD_ID = 'aa389497-86fc-47c1-a855-aab1191a32a8'; +export const CHILD_TIERS_FIELD_ID = '2b9af45e-4f52-4002-b438-7c9a082bfd1e'; + +export default defineField({ + universalIdentifier: PARENT_TIER_FIELD_ID, + objectUniversalIdentifier: PARTNER_TIER_UNIVERSAL_IDENTIFIER, + type: FieldType.RELATION, + name: 'parentTier', + label: 'Parent Tier', + icon: 'IconArrowUp', + relationTargetObjectMetadataUniversalIdentifier: PARTNER_TIER_UNIVERSAL_IDENTIFIER, + relationTargetFieldMetadataUniversalIdentifier: CHILD_TIERS_FIELD_ID, + isNullable: true, + universalSettings: { + relationType: RelationType.MANY_TO_ONE, + onDelete: OnDeleteAction.SET_NULL, + joinColumnName: 'parentTierId', + }, +}); diff --git a/src/src/fields/task-aura-partner-task.field.ts b/src/src/fields/task-aura-partner-task.field.ts new file mode 100644 index 0000000..80d7613 --- /dev/null +++ b/src/src/fields/task-aura-partner-task.field.ts @@ -0,0 +1,12 @@ +import { defineField, FieldType, STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS } from 'twenty-sdk/define'; + +export default defineField({ + universalIdentifier: '248a2330-29c9-4239-8363-3d2c39f536c8', + objectUniversalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.task.universalIdentifier, + type: FieldType.BOOLEAN, + name: 'auraPartnerTask', + label: 'AURA Partner Task', + description: 'Task generated from an AURA Partner Tier template.', + icon: 'IconSparkles', + defaultValue: false, +}); diff --git a/src/src/fields/task-aura-template-id.field.ts b/src/src/fields/task-aura-template-id.field.ts new file mode 100644 index 0000000..1cf448b --- /dev/null +++ b/src/src/fields/task-aura-template-id.field.ts @@ -0,0 +1,11 @@ +import { defineField, FieldType, STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS } from 'twenty-sdk/define'; + +export default defineField({ + universalIdentifier: 'a59c3c69-e462-4893-96d9-5a3049b4c2bf', + objectUniversalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.task.universalIdentifier, + type: FieldType.TEXT, + name: 'auraTaskTemplateId', + label: 'AURA Task Template ID', + description: 'Internal ID used to prevent duplicate generated tasks.', + icon: 'IconFingerprint', +}); diff --git a/src/src/fields/task-templates-on-tier.field.ts b/src/src/fields/task-templates-on-tier.field.ts new file mode 100644 index 0000000..a4904f8 --- /dev/null +++ b/src/src/fields/task-templates-on-tier.field.ts @@ -0,0 +1,16 @@ +import { defineField, FieldType, RelationType } from 'twenty-sdk/define'; +import { PARTNER_TIER_UNIVERSAL_IDENTIFIER } from '../objects/partner-tier.object'; +import { PARTNER_TIER_TASK_TEMPLATE_UNIVERSAL_IDENTIFIER } from '../objects/partner-tier-task-template.object'; +import { TIER_ON_TEMPLATE_FIELD_ID, TEMPLATES_ON_TIER_FIELD_ID } from './tier-on-task-template.field'; + +export default defineField({ + universalIdentifier: TEMPLATES_ON_TIER_FIELD_ID, + objectUniversalIdentifier: PARTNER_TIER_UNIVERSAL_IDENTIFIER, + type: FieldType.RELATION, + name: 'taskTemplates', + label: 'Task Templates', + icon: 'IconChecklist', + relationTargetObjectMetadataUniversalIdentifier: PARTNER_TIER_TASK_TEMPLATE_UNIVERSAL_IDENTIFIER, + relationTargetFieldMetadataUniversalIdentifier: TIER_ON_TEMPLATE_FIELD_ID, + universalSettings: { relationType: RelationType.ONE_TO_MANY }, +}); diff --git a/src/src/fields/tier-on-opportunity.field.ts b/src/src/fields/tier-on-opportunity.field.ts new file mode 100644 index 0000000..9ceef42 --- /dev/null +++ b/src/src/fields/tier-on-opportunity.field.ts @@ -0,0 +1,22 @@ +import { defineField, FieldType, OnDeleteAction, RelationType, STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS } from 'twenty-sdk/define'; +import { PARTNER_TIER_UNIVERSAL_IDENTIFIER } from '../objects/partner-tier.object'; + +export const TIER_ON_OPPORTUNITY_FIELD_ID = '259564f8-251a-4b0c-9cd2-6694b916216c'; +export const OPPORTUNITIES_ON_TIER_FIELD_ID = 'aed30cf8-604e-4d4c-9906-f77483b47732'; + +export default defineField({ + universalIdentifier: TIER_ON_OPPORTUNITY_FIELD_ID, + objectUniversalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.opportunity.universalIdentifier, + type: FieldType.RELATION, + name: 'partnerTier', + label: 'Partner Tier', + icon: 'IconAward', + relationTargetObjectMetadataUniversalIdentifier: PARTNER_TIER_UNIVERSAL_IDENTIFIER, + relationTargetFieldMetadataUniversalIdentifier: OPPORTUNITIES_ON_TIER_FIELD_ID, + isNullable: true, + universalSettings: { + relationType: RelationType.MANY_TO_ONE, + onDelete: OnDeleteAction.SET_NULL, + joinColumnName: 'partnerTierId', + }, +}); diff --git a/src/src/fields/tier-on-task-template.field.ts b/src/src/fields/tier-on-task-template.field.ts new file mode 100644 index 0000000..2007520 --- /dev/null +++ b/src/src/fields/tier-on-task-template.field.ts @@ -0,0 +1,23 @@ +import { defineField, FieldType, OnDeleteAction, RelationType } from 'twenty-sdk/define'; +import { PARTNER_TIER_UNIVERSAL_IDENTIFIER } from '../objects/partner-tier.object'; +import { PARTNER_TIER_TASK_TEMPLATE_UNIVERSAL_IDENTIFIER } from '../objects/partner-tier-task-template.object'; + +export const TIER_ON_TEMPLATE_FIELD_ID = '295cdbe4-867e-4a57-a87c-6dd74d11d905'; +export const TEMPLATES_ON_TIER_FIELD_ID = 'ddf634c5-dc59-4c63-a810-166888852ae5'; + +export default defineField({ + universalIdentifier: TIER_ON_TEMPLATE_FIELD_ID, + objectUniversalIdentifier: PARTNER_TIER_TASK_TEMPLATE_UNIVERSAL_IDENTIFIER, + type: FieldType.RELATION, + name: 'partnerTier', + label: 'Partner Tier', + icon: 'IconAward', + relationTargetObjectMetadataUniversalIdentifier: PARTNER_TIER_UNIVERSAL_IDENTIFIER, + relationTargetFieldMetadataUniversalIdentifier: TEMPLATES_ON_TIER_FIELD_ID, + isNullable: true, + universalSettings: { + relationType: RelationType.MANY_TO_ONE, + onDelete: OnDeleteAction.SET_NULL, + joinColumnName: 'partnerTierId', + }, +}); diff --git a/src/src/front-components/opportunity-partner-tier.front-component.tsx b/src/src/front-components/opportunity-partner-tier.front-component.tsx new file mode 100644 index 0000000..c9df680 --- /dev/null +++ b/src/src/front-components/opportunity-partner-tier.front-component.tsx @@ -0,0 +1,763 @@ +import { useEffect, useMemo, useState } from 'react'; + +import { RestApiClient } from 'twenty-client-sdk/rest'; +import { defineFrontComponent } from 'twenty-sdk/define'; +import { enqueueSnackbar, useSelectedRecordIds } from 'twenty-sdk/front-component'; + +export const OPPORTUNITY_PARTNER_TIER_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER = + '5d403444-9a7a-48c0-96d6-b208235a6af0'; + +type JsonRecord = Record; + +type Tier = { + id: string; + name: string; + parentTierId: string | null; +}; + +type Template = { + id: string; + name: string; + dueAt: string | null; + partnerTierId: string | null; +}; + +const isRecord = (value: unknown): value is JsonRecord => + typeof value === 'object' && value !== null && !Array.isArray(value); + +const getString = (record: JsonRecord, key: string): string | null => { + const value = record[key]; + return typeof value === 'string' && value !== '' ? value : null; +}; + + +const unwrapOne = (response: unknown, key?: string): JsonRecord => { + if (!isRecord(response)) return {}; + if (key && isRecord(response[key])) return response[key] as JsonRecord; + + if (isRecord(response.data)) { + if (key && isRecord(response.data[key])) return response.data[key] as JsonRecord; + return response.data; + } + + return response; +}; + +const unwrapList = (response: unknown, key: string): JsonRecord[] => { + if (Array.isArray(response)) return response.filter(isRecord); + if (!isRecord(response)) return []; + + const direct = response[key]; + if (Array.isArray(direct)) return direct.filter(isRecord); + + if (isRecord(direct) && Array.isArray(direct.edges)) { + return direct.edges + .filter(isRecord) + .map((edge) => edge.node) + .filter(isRecord); + } + + const data = response.data; + + if (Array.isArray(data)) return data.filter(isRecord); + + if (isRecord(data)) { + const nested = data[key]; + if (Array.isArray(nested)) return nested.filter(isRecord); + + if (isRecord(nested) && Array.isArray(nested.edges)) { + return nested.edges + .filter(isRecord) + .map((edge) => edge.node) + .filter(isRecord); + } + } + + return []; +}; + +const relationId = ( + record: JsonRecord, + idField: string, + relationField: string, +): string | null => { + const direct = getString(record, idField); + if (direct) return direct; + + const relation = record[relationField]; + return isRecord(relation) ? getString(relation, 'id') : null; +}; + +const normalizeTier = (record: JsonRecord): Tier | null => { + const id = getString(record, 'id'); + const name = getString(record, 'name'); + if (!id || !name) return null; + + return { + id, + name, + parentTierId: relationId(record, 'parentTierId', 'parentTier'), + }; +}; + +const normalizeTemplate = (record: JsonRecord): Template | null => { + const id = getString(record, 'id'); + const name = getString(record, 'name'); + if (!id || !name) return null; + + return { + id, + name, + dueAt: getString(record, 'dueAt'), + partnerTierId: relationId(record, 'partnerTierId', 'partnerTier'), + }; +}; + +const getInheritedTierIds = (selectedTierId: string, tiers: Tier[]): string[] => { + const byId = new Map(tiers.map((tier) => [tier.id, tier])); + const chain: string[] = []; + const visited = new Set(); + let currentId: string | null = selectedTierId; + + while (currentId && !visited.has(currentId) && chain.length < 20) { + visited.add(currentId); + chain.unshift(currentId); + currentId = byId.get(currentId)?.parentTierId ?? null; + } + + return chain; +}; + +const extractCreatedId = (response: unknown, key: string): string | null => + getString(unwrapOne(response, key), 'id'); + +const replaceCompanyName = (value: string, companyName: string): string => + value.split('{name}').join(companyName); + +const OpportunityPartnerTier = () => { + const selectedRecordIds = useSelectedRecordIds(); + const recordId = selectedRecordIds.length === 1 ? selectedRecordIds[0] : null; + const client = useMemo(() => new RestApiClient(), []); + + const [tiers, setTiers] = useState([]); + const [templates, setTemplates] = useState([]); + const [selectedTierId, setSelectedTierId] = useState(''); + const [companyName, setCompanyName] = useState('Company'); + const [companyId, setCompanyId] = useState(null); + + const [isLoading, setIsLoading] = useState(true); + const [isSavingTier, setIsSavingTier] = useState(false); + const [isGenerating, setIsGenerating] = useState(false); + const [isCreatingDefaults, setIsCreatingDefaults] = useState(false); + const [errorMessage, setErrorMessage] = useState(null); + + const loadData = async () => { + if (!recordId) { + setIsLoading(false); + return; + } + + setIsLoading(true); + setErrorMessage(null); + + try { + const [opportunityResponse, tiersResponse, templatesResponse] = + await Promise.all([ + client.get(`/rest/opportunities/${recordId}`), + client.get('/rest/partnerTiers?limit=60'), + client.get('/rest/partnerTierTaskTemplates?limit=60'), + ]); + + const opportunity = unwrapOne(opportunityResponse, 'opportunity'); + + const loadedTiersUnsorted = unwrapList( + tiersResponse, + 'partnerTiers', + ) + .map(normalizeTier) + .filter((tier): tier is Tier => tier !== null); + + const tierByIdForSorting = new Map( + loadedTiersUnsorted.map((tier) => [tier.id, tier]), + ); + + const getTierDepth = (tier: Tier): number => { + let depth = 0; + let parentId = tier.parentTierId; + const visited = new Set(); + + while ( + parentId && + !visited.has(parentId) && + depth < 20 + ) { + visited.add(parentId); + depth += 1; + parentId = + tierByIdForSorting.get(parentId)?.parentTierId ?? + null; + } + + return depth; + }; + + const loadedTiers = [...loadedTiersUnsorted].sort( + (a, b) => + getTierDepth(a) - getTierDepth(b) || + a.name.localeCompare(b.name), + ); + + const loadedTemplates = unwrapList( + templatesResponse, + 'partnerTierTaskTemplates', + ) + .map(normalizeTemplate) + .filter((template): template is Template => template !== null); + + setTiers(loadedTiers); + setTemplates(loadedTemplates); + + setSelectedTierId( + relationId(opportunity, 'partnerTierId', 'partnerTier') ?? '', + ); + + const oppCompanyId = relationId(opportunity, 'companyId', 'company'); + setCompanyId(oppCompanyId); + + const companyRelation = opportunity.company; + const nestedName = isRecord(companyRelation) + ? getString(companyRelation, 'name') + : null; + + if (nestedName) { + setCompanyName(nestedName); + } else if (oppCompanyId) { + const companyResponse = await client.get( + `/rest/companies/${oppCompanyId}`, + ); + const company = unwrapOne(companyResponse, 'company'); + setCompanyName(getString(company, 'name') ?? 'Company'); + } else { + setCompanyName('Company'); + } + } catch (error) { + console.error(error); + setErrorMessage('Could not load Partner Tier data.'); + } finally { + setIsLoading(false); + } + }; + + useEffect(() => { + void loadData(); + }, [recordId]); + + const inheritedIds = selectedTierId + ? getInheritedTierIds(selectedTierId, tiers) + : []; + + const order = new Map(inheritedIds.map((id, index) => [id, index])); + + const inheritedTemplates = templates + .filter( + (template) => + template.partnerTierId !== null && order.has(template.partnerTierId), + ) + .sort((a, b) => { + const aOrder = a.partnerTierId ? order.get(a.partnerTierId) ?? 0 : 0; + const bOrder = b.partnerTierId ? order.get(b.partnerTierId) ?? 0 : 0; + return aOrder - bOrder || a.name.localeCompare(b.name); + }); + + const saveTier = async () => { + if (!recordId || isSavingTier) return; + + setIsSavingTier(true); + setErrorMessage(null); + + try { + await client.patch(`/rest/opportunities/${recordId}`, { + partnerTierId: selectedTierId === '' ? null : selectedTierId, + }); + + await enqueueSnackbar({ + message: 'Partner Tier saved', + variant: 'success', + }); + } catch (error) { + console.error(error); + setErrorMessage('Could not save Partner Tier.'); + } finally { + setIsSavingTier(false); + } + }; + + const createDefaultTiers = async () => { + if (isCreatingDefaults || tiers.length !== 0) return; + + setIsCreatingDefaults(true); + setErrorMessage(null); + + try { + const collaboratorResponse = await client.post('/rest/partnerTiers', { + name: 'Collaborator', + description: 'Base sponsorship tier.', + }); + + const collaboratorId = extractCreatedId( + collaboratorResponse, + 'partnerTier', + ); + + if (!collaboratorId) throw new Error('Missing Collaborator id'); + + const silverResponse = await client.post('/rest/partnerTiers', { + name: 'Silver', + description: 'Includes Collaborator tasks plus Silver tasks.', + parentTierId: collaboratorId, + }); + + const silverId = extractCreatedId(silverResponse, 'partnerTier'); + if (!silverId) throw new Error('Missing Silver id'); + + await client.post('/rest/partnerTiers', { + name: 'Gold', + description: + 'Includes Collaborator and Silver tasks plus Gold tasks.', + parentTierId: silverId, + }); + + await enqueueSnackbar({ + message: 'Collaborator, Silver and Gold created', + variant: 'success', + }); + + await loadData(); + } catch (error) { + console.error(error); + setErrorMessage('Could not create default tiers.'); + } finally { + setIsCreatingDefaults(false); + } + }; + + const generateTasks = async () => { + if (!recordId || !selectedTierId || isGenerating) return; + + setIsGenerating(true); + setErrorMessage(null); + + try { + const targetFilter = `targetOpportunityId[eq]:'${recordId}'`; + + const targetResponse = await client.get( + `/rest/taskTargets?filter=${encodeURIComponent(targetFilter)}&limit=60`, + ); + + const taskTargets = unwrapList(targetResponse, 'taskTargets'); + + const taskIds = Array.from( + new Set( + taskTargets + .map((target) => { + const direct = getString(target, 'taskId'); + if (direct) return direct; + + return isRecord(target.task) + ? getString(target.task, 'id') + : null; + }) + .filter((id): id is string => id !== null), + ), + ); + + const existingTemplateIds = new Set(); + + for (const taskId of taskIds) { + try { + const taskResponse = await client.get(`/rest/tasks/${taskId}`); + const task = unwrapOne(taskResponse, 'task'); + + if (task.auraPartnerTask === true) { + const templateId = getString(task, 'auraTaskTemplateId'); + if (templateId) existingTemplateIds.add(templateId); + } + } catch { + // Deleted tasks are intentionally ignored so they can be recreated. + } + } + + let created = 0; + let skipped = 0; + + for (const template of inheritedTemplates) { + if (existingTemplateIds.has(template.id)) { + skipped += 1; + continue; + } + + const rendered = replaceCompanyName(template.name, companyName); + const title = `[${companyName}] ${rendered}`; + + const taskPayload: JsonRecord = { + title, + status: 'TODO', + auraPartnerTask: true, + auraTaskTemplateId: template.id, + }; + + if (template.dueAt) taskPayload.dueAt = template.dueAt; + const taskResponse = await client.post('/rest/tasks', taskPayload); + + console.log('AURA create task response:', taskResponse); + + const taskRecord = unwrapOne(taskResponse, 'task'); + + console.log('AURA unwrapped task:', taskRecord); + + const taskId = + getString(taskRecord, 'id') ?? + (isRecord(taskRecord.createTask) + ? getString(taskRecord.createTask, 'id') + : null); + + if (!taskId) { + console.error('AURA could not extract task id:', taskResponse); + throw new Error('Task created without an id'); + } + + const targetPayload: JsonRecord = { + taskId, + targetOpportunityId: recordId, + }; + + await client.post('/rest/taskTargets', targetPayload); + + if (companyId) { + await client.post('/rest/taskTargets', { + taskId, + targetCompanyId: companyId, + }); + } + + created += 1; + } + + await enqueueSnackbar({ + message: `${created} task(s) created, ${skipped} already existed`, + variant: 'success', + }); + } catch (error) { + console.error(error); + setErrorMessage('Could not generate Partner tasks.'); + } finally { + setIsGenerating(false); + } + }; + + const tierById = new Map(tiers.map((tier) => [tier.id, tier])); + + const shellStyle = { + width: '100%', + boxSizing: 'border-box' as const, + padding: '20px 22px 24px', + fontFamily: 'sans-serif', + }; + + const cardStyle = { + border: '1px solid rgba(127,127,127,0.20)', + borderRadius: '10px', + overflow: 'hidden', + background: 'rgba(127,127,127,0.025)', + }; + + const buttonStyle = { + border: '1px solid rgba(127,127,127,0.28)', + borderRadius: '6px', + padding: '8px 12px', + background: 'transparent', + color: 'inherit', + cursor: 'pointer', + fontSize: '13px', + }; + + const primaryButtonStyle = { + ...buttonStyle, + background: 'rgba(127,127,127,0.13)', + fontWeight: 600, + }; + + if (!recordId) { + return
Open one Opportunity.
; + } + + if (isLoading) { + return
Loading Partner Tier…
; + } + + return ( +
+
+
+ Partner Tier +
+
+ Select a sponsorship tier and generate inherited tasks. +
+
+ + {errorMessage ? ( +
+ {errorMessage} +
+ ) : null} + + {tiers.length === 0 ? ( +
+
No Partner Tiers yet
+
+ Create Collaborator → Silver → Gold. +
+ +
+ ) : ( + <> +
+
+ + + +
+
+ +
+
+
+
+ Inherited task templates +
+
+ Parent-tier templates are included automatically. +
+
+ + +
+ + {!selectedTierId ? ( +
+ Select a Partner Tier. +
+ ) : inheritedTemplates.length === 0 ? ( +
+ No templates are defined for this tier or its parents. +
+ ) : ( + inheritedTemplates.map((template, index) => { + const tier = template.partnerTierId + ? tierById.get(template.partnerTierId) + : undefined; + + return ( +
+
+ {index + 1} +
+ +
+
+ {template.name} +
+
+ {tier?.name ?? 'Tier'} +
+
+ +
+ {template.dueAt + ? new Date(template.dueAt).toLocaleDateString() + : 'No due date'} +
+
+ ); + }) + )} +
+ +
+ Existing generated tasks are skipped. Deleted generated tasks are + recreated the next time you generate. +
+ + )} +
+ ); +}; + +export default defineFrontComponent({ + universalIdentifier: + OPPORTUNITY_PARTNER_TIER_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER, + name: 'opportunity-partner-tier', + description: + 'Select an AURA Partner Tier and generate inherited sponsorship tasks.', + component: OpportunityPartnerTier, +}); diff --git a/src/src/front-components/partner-advantages.front-component.tsx b/src/src/front-components/partner-advantages.front-component.tsx new file mode 100644 index 0000000..b4797ba --- /dev/null +++ b/src/src/front-components/partner-advantages.front-component.tsx @@ -0,0 +1,1040 @@ +import { useEffect, useMemo, useState } from 'react'; + +import { RestApiClient } from 'twenty-client-sdk/rest'; +import { defineFrontComponent } from 'twenty-sdk/define'; + +export const PARTNER_ADVANTAGES_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER = + '337a928a-a9e1-4bfe-ba60-d573869a3e54'; + +type JsonRecord = Record; + +type Season = { + id: string; + name: string; +}; + +type PartnerTier = { + id: string; + name: string; +}; + +type Opportunity = { + id: string; + name: string; + seasonId: string | null; + partnerTierId: string | null; +}; + +type PartnerTask = { + id: string; + title: string; + status: string | null; + dueAt: string | null; + assigneeId: string | null; + assigneeName: string | null; +}; + +type WorkspaceMember = { + id: string; + name: string; +}; + +type TaskTargetLink = { + taskId: string; + opportunityId: string; +}; + +type OpportunityTaskGroup = { + opportunity: Opportunity; + tasks: PartnerTask[]; + pendingCount: number; + nextDueAt: string | null; +}; + +const PAGE_SIZE = 60; +const UNASSIGNED_SEASON_ID = '__aura_unassigned_season__'; + +const isRecord = (value: unknown): value is JsonRecord => + typeof value === 'object' && value !== null && !Array.isArray(value); + +const getString = (record: JsonRecord, key: string): string | null => { + const value = record[key]; + return typeof value === 'string' && value !== '' ? value : null; +}; + +const getBoolean = (record: JsonRecord, key: string): boolean | null => { + const value = record[key]; + if (typeof value === 'boolean') return value; + if (value === 'true' || value === 1) return true; + if (value === 'false' || value === 0) return false; + return null; +}; + +const unwrapOne = (response: unknown, key?: string): JsonRecord => { + if (!isRecord(response)) return {}; + + if (key && isRecord(response[key])) { + return response[key] as JsonRecord; + } + + if (isRecord(response.data)) { + if (key && isRecord(response.data[key])) { + return response.data[key] as JsonRecord; + } + + return response.data; + } + + return response; +}; + +const unwrapList = (response: unknown, key: string): JsonRecord[] => { + if (Array.isArray(response)) return response.filter(isRecord); + if (!isRecord(response)) return []; + + const direct = response[key]; + if (Array.isArray(direct)) return direct.filter(isRecord); + + if (isRecord(direct) && Array.isArray(direct.edges)) { + return direct.edges + .filter(isRecord) + .map((edge) => edge.node) + .filter(isRecord); + } + + const data = response.data; + + if (Array.isArray(data)) return data.filter(isRecord); + + if (isRecord(data)) { + const nested = data[key]; + + if (Array.isArray(nested)) return nested.filter(isRecord); + + if (isRecord(nested) && Array.isArray(nested.edges)) { + return nested.edges + .filter(isRecord) + .map((edge) => edge.node) + .filter(isRecord); + } + } + + return []; +}; + +const findPageInfo = ( + response: unknown, + key: string, +): { hasNextPage: boolean; endCursor: string | null } | null => { + if (!isRecord(response)) return null; + + const candidates: unknown[] = [ + response.pageInfo, + isRecord(response.data) ? response.data.pageInfo : undefined, + isRecord(response[key]) ? response[key].pageInfo : undefined, + isRecord(response.data) && isRecord(response.data[key]) + ? response.data[key].pageInfo + : undefined, + isRecord(response.meta) ? response.meta.pageInfo : undefined, + ]; + + for (const candidate of candidates) { + if (!isRecord(candidate)) continue; + + const hasNextPage = candidate.hasNextPage; + const endCursor = candidate.endCursor; + + if (typeof hasNextPage === 'boolean') { + return { + hasNextPage, + endCursor: typeof endCursor === 'string' ? endCursor : null, + }; + } + } + + return null; +}; + +const fetchAllRecords = async ( + client: RestApiClient, + resource: string, + maxPages = 50, +): Promise => { + const records: JsonRecord[] = []; + const seenIds = new Set(); + let cursor: string | null = null; + + for (let page = 0; page < maxPages; page += 1) { + const cursorPart = cursor + ? `&starting_after=${encodeURIComponent(cursor)}` + : ''; + + const response = await client.get( + `/rest/${resource}?limit=${PAGE_SIZE}${cursorPart}`, + ); + + const pageRecords = unwrapList(response, resource); + + for (const record of pageRecords) { + const id = getString(record, 'id'); + if (!id || seenIds.has(id)) continue; + seenIds.add(id); + records.push(record); + } + + const pageInfo = findPageInfo(response, resource); + if (!pageInfo?.hasNextPage || !pageInfo.endCursor) break; + if (pageInfo.endCursor === cursor) break; + + cursor = pageInfo.endCursor; + } + + return records; +}; + +const fetchRecordsById = async ( + client: RestApiClient, + resource: string, + ids: string[], +): Promise => { + const result: JsonRecord[] = []; + const uniqueIds = Array.from(new Set(ids)); + const chunkSize = 12; + + for (let index = 0; index < uniqueIds.length; index += chunkSize) { + const chunk = uniqueIds.slice(index, index + chunkSize); + + const records = await Promise.all( + chunk.map(async (id) => { + try { + const response = await client.get( + `/rest/${resource}/${encodeURIComponent(id)}`, + ); + const singular = resource.endsWith('ies') + ? `${resource.slice(0, -3)}y` + : resource.endsWith('s') + ? resource.slice(0, -1) + : resource; + return unwrapOne(response, singular); + } catch { + return null; + } + }), + ); + + for (const record of records) { + if (record && getString(record, 'id')) result.push(record); + } + } + + return result; +}; + +const relationId = ( + record: JsonRecord, + idField: string, + relationField: string, +): string | null => { + const direct = getString(record, idField); + if (direct) return direct; + + const relation = record[relationField]; + return isRecord(relation) ? getString(relation, 'id') : null; +}; + +const normalizeSeason = (record: JsonRecord): Season | null => { + const id = getString(record, 'id'); + const name = getString(record, 'name'); + return id && name ? { id, name } : null; +}; + +const normalizeTier = (record: JsonRecord): PartnerTier | null => { + const id = getString(record, 'id'); + const name = getString(record, 'name'); + return id && name ? { id, name } : null; +}; + +const normalizeOpportunity = (record: JsonRecord): Opportunity | null => { + const id = getString(record, 'id'); + if (!id) return null; + + const company = record.company; + const companyName = isRecord(company) ? getString(company, 'name') : null; + + return { + id, + name: getString(record, 'name') ?? companyName ?? 'Opportunity', + seasonId: relationId(record, 'seasonId', 'season'), + partnerTierId: relationId(record, 'partnerTierId', 'partnerTier'), + }; +}; + +const getFullName = (value: unknown): string | null => { + if (!isRecord(value)) return null; + + const firstName = getString(value, 'firstName') ?? ''; + const lastName = getString(value, 'lastName') ?? ''; + const fullName = `${firstName} ${lastName}`.trim(); + + return fullName || null; +}; + +const normalizeWorkspaceMember = (record: JsonRecord): WorkspaceMember | null => { + const id = getString(record, 'id'); + if (!id) return null; + + const nestedName = getFullName(record.name); + const fallbackName = + getString(record, 'displayName') ?? + getString(record, 'userEmail') ?? + getString(record, 'email'); + + return { + id, + name: nestedName ?? fallbackName ?? 'Workspace member', + }; +}; + +const normalizeTask = (record: JsonRecord): PartnerTask | null => { + const id = getString(record, 'id'); + const title = getString(record, 'title'); + + if (!id || !title) return null; + + // The generated AURA tasks carry both markers. Accept either one because + // some Twenty list responses can omit custom boolean fields while the + // single-record endpoint still returns the custom text field. + const isAuraPartnerTask = getBoolean(record, 'auraPartnerTask') === true; + const auraTemplateId = getString(record, 'auraTaskTemplateId'); + + if (!isAuraPartnerTask && !auraTemplateId) return null; + + const assignee = record.assignee; + const nestedAssigneeName = isRecord(assignee) + ? getFullName(assignee.name) ?? + getString(assignee, 'displayName') ?? + getString(assignee, 'userEmail') + : null; + + return { + id, + title, + status: getString(record, 'status'), + dueAt: getString(record, 'dueAt'), + assigneeId: + relationId(record, 'assigneeId', 'assignee') ?? + relationId(record, 'workspaceMemberId', 'workspaceMember'), + assigneeName: nestedAssigneeName, + }; +}; + +const normalizeTaskTargetLink = (record: JsonRecord): TaskTargetLink | null => { + // Twenty exposes TaskTarget relations with `target*` names when reading + // records (for example `targetOpportunityId`), even though creation accepts + // `opportunityId`. Keep both forms for compatibility across versions. + const taskId = + relationId(record, 'taskId', 'task') ?? + relationId(record, 'targetTaskId', 'targetTask'); + + const opportunityId = + relationId(record, 'targetOpportunityId', 'targetOpportunity') ?? + relationId(record, 'opportunityId', 'opportunity'); + + return taskId && opportunityId ? { taskId, opportunityId } : null; +}; + +const isTaskCompleted = (task: PartnerTask): boolean => { + const status = (task.status ?? '').toUpperCase(); + return status === 'DONE' || status === 'COMPLETED'; +}; + +const toTime = (value: string | null): number | null => { + if (!value) return null; + const parsed = new Date(value).getTime(); + return Number.isFinite(parsed) ? parsed : null; +}; + +const formatDate = (value: string | null): string => { + if (!value) return '—'; + + const date = new Date(value); + if (Number.isNaN(date.getTime())) return '—'; + + return date.toLocaleDateString(undefined, { + day: '2-digit', + month: '2-digit', + year: 'numeric', + }); +}; + +const PartnerAdvantages = () => { + const client = useMemo(() => new RestApiClient(), []); + + const [seasons, setSeasons] = useState([]); + const [tiers, setTiers] = useState([]); + const [opportunities, setOpportunities] = useState([]); + const [tasks, setTasks] = useState([]); + const [workspaceMembers, setWorkspaceMembers] = useState([]); + const [links, setLinks] = useState([]); + + const [selectedSeasonId, setSelectedSeasonId] = useState(''); + const [isLoading, setIsLoading] = useState(true); + const [errorMessage, setErrorMessage] = useState(null); + + useEffect(() => { + const load = async () => { + setIsLoading(true); + setErrorMessage(null); + + try { + const [seasonRecords, tierRecords, targetRecords, memberRecords] = + await Promise.all([ + fetchAllRecords(client, 'seasons'), + fetchAllRecords(client, 'partnerTiers'), + fetchAllRecords(client, 'taskTargets'), + fetchAllRecords(client, 'workspaceMembers').catch(() => []), + ]); + + const loadedSeasons = seasonRecords + .map(normalizeSeason) + .filter((season): season is Season => season !== null) + .sort((a, b) => b.name.localeCompare(a.name)); + + const loadedTiers = tierRecords + .map(normalizeTier) + .filter((tier): tier is PartnerTier => tier !== null); + + const loadedLinks = targetRecords + .map(normalizeTaskTargetLink) + .filter((link): link is TaskTargetLink => link !== null); + + const loadedWorkspaceMembers = memberRecords + .map(normalizeWorkspaceMember) + .filter((member): member is WorkspaceMember => member !== null); + + const taskIds = loadedLinks.map((link) => link.taskId); + const opportunityIds = loadedLinks.map((link) => link.opportunityId); + + // Fetch single records deliberately. The existing AURA Tier component + // already uses these endpoints and they reliably expose AURA custom + // fields/relations such as auraPartnerTask, auraTaskTemplateId, + // partnerTierId and seasonId. + const [taskRecords, opportunityRecords] = await Promise.all([ + fetchRecordsById(client, 'tasks', taskIds), + fetchRecordsById(client, 'opportunities', opportunityIds), + ]); + + const loadedTasks = taskRecords + .map(normalizeTask) + .filter((task): task is PartnerTask => task !== null); + + const loadedOpportunities = opportunityRecords + .map(normalizeOpportunity) + .filter( + (opportunity): opportunity is Opportunity => opportunity !== null, + ); + + setSeasons(loadedSeasons); + setTiers(loadedTiers); + setTasks(loadedTasks); + setWorkspaceMembers(loadedWorkspaceMembers); + setOpportunities(loadedOpportunities); + setLinks(loadedLinks); + + setSelectedSeasonId((current) => { + if ( + current === UNASSIGNED_SEASON_ID || + loadedSeasons.some((season) => season.id === current) + ) { + return current; + } + + // AURA rule: default to the alphabetically last Season name. + if (loadedSeasons.length > 0) return loadedSeasons[0].id; + + // If Season has not been assigned yet, do not hide existing tasks. + return loadedOpportunities.some((opportunity) => !opportunity.seasonId) + ? UNASSIGNED_SEASON_ID + : ''; + }); + } catch (error) { + console.error(error); + setErrorMessage('Could not load Partner advantages.'); + } finally { + setIsLoading(false); + } + }; + + void load(); + }, [client]); + + const sortedSeasons = useMemo( + () => [...seasons].sort((a, b) => b.name.localeCompare(a.name)), + [seasons], + ); + + const tierById = useMemo( + () => new Map(tiers.map((tier) => [tier.id, tier])), + [tiers], + ); + + const taskById = useMemo( + () => new Map(tasks.map((task) => [task.id, task])), + [tasks], + ); + + const workspaceMemberById = useMemo( + () => new Map(workspaceMembers.map((member) => [member.id, member])), + [workspaceMembers], + ); + + const [updatingTaskIds, setUpdatingTaskIds] = useState>( + () => new Set(), + ); + + const setTaskCompleted = async (task: PartnerTask, completed: boolean) => { + if (updatingTaskIds.has(task.id)) return; + + setUpdatingTaskIds((current) => { + const next = new Set(current); + next.add(task.id); + return next; + }); + + const previousStatus = task.status; + const nextStatus = completed ? 'DONE' : 'TODO'; + + setTasks((current) => + current.map((item) => + item.id === task.id ? { ...item, status: nextStatus } : item, + ), + ); + + try { + await client.patch(`/rest/tasks/${encodeURIComponent(task.id)}`, { + status: nextStatus, + }); + } catch (error) { + console.error(error); + setTasks((current) => + current.map((item) => + item.id === task.id ? { ...item, status: previousStatus } : item, + ), + ); + setErrorMessage('Could not update the task status.'); + } finally { + setUpdatingTaskIds((current) => { + const next = new Set(current); + next.delete(task.id); + return next; + }); + } + }; + + const opportunityById = useMemo( + () => new Map(opportunities.map((opportunity) => [opportunity.id, opportunity])), + [opportunities], + ); + + const partnerLinks = useMemo( + () => links.filter((link) => taskById.has(link.taskId)), + [links, taskById], + ); + + const partnerOpportunityIds = useMemo( + () => new Set(partnerLinks.map((link) => link.opportunityId)), + [partnerLinks], + ); + + const unassignedOpportunityCount = useMemo( + () => + opportunities.filter( + (opportunity) => + partnerOpportunityIds.has(opportunity.id) && !opportunity.seasonId, + ).length, + [opportunities, partnerOpportunityIds], + ); + + const unassignedTaskCount = useMemo(() => { + let count = 0; + for (const link of partnerLinks) { + const opportunity = opportunityById.get(link.opportunityId); + if (opportunity && !opportunity.seasonId) count += 1; + } + return count; + }, [partnerLinks, opportunityById]); + + const seasonOptions = useMemo( + () => [ + ...sortedSeasons, + ...(unassignedOpportunityCount > 0 + ? [{ id: UNASSIGNED_SEASON_ID, name: 'Sin temporada' }] + : []), + ], + [sortedSeasons, unassignedOpportunityCount], + ); + + const selectedSeasonIndex = seasonOptions.findIndex( + (season) => season.id === selectedSeasonId, + ); + + const groups = useMemo(() => { + if (!selectedSeasonId) return []; + + const seasonOpportunities = opportunities.filter((opportunity) => + selectedSeasonId === UNASSIGNED_SEASON_ID + ? opportunity.seasonId === null + : opportunity.seasonId === selectedSeasonId, + ); + + const selectedOpportunityById = new Map( + seasonOpportunities.map((opportunity) => [opportunity.id, opportunity]), + ); + + const tasksByOpportunityId = new Map>(); + + for (const link of partnerLinks) { + if (!selectedOpportunityById.has(link.opportunityId)) continue; + + const task = taskById.get(link.taskId); + if (!task) continue; + + const current = + tasksByOpportunityId.get(link.opportunityId) ?? + new Map(); + + current.set(task.id, task); + tasksByOpportunityId.set(link.opportunityId, current); + } + + const result: OpportunityTaskGroup[] = []; + + for (const opportunity of seasonOpportunities) { + const opportunityTasks = Array.from( + tasksByOpportunityId.get(opportunity.id)?.values() ?? [], + ).sort((a, b) => { + const aDone = isTaskCompleted(a); + const bDone = isTaskCompleted(b); + + if (aDone !== bDone) return aDone ? 1 : -1; + + const aDue = toTime(a.dueAt); + const bDue = toTime(b.dueAt); + + if (aDue !== null && bDue !== null && aDue !== bDue) return aDue - bDue; + if (aDue !== null && bDue === null) return -1; + if (aDue === null && bDue !== null) return 1; + + return a.title.localeCompare(b.title); + }); + + if (opportunityTasks.length === 0) continue; + + const pendingTasks = opportunityTasks.filter( + (task) => !isTaskCompleted(task), + ); + + const nextDueAt = pendingTasks.reduce((earliest, task) => { + const taskTime = toTime(task.dueAt); + if (taskTime === null) return earliest; + + const earliestTime = toTime(earliest); + if (earliestTime === null || taskTime < earliestTime) return task.dueAt; + + return earliest; + }, null); + + result.push({ + opportunity, + tasks: opportunityTasks, + pendingCount: pendingTasks.length, + nextDueAt, + }); + } + + return result.sort((a, b) => { + const aDue = toTime(a.nextDueAt); + const bDue = toTime(b.nextDueAt); + + if (aDue !== null && bDue !== null && aDue !== bDue) return aDue - bDue; + if (aDue !== null && bDue === null) return -1; + if (aDue === null && bDue !== null) return 1; + + return a.opportunity.name.localeCompare(b.opportunity.name); + }); + }, [opportunities, selectedSeasonId, partnerLinks, taskById]); + + const totalPending = groups.reduce( + (sum, group) => sum + group.pendingCount, + 0, + ); + + const shellStyle = { + width: '100%', + boxSizing: 'border-box' as const, + padding: '22px 24px 30px', + fontFamily: 'sans-serif', + }; + + const cardStyle = { + border: '1px solid rgba(127,127,127,0.20)', + borderRadius: '10px', + overflow: 'hidden', + background: 'rgba(127,127,127,0.025)', + }; + + const buttonStyle = { + border: '1px solid rgba(127,127,127,0.26)', + borderRadius: '6px', + padding: '7px 10px', + background: 'transparent', + color: 'inherit', + cursor: 'pointer', + fontSize: '13px', + }; + + if (isLoading) { + return
Loading Partner advantages…
; + } + + return ( +
+
+
+
+ Ventajas Partners +
+
+ Partner tasks grouped by sponsorship. +
+
+ +
+
+ Season +
+ +
+ + + + + +
+
+
+ + {errorMessage ? ( +
+ {errorMessage} +
+ ) : null} + + {unassignedTaskCount > 0 && selectedSeasonId !== UNASSIGNED_SEASON_ID ? ( +
+ + {unassignedTaskCount} Partner task + {unassignedTaskCount === 1 ? '' : 's'} belong to sponsorships with no + Season assigned. + + +
+ ) : null} + + {selectedSeasonId && groups.length > 0 ? ( +
+ {groups.length} sponsorship{groups.length === 1 ? '' : 's'} ·{' '} + {totalPending} pending task{totalPending === 1 ? '' : 's'} +
+ ) : null} + + {!selectedSeasonId ? ( +
+ No Season is available. +
+ ) : groups.length === 0 ? ( +
+
No Partner tasks for this Season
+
+ {tasks.length > 0 + ? `${tasks.length} AURA Partner task${tasks.length === 1 ? '' : 's'} exist, but none belong to sponsorships in this Season.` + : 'No AURA Partner tasks were found.'} +
+
+ ) : ( +
+ {groups.map((group) => { + const tierName = group.opportunity.partnerTierId + ? tierById.get(group.opportunity.partnerTierId)?.name + : null; + + return ( +
+ +
+
+ {group.opportunity.name} + {tierName ? ( + + {' '} + · {tierName} + + ) : null} +
+
+ {group.pendingCount} pending · Next delivery:{' '} + {formatDate(group.nextDueAt)} +
+
+ +
+ {group.tasks.length} task{group.tasks.length === 1 ? '' : 's'} + +
+
+ +
+ {group.tasks.map((task, index) => { + const completed = isTaskCompleted(task); + const dueTime = toTime(task.dueAt); + const isOverdue = + !completed && dueTime !== null && dueTime < Date.now(); + const isUpdating = updatingTaskIds.has(task.id); + const assigneeName = + task.assigneeName ?? + (task.assigneeId + ? workspaceMemberById.get(task.assigneeId)?.name ?? + 'Assigned member' + : 'Unassigned'); + + return ( +
+ + +
+ {task.title} +
+ +
+ {assigneeName} +
+ +
+ {formatDate(task.dueAt)} + {isOverdue ? ' · Overdue' : ''} +
+
+ ); + })} +
+
+ ); + })} +
+ )} +
+ ); +}; + +export default defineFrontComponent({ + universalIdentifier: + PARTNER_ADVANTAGES_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER, + name: 'partner-advantages', + description: + 'AURA Partner tasks grouped by Opportunity, with assignee and inline completion.', + component: PartnerAdvantages, +}); diff --git a/src/src/navigation-menu-items/partner-advantages.navigation-menu-item.ts b/src/src/navigation-menu-items/partner-advantages.navigation-menu-item.ts new file mode 100644 index 0000000..700aa3c --- /dev/null +++ b/src/src/navigation-menu-items/partner-advantages.navigation-menu-item.ts @@ -0,0 +1,19 @@ +import { + defineNavigationMenuItem, + NavigationMenuItemType, +} from 'twenty-sdk/define'; + +import { + PARTNER_ADVANTAGES_PAGE_LAYOUT_UNIVERSAL_IDENTIFIER, +} from '../page-layouts/partner-advantages.page-layout'; + +export default defineNavigationMenuItem({ + universalIdentifier: '469d4675-326d-48db-96ff-e5a9dbcb0525', + name: 'Ventajas Partners', + icon: 'IconChecklist', + color: 'blue', + position: 40, + type: NavigationMenuItemType.PAGE_LAYOUT, + pageLayoutUniversalIdentifier: + PARTNER_ADVANTAGES_PAGE_LAYOUT_UNIVERSAL_IDENTIFIER, +}); diff --git a/src/src/navigation-menu-items/partner-tiers.navigation-menu-item.ts b/src/src/navigation-menu-items/partner-tiers.navigation-menu-item.ts new file mode 100644 index 0000000..093f8ed --- /dev/null +++ b/src/src/navigation-menu-items/partner-tiers.navigation-menu-item.ts @@ -0,0 +1,12 @@ +import { defineNavigationMenuItem, NavigationMenuItemType } from 'twenty-sdk/define'; +import { PARTNER_TIER_UNIVERSAL_IDENTIFIER } from '../objects/partner-tier.object'; + +export default defineNavigationMenuItem({ + universalIdentifier: '36e443e1-7dad-4e8f-a385-a1ff47ec65be', + name: 'Partner Tiers', + icon: 'IconAward', + color: 'yellow', + position: 90, + type: NavigationMenuItemType.OBJECT, + targetObjectUniversalIdentifier: PARTNER_TIER_UNIVERSAL_IDENTIFIER, +}); diff --git a/src/src/navigation-menu-items/tier-task-templates.navigation-menu-item.ts b/src/src/navigation-menu-items/tier-task-templates.navigation-menu-item.ts new file mode 100644 index 0000000..e1ff7b0 --- /dev/null +++ b/src/src/navigation-menu-items/tier-task-templates.navigation-menu-item.ts @@ -0,0 +1,12 @@ +import { defineNavigationMenuItem, NavigationMenuItemType } from 'twenty-sdk/define'; +import { PARTNER_TIER_TASK_TEMPLATE_UNIVERSAL_IDENTIFIER } from '../objects/partner-tier-task-template.object'; + +export default defineNavigationMenuItem({ + universalIdentifier: '3e87c620-e43f-46da-8e23-43f269929f73', + name: 'Tier Task Templates', + icon: 'IconChecklist', + color: 'blue', + position: 91, + type: NavigationMenuItemType.OBJECT, + targetObjectUniversalIdentifier: PARTNER_TIER_TASK_TEMPLATE_UNIVERSAL_IDENTIFIER, +}); diff --git a/src/src/objects/partner-tier-task-template.object.ts b/src/src/objects/partner-tier-task-template.object.ts new file mode 100644 index 0000000..8eda8f8 --- /dev/null +++ b/src/src/objects/partner-tier-task-template.object.ts @@ -0,0 +1,37 @@ +import { defineObject, FieldType } from 'twenty-sdk/define'; + +export const PARTNER_TIER_TASK_TEMPLATE_UNIVERSAL_IDENTIFIER = '9a1c68ad-f3fa-45a9-b714-9c21bbd7d71a'; + +export default defineObject({ + universalIdentifier: PARTNER_TIER_TASK_TEMPLATE_UNIVERSAL_IDENTIFIER, + nameSingular: 'partnerTierTaskTemplate', + namePlural: 'partnerTierTaskTemplates', + labelSingular: 'Tier Task Template', + labelPlural: 'Tier Task Templates', + icon: 'IconChecklist', + fields: [ + { + universalIdentifier: 'ea107cf9-99af-47aa-bd3b-bcb6866e98d5', + type: FieldType.TEXT, + name: 'name', + label: 'Task Title Template', + description: 'Use {name} to insert the company name.', + icon: 'IconCheckbox', + }, + { + universalIdentifier: 'a309c010-088c-4a80-9c43-5d9999846548', + type: FieldType.DATE_TIME, + name: 'dueAt', + label: 'Due At', + description: 'Optional fixed due date and time.', + icon: 'IconCalendarTime', + }, + { + universalIdentifier: '5fd07234-bb96-4372-9fc6-6e45ff0d7a8e', + type: FieldType.TEXT, + name: 'description', + label: 'Description', + icon: 'IconNotes', + }, + ], +}); diff --git a/src/src/objects/partner-tier.object.ts b/src/src/objects/partner-tier.object.ts new file mode 100644 index 0000000..1fc3293 --- /dev/null +++ b/src/src/objects/partner-tier.object.ts @@ -0,0 +1,52 @@ +import { defineObject, FieldType } from 'twenty-sdk/define'; + +export const PARTNER_TIER_UNIVERSAL_IDENTIFIER = + 'eda9eeaa-275b-40ae-863a-683ef6140094'; + +export const PARTNER_TIER_NAME_FIELD_UNIVERSAL_IDENTIFIER = + 'ec041113-476e-4e9b-bb35-cefe767ee45b'; + +export const PARTNER_TIER_DESCRIPTION_FIELD_UNIVERSAL_IDENTIFIER = + 'bd0ac300-36a5-49d7-b604-d60f441cd2b8'; + +export default defineObject({ + universalIdentifier: PARTNER_TIER_UNIVERSAL_IDENTIFIER, + + nameSingular: 'partnerTier', + namePlural: 'partnerTiers', + + labelSingular: 'Partner Tier', + labelPlural: 'Partner Tiers', + + icon: 'IconAward', + + fields: [ + { + universalIdentifier: + PARTNER_TIER_NAME_FIELD_UNIVERSAL_IDENTIFIER, + + type: FieldType.TEXT, + + name: 'name', + label: 'Name', + + description: 'Name of the sponsorship tier.', + + icon: 'IconAward', + }, + { + universalIdentifier: + PARTNER_TIER_DESCRIPTION_FIELD_UNIVERSAL_IDENTIFIER, + + type: FieldType.TEXT, + + name: 'description', + label: 'Description', + + description: + 'Optional description of the sponsorship tier.', + + icon: 'IconNotes', + }, + ], +}); diff --git a/src/src/page-layouts/opportunity-partner-tier.page-layout-tab.ts b/src/src/page-layouts/opportunity-partner-tier.page-layout-tab.ts new file mode 100644 index 0000000..cf21ac5 --- /dev/null +++ b/src/src/page-layouts/opportunity-partner-tier.page-layout-tab.ts @@ -0,0 +1,32 @@ +import { + definePageLayoutTab, + PageLayoutTabLayoutMode, + STANDARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIERS, +} from 'twenty-sdk/define'; + +import { + OPPORTUNITY_PARTNER_TIER_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER, +} from '../front-components/opportunity-partner-tier.front-component'; + +export default definePageLayoutTab({ + universalIdentifier: '0744babb-70f8-4bc3-b405-a83e770b9bc9', + pageLayoutUniversalIdentifier: + STANDARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIERS.opportunityRecordPage + .universalIdentifier, + title: 'Partner Tier', + position: 40, + icon: 'IconAward', + layoutMode: PageLayoutTabLayoutMode.CANVAS, + widgets: [ + { + universalIdentifier: 'c8b6c037-c54e-4548-a81d-0682de8d0dbf', + title: 'Partner Tier', + type: 'FRONT_COMPONENT', + configuration: { + configurationType: 'FRONT_COMPONENT', + frontComponentUniversalIdentifier: + OPPORTUNITY_PARTNER_TIER_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER, + }, + }, + ], +}); diff --git a/src/src/page-layouts/partner-advantages.page-layout.ts b/src/src/page-layouts/partner-advantages.page-layout.ts new file mode 100644 index 0000000..b1de511 --- /dev/null +++ b/src/src/page-layouts/partner-advantages.page-layout.ts @@ -0,0 +1,43 @@ +import { + definePageLayout, + PageLayoutTabLayoutMode, +} from 'twenty-sdk/define'; + +import { + PARTNER_ADVANTAGES_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER, +} from '../front-components/partner-advantages.front-component'; + +export const PARTNER_ADVANTAGES_PAGE_LAYOUT_UNIVERSAL_IDENTIFIER = + 'b1402f8c-c515-49b1-a831-43032bd8ca18'; + +export default definePageLayout({ + universalIdentifier: + PARTNER_ADVANTAGES_PAGE_LAYOUT_UNIVERSAL_IDENTIFIER, + name: 'Ventajas Partners', + type: 'STANDALONE_PAGE', + tabs: [ + { + universalIdentifier: '5a741967-07a7-4ec8-9df2-6861b2eccc8b', + title: 'Ventajas Partners', + position: 0, + icon: 'IconChecklist', + layoutMode: PageLayoutTabLayoutMode.VERTICAL_LIST, + widgets: [ + { + universalIdentifier: '4bf0ea32-b59d-434a-ae14-5be2d7827014', + title: 'Ventajas Partners', + type: 'FRONT_COMPONENT', + position: { + layoutMode: PageLayoutTabLayoutMode.VERTICAL_LIST, + index: 0, + }, + configuration: { + configurationType: 'FRONT_COMPONENT', + frontComponentUniversalIdentifier: + PARTNER_ADVANTAGES_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER, + }, + }, + ], + }, + ], +}); diff --git a/src/src/page-layouts/partner-tier-record.page-layout.ts b/src/src/page-layouts/partner-tier-record.page-layout.ts new file mode 100644 index 0000000..4234887 --- /dev/null +++ b/src/src/page-layouts/partner-tier-record.page-layout.ts @@ -0,0 +1,129 @@ +import { + definePageLayout, + PageLayoutTabLayoutMode, +} from 'twenty-sdk/define'; + +import { + PARTNER_TIER_UNIVERSAL_IDENTIFIER, +} from '../objects/partner-tier.object'; + +import { + OPPORTUNITIES_ON_TIER_FIELD_ID, +} from '../fields/tier-on-opportunity.field'; + +import { + TEMPLATES_ON_TIER_FIELD_ID, +} from '../fields/tier-on-task-template.field'; + +export const PARTNER_TIER_RECORD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIER = + 'cbee43ce-ee2b-4fdf-a0e3-f9fa59ad7383'; + +export default definePageLayout({ + universalIdentifier: + PARTNER_TIER_RECORD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIER, + + name: 'Partner Tier Record Page', + + type: 'RECORD_PAGE', + + objectUniversalIdentifier: + PARTNER_TIER_UNIVERSAL_IDENTIFIER, + + tabs: [ + { + universalIdentifier: + '88a93056-7fe7-4d2c-a8f8-ea176de26aaf', + + title: 'Details', + position: 0, + icon: 'IconInfoCircle', + + layoutMode: + PageLayoutTabLayoutMode.VERTICAL_LIST, + + widgets: [ + { + universalIdentifier: + 'd123a097-11a9-4c5e-bd0f-406e32f26b1b', + + title: 'Details', + type: 'FIELDS', + + configuration: { + configurationType: 'FIELDS', + }, + }, + ], + }, + + { + universalIdentifier: + 'c0e0cfb3-8893-4352-aa00-1d3f22572e76', + + title: 'Opportunities', + position: 10, + icon: 'IconTargetArrow', + + layoutMode: + PageLayoutTabLayoutMode.VERTICAL_LIST, + + widgets: [ + { + universalIdentifier: + '7879ae77-7908-494d-8331-c6b225de9b9e', + + title: 'Opportunities', + type: 'FIELD', + + position: { + layoutMode: + PageLayoutTabLayoutMode.VERTICAL_LIST, + index: 0, + }, + + configuration: { + configurationType: 'FIELD', + fieldMetadataId: + OPPORTUNITIES_ON_TIER_FIELD_ID, + fieldDisplayMode: 'TABLE', + }, + }, + ], + }, + + { + universalIdentifier: + '80c9d5be-28f4-4bcd-9f84-ba811669d46d', + + title: 'Task Templates', + position: 20, + icon: 'IconChecklist', + + layoutMode: + PageLayoutTabLayoutMode.VERTICAL_LIST, + + widgets: [ + { + universalIdentifier: + '52906149-a432-49db-94f9-887231f3ded3', + + title: 'Task Templates', + type: 'FIELD', + + position: { + layoutMode: + PageLayoutTabLayoutMode.VERTICAL_LIST, + index: 0, + }, + + configuration: { + configurationType: 'FIELD', + fieldMetadataId: + TEMPLATES_ON_TIER_FIELD_ID, + fieldDisplayMode: 'TABLE', + }, + }, + ], + }, + ], +}); diff --git a/src/src/page-layouts/partner-tier-task-template-record.page-layout.ts b/src/src/page-layouts/partner-tier-task-template-record.page-layout.ts new file mode 100644 index 0000000..c0bb2fb --- /dev/null +++ b/src/src/page-layouts/partner-tier-task-template-record.page-layout.ts @@ -0,0 +1,51 @@ +import { + definePageLayout, + PageLayoutTabLayoutMode, +} from 'twenty-sdk/define'; + +import { + PARTNER_TIER_TASK_TEMPLATE_UNIVERSAL_IDENTIFIER, +} from '../objects/partner-tier-task-template.object'; + +export const PARTNER_TIER_TASK_TEMPLATE_RECORD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIER = + 'a1ba0613-b347-4752-b60d-9f753944141e'; + +export default definePageLayout({ + universalIdentifier: + PARTNER_TIER_TASK_TEMPLATE_RECORD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIER, + + name: 'Tier Task Template Record Page', + + type: 'RECORD_PAGE', + + objectUniversalIdentifier: + PARTNER_TIER_TASK_TEMPLATE_UNIVERSAL_IDENTIFIER, + + tabs: [ + { + universalIdentifier: + '118305f1-33f4-4790-a94b-58af68dd5dc7', + + title: 'Details', + position: 0, + icon: 'IconInfoCircle', + + layoutMode: + PageLayoutTabLayoutMode.VERTICAL_LIST, + + widgets: [ + { + universalIdentifier: + '7c854877-cf5d-495e-ae64-c44d2f7fe949', + + title: 'Details', + type: 'FIELDS', + + configuration: { + configurationType: 'FIELDS', + }, + }, + ], + }, + ], +}); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..abacf4d --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,42 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "sourceMap": true, + "declaration": true, + "outDir": "./dist", + "rootDir": ".", + "jsx": "react-jsx", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "importHelpers": true, + "allowUnreachableCode": false, + "strict": true, + "alwaysStrict": true, + "noImplicitAny": true, + "strictBindCallApply": false, + "target": "es2020", + "module": "esnext", + "lib": ["es2020", "dom"], + "skipLibCheck": true, + "skipDefaultLibCheck": true, + "resolveJsonModule": true, + "paths": { + "src/*": ["./src/*"], + "~/*": ["./*"] + } + }, + "exclude": [ + "node_modules", + "dist", + "**/*.test.ts", + "**/*.spec.ts", + "**/*.integration-test.ts" + ], + "references": [ + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/tsconfig.spec.json b/tsconfig.spec.json new file mode 100644 index 0000000..ea69a6c --- /dev/null +++ b/tsconfig.spec.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "composite": true, + "types": ["vitest/globals", "node"] + }, + "include": ["src/**/*.ts", "src/**/*.tsx"], + "exclude": ["node_modules", "dist"] +} diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..055af77 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,31 @@ +import tsconfigPaths from 'vite-tsconfig-paths'; +import { defineConfig } from 'vitest/config'; + +const TWENTY_API_URL = process.env.TWENTY_API_URL ?? 'http://localhost:2020'; +const TWENTY_API_KEY = + process.env.TWENTY_API_KEY ?? + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyMDIwMjAyMC0xYzI1LTRkMDItYmYyNS02YWVjY2Y3ZWE0MTkiLCJ0eXBlIjoiQVBJX0tFWSIsIndvcmtzcGFjZUlkIjoiMjAyMDIwMjAtMWMyNS00ZDAyLWJmMjUtNmFlY2NmN2VhNDE5IiwiaWF0IjoxNzM1Njg5NjAwLCJleHAiOjQ4OTE0NDk2MDAsImp0aSI6IjIwMjAyMDIwLWY0MDEtNGQ4YS1hNzMxLTY0ZDAwN2MyN2JhZCJ9.bfQjfyN0NEtTCLE_xPyNcwonDzlSXFoP8kdCQTdnuDc'; + +// Make env vars available to globalSetup (test.env only applies to workers) +process.env.TWENTY_API_URL = TWENTY_API_URL; +process.env.TWENTY_API_KEY = TWENTY_API_KEY; + +export default defineConfig({ + plugins: [ + tsconfigPaths({ + projects: ['tsconfig.spec.json'], + ignoreConfigErrors: true, + }), + ], + test: { + testTimeout: 120_000, + hookTimeout: 120_000, + fileParallelism: false, + include: ['src/**/*.integration-test.ts'], + globalSetup: ['src/__tests__/global-setup.ts'], + env: { + TWENTY_API_URL, + TWENTY_API_KEY, + }, + }, +}); diff --git a/vitest.unit.config.ts b/vitest.unit.config.ts new file mode 100644 index 0000000..e0d1400 --- /dev/null +++ b/vitest.unit.config.ts @@ -0,0 +1,14 @@ +import tsconfigPaths from 'vite-tsconfig-paths'; +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + plugins: [ + tsconfigPaths({ + projects: ['tsconfig.spec.json'], + ignoreConfigErrors: true, + }), + ], + test: { + include: ['src/**/*.test.ts'], + }, +}); diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..2c1c59d --- /dev/null +++ b/yarn.lock @@ -0,0 +1,3492 @@ +# This file is generated by running "yarn install" inside your project. +# Manual changes might be lost - proceed with caution! + +__metadata: + version: 8 + cacheKey: 10c0 + +"@alcalzone/ansi-tokenize@npm:^0.2.4": + version: 0.2.5 + resolution: "@alcalzone/ansi-tokenize@npm:0.2.5" + dependencies: + ansi-styles: "npm:^6.2.1" + is-fullwidth-code-point: "npm:^5.0.0" + checksum: 10c0/dd8622288426b5b7dbf8b68d51d4b930cea591d0e3b9dbc3f523131464d78ac922c165fcb7ba5307f133d35dbcaa0e6648a1c3fb6a0dc2725546d6f867b70af4 + languageName: node + linkType: hard + +"@babel/runtime@npm:^7.29.2": + version: 7.29.7 + resolution: "@babel/runtime@npm:7.29.7" + checksum: 10c0/ca11572f7146b21e0bde6a9ed4bb6a89eafbee5f0944c7eb54d0d8a2dac962c33638a1d611e14faa71dfbb92b4b5f9236232208568a6b7d5c6f3f39ddb91771e + languageName: node + linkType: hard + +"@base-ui/react@npm:^1.5.0": + version: 1.7.0 + resolution: "@base-ui/react@npm:1.7.0" + dependencies: + "@babel/runtime": "npm:^7.29.2" + "@base-ui/utils": "npm:0.3.2" + "@floating-ui/react-dom": "npm:^2.1.9" + "@floating-ui/utils": "npm:^0.2.12" + use-sync-external-store: "npm:^1.6.0" + peerDependencies: + "@date-fns/tz": ^1.2.0 + "@types/react": ^17 || ^18 || ^19 + date-fns: ^4.0.0 + react: ^17 || ^18 || ^19 + react-dom: ^17 || ^18 || ^19 + peerDependenciesMeta: + "@date-fns/tz": + optional: true + "@types/react": + optional: true + date-fns: + optional: true + checksum: 10c0/05bc69f68431ff30a5fac98d8e75c77af7c27749d10aeac10ab7f26843a0a8b49ca5cff32654aad1a107cb7f44aeb1705c15138c9c7f7ddc19d6321fbb263e4d + languageName: node + linkType: hard + +"@base-ui/utils@npm:0.3.2": + version: 0.3.2 + resolution: "@base-ui/utils@npm:0.3.2" + dependencies: + "@babel/runtime": "npm:^7.29.2" + "@floating-ui/utils": "npm:^0.2.12" + reselect: "npm:^5.2.0" + use-sync-external-store: "npm:^1.6.0" + peerDependencies: + "@types/react": ^17 || ^18 || ^19 + react: ^17 || ^18 || ^19 + react-dom: ^17 || ^18 || ^19 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/b523339f350679915550605e59754b5db9806a41a3b390d6f6adad29fedc0caf084c8ad1bad6bb62a463360593382defb7d3ea941a94404ed9db0142fdae5800 + languageName: node + linkType: hard + +"@emnapi/runtime@npm:^1.11.3": + version: 1.11.3 + resolution: "@emnapi/runtime@npm:1.11.3" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10c0/a00f1020fefb9d4145c367f93a9fddb383a00da8ffd7871e20b19659890379b83aeecb9f84d7d0eda5456343f4a09eb05b0acb5153b0d3d889539dedb1ed87c3 + languageName: node + linkType: hard + +"@esbuild/aix-ppc64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/aix-ppc64@npm:0.28.2" + conditions: os=aix & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/android-arm64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/android-arm64@npm:0.28.2" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/android-arm@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/android-arm@npm:0.28.2" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@esbuild/android-x64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/android-x64@npm:0.28.2" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/darwin-arm64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/darwin-arm64@npm:0.28.2" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/darwin-x64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/darwin-x64@npm:0.28.2" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/freebsd-arm64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/freebsd-arm64@npm:0.28.2" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/freebsd-x64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/freebsd-x64@npm:0.28.2" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/linux-arm64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/linux-arm64@npm:0.28.2" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/linux-arm@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/linux-arm@npm:0.28.2" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@esbuild/linux-ia32@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/linux-ia32@npm:0.28.2" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/linux-loong64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/linux-loong64@npm:0.28.2" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + +"@esbuild/linux-mips64el@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/linux-mips64el@npm:0.28.2" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + +"@esbuild/linux-ppc64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/linux-ppc64@npm:0.28.2" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/linux-riscv64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/linux-riscv64@npm:0.28.2" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + +"@esbuild/linux-s390x@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/linux-s390x@npm:0.28.2" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + +"@esbuild/linux-x64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/linux-x64@npm:0.28.2" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/netbsd-arm64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/netbsd-arm64@npm:0.28.2" + conditions: os=netbsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/netbsd-x64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/netbsd-x64@npm:0.28.2" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/openbsd-arm64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/openbsd-arm64@npm:0.28.2" + conditions: os=openbsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/openbsd-x64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/openbsd-x64@npm:0.28.2" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/openharmony-arm64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/openharmony-arm64@npm:0.28.2" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/sunos-x64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/sunos-x64@npm:0.28.2" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/win32-arm64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/win32-arm64@npm:0.28.2" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/win32-ia32@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/win32-ia32@npm:0.28.2" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/win32-x64@npm:0.28.2": + version: 0.28.2 + resolution: "@esbuild/win32-x64@npm:0.28.2" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@floating-ui/core@npm:^1.8.0": + version: 1.8.0 + resolution: "@floating-ui/core@npm:1.8.0" + dependencies: + "@floating-ui/utils": "npm:^0.2.12" + checksum: 10c0/cece958f6fab0f8cd7740cd57988c4f8bc5356dca39dd4c093433d44c91952c3a02c7a54d93d7ed6c078b6544d531a29f66621d2839c8bfbb4467dca6aed7df0 + languageName: node + linkType: hard + +"@floating-ui/dom@npm:^1.8.0": + version: 1.8.0 + resolution: "@floating-ui/dom@npm:1.8.0" + dependencies: + "@floating-ui/core": "npm:^1.8.0" + "@floating-ui/utils": "npm:^0.2.12" + checksum: 10c0/c32b2de7a596b69cfffa909c7028825e14f6319be1b45da529f8943c89fe16b75a49c214e800c87ded3f172353d3b99163d50072f11410488e756961b7b629cc + languageName: node + linkType: hard + +"@floating-ui/react-dom@npm:^2.1.9": + version: 2.1.9 + resolution: "@floating-ui/react-dom@npm:2.1.9" + dependencies: + "@floating-ui/dom": "npm:^1.8.0" + peerDependencies: + react: ">=16.8.0" + react-dom: ">=16.8.0" + checksum: 10c0/9fa9851069738cb9591a1ab2da16e257f193eae5fd2d6d48ad0b1e73f3c049929b6805c5ad43d4af3c1da63c60b215b0ee4651a1fe5caeb80e66d9d423744348 + languageName: node + linkType: hard + +"@floating-ui/utils@npm:^0.2.12": + version: 0.2.12 + resolution: "@floating-ui/utils@npm:0.2.12" + checksum: 10c0/bb910b90d56991a62011afaf5f8e0c4b7fb6dab69403f4dd17fbd6eb25560b28d533dff9791f270b4f459852e9006a1077b5d73cbedb5e34d9424afc6a828314 + languageName: node + linkType: hard + +"@genql/runtime@npm:^2.10.0": + version: 2.10.0 + resolution: "@genql/runtime@npm:2.10.0" + dependencies: + "@types/qs": "npm:^6.9.0" + "@types/ws": "npm:^6.0.1" + graphql-query-batcher: "npm:^1.0.1" + isomorphic-unfetch: "npm:^3.0.0" + lodash: "npm:^4.17.20" + subscriptions-transport-ws: "npm:^0.9.16" + tslib: "npm:^2.0.0" + utility-types: "npm:^3.10.0" + ws: "npm:^6.1.4" + zen-observable-ts: "npm:^0.8.21" + peerDependencies: + graphql: "*" + checksum: 10c0/e2a886c2469c933681e2b0ddd6a5b7f4cb12932251ba460e3cf2db4246817da79313ea4ba9769ec7cbe53ab9c1cb81ad8fcce6a969cd241185b79398d2a4f3c6 + languageName: node + linkType: hard + +"@img/colour@npm:^1.1.0": + version: 1.1.0 + resolution: "@img/colour@npm:1.1.0" + checksum: 10c0/2ebea2c0bbaee73b99badcefa04e1e71d83f36e5369337d3121dca841f4569533c4e2faddda6d62dd247f0d5cca143711f9446c59bcce81e427ba433a7a94a17 + languageName: node + linkType: hard + +"@img/sharp-darwin-arm64@npm:0.35.4": + version: 0.35.4 + resolution: "@img/sharp-darwin-arm64@npm:0.35.4" + dependencies: + "@img/sharp-libvips-darwin-arm64": "npm:1.3.3" + dependenciesMeta: + "@img/sharp-libvips-darwin-arm64": + optional: true + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@img/sharp-darwin-x64@npm:0.35.4": + version: 0.35.4 + resolution: "@img/sharp-darwin-x64@npm:0.35.4" + dependencies: + "@img/sharp-libvips-darwin-x64": "npm:1.3.3" + dependenciesMeta: + "@img/sharp-libvips-darwin-x64": + optional: true + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@img/sharp-freebsd-wasm32@npm:0.35.4": + version: 0.35.4 + resolution: "@img/sharp-freebsd-wasm32@npm:0.35.4" + dependencies: + "@img/sharp-wasm32": "npm:0.35.4" + conditions: os=freebsd + languageName: node + linkType: hard + +"@img/sharp-libvips-darwin-arm64@npm:1.3.3": + version: 1.3.3 + resolution: "@img/sharp-libvips-darwin-arm64@npm:1.3.3" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@img/sharp-libvips-darwin-x64@npm:1.3.3": + version: 1.3.3 + resolution: "@img/sharp-libvips-darwin-x64@npm:1.3.3" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-arm64@npm:1.3.3": + version: 1.3.3 + resolution: "@img/sharp-libvips-linux-arm64@npm:1.3.3" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-arm@npm:1.3.3": + version: 1.3.3 + resolution: "@img/sharp-libvips-linux-arm@npm:1.3.3" + conditions: os=linux & cpu=arm & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-ppc64@npm:1.3.3": + version: 1.3.3 + resolution: "@img/sharp-libvips-linux-ppc64@npm:1.3.3" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-riscv64@npm:1.3.3": + version: 1.3.3 + resolution: "@img/sharp-libvips-linux-riscv64@npm:1.3.3" + conditions: os=linux & cpu=riscv64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-s390x@npm:1.3.3": + version: 1.3.3 + resolution: "@img/sharp-libvips-linux-s390x@npm:1.3.3" + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linux-x64@npm:1.3.3": + version: 1.3.3 + resolution: "@img/sharp-libvips-linux-x64@npm:1.3.3" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-libvips-linuxmusl-arm64@npm:1.3.3": + version: 1.3.3 + resolution: "@img/sharp-libvips-linuxmusl-arm64@npm:1.3.3" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@img/sharp-libvips-linuxmusl-x64@npm:1.3.3": + version: 1.3.3 + resolution: "@img/sharp-libvips-linuxmusl-x64@npm:1.3.3" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@img/sharp-linux-arm64@npm:0.35.4": + version: 0.35.4 + resolution: "@img/sharp-linux-arm64@npm:0.35.4" + dependencies: + "@img/sharp-libvips-linux-arm64": "npm:1.3.3" + dependenciesMeta: + "@img/sharp-libvips-linux-arm64": + optional: true + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linux-arm@npm:0.35.4": + version: 0.35.4 + resolution: "@img/sharp-linux-arm@npm:0.35.4" + dependencies: + "@img/sharp-libvips-linux-arm": "npm:1.3.3" + dependenciesMeta: + "@img/sharp-libvips-linux-arm": + optional: true + conditions: os=linux & cpu=arm & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linux-ppc64@npm:0.35.4": + version: 0.35.4 + resolution: "@img/sharp-linux-ppc64@npm:0.35.4" + dependencies: + "@img/sharp-libvips-linux-ppc64": "npm:1.3.3" + dependenciesMeta: + "@img/sharp-libvips-linux-ppc64": + optional: true + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linux-riscv64@npm:0.35.4": + version: 0.35.4 + resolution: "@img/sharp-linux-riscv64@npm:0.35.4" + dependencies: + "@img/sharp-libvips-linux-riscv64": "npm:1.3.3" + dependenciesMeta: + "@img/sharp-libvips-linux-riscv64": + optional: true + conditions: os=linux & cpu=riscv64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linux-s390x@npm:0.35.4": + version: 0.35.4 + resolution: "@img/sharp-linux-s390x@npm:0.35.4" + dependencies: + "@img/sharp-libvips-linux-s390x": "npm:1.3.3" + dependenciesMeta: + "@img/sharp-libvips-linux-s390x": + optional: true + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linux-x64@npm:0.35.4": + version: 0.35.4 + resolution: "@img/sharp-linux-x64@npm:0.35.4" + dependencies: + "@img/sharp-libvips-linux-x64": "npm:1.3.3" + dependenciesMeta: + "@img/sharp-libvips-linux-x64": + optional: true + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@img/sharp-linuxmusl-arm64@npm:0.35.4": + version: 0.35.4 + resolution: "@img/sharp-linuxmusl-arm64@npm:0.35.4" + dependencies: + "@img/sharp-libvips-linuxmusl-arm64": "npm:1.3.3" + dependenciesMeta: + "@img/sharp-libvips-linuxmusl-arm64": + optional: true + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@img/sharp-linuxmusl-x64@npm:0.35.4": + version: 0.35.4 + resolution: "@img/sharp-linuxmusl-x64@npm:0.35.4" + dependencies: + "@img/sharp-libvips-linuxmusl-x64": "npm:1.3.3" + dependenciesMeta: + "@img/sharp-libvips-linuxmusl-x64": + optional: true + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@img/sharp-wasm32@npm:0.35.4": + version: 0.35.4 + resolution: "@img/sharp-wasm32@npm:0.35.4" + dependencies: + "@emnapi/runtime": "npm:^1.11.3" + checksum: 10c0/7f5e394f168eb2f038b8ae4949fb5434085d1db931ede2ae1389479d7a1dbe11e2fc9d58e8e9830bff744ba996256c9e411be1d466dfcbf6e1f4d153b84f25ff + languageName: node + linkType: hard + +"@img/sharp-webcontainers-wasm32@npm:0.35.4": + version: 0.35.4 + resolution: "@img/sharp-webcontainers-wasm32@npm:0.35.4" + dependencies: + "@img/sharp-wasm32": "npm:0.35.4" + conditions: cpu=wasm32 + languageName: node + linkType: hard + +"@img/sharp-win32-arm64@npm:0.35.4": + version: 0.35.4 + resolution: "@img/sharp-win32-arm64@npm:0.35.4" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@img/sharp-win32-ia32@npm:0.35.4": + version: 0.35.4 + resolution: "@img/sharp-win32-ia32@npm:0.35.4" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@img/sharp-win32-x64@npm:0.35.4": + version: 0.35.4 + resolution: "@img/sharp-win32-x64@npm:0.35.4" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@inquirer/ansi@npm:^2.0.7": + version: 2.0.7 + resolution: "@inquirer/ansi@npm:2.0.7" + checksum: 10c0/a574f97a899f0d9346fa26b528b3f4a9ba6dcb9172288efb6b4314d8486470ed53d2f538200f66a25b843c6e0cbf83688c6d5174a8dc6eca853b291b09609c5a + languageName: node + linkType: hard + +"@inquirer/checkbox@npm:^5.2.2": + version: 5.2.2 + resolution: "@inquirer/checkbox@npm:5.2.2" + dependencies: + "@inquirer/ansi": "npm:^2.0.7" + "@inquirer/core": "npm:^12.0.0" + "@inquirer/figures": "npm:^2.0.8" + "@inquirer/type": "npm:^4.0.7" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10c0/b71cc4e06a69e3a4bcc3ebbd29476d9ea205299837e8677939135eda59ec9fd9eb8d2250741a6d5060754d75b7003f74d26f44172618b55f0c731b0a5ebe1a54 + languageName: node + linkType: hard + +"@inquirer/confirm@npm:^6.2.0": + version: 6.2.0 + resolution: "@inquirer/confirm@npm:6.2.0" + dependencies: + "@inquirer/core": "npm:^12.0.0" + "@inquirer/type": "npm:^4.0.7" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10c0/dd673ce6db0e0e7ae96c5a4520ae24cb2e10f33e2347bdc99805aaaccd310c05a338c87f56d350a290ca60d9c65bbd1db8fd500e432ab6074d39140cafc793d6 + languageName: node + linkType: hard + +"@inquirer/core@npm:^12.0.0": + version: 12.0.0 + resolution: "@inquirer/core@npm:12.0.0" + dependencies: + "@inquirer/ansi": "npm:^2.0.7" + "@inquirer/figures": "npm:^2.0.8" + "@inquirer/type": "npm:^4.0.7" + cli-width: "npm:^4.1.0" + fast-wrap-ansi: "npm:^0.2.0" + mute-stream: "npm:^3.0.0" + signal-exit: "npm:^4.1.0" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10c0/070f1978e5f306f5e7588c1096ac11481276635233f6f683dd1fa0e2236faf26a00709a09aa37af9479c3bd94645e73d8000cf6834527883a5bc7007832fd5ad + languageName: node + linkType: hard + +"@inquirer/editor@npm:^5.3.0": + version: 5.3.0 + resolution: "@inquirer/editor@npm:5.3.0" + dependencies: + "@inquirer/core": "npm:^12.0.0" + "@inquirer/external-editor": "npm:^3.0.4" + "@inquirer/type": "npm:^4.0.7" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10c0/151b1e0c4405d94d7d8e16618ba39de6e32efee563f99eb6c4c2225901467a211230327baad9c0940fae4a3c1e7f443908bf0849e509fbe5cd288f939d62d9f2 + languageName: node + linkType: hard + +"@inquirer/expand@npm:^5.1.2": + version: 5.1.2 + resolution: "@inquirer/expand@npm:5.1.2" + dependencies: + "@inquirer/core": "npm:^12.0.0" + "@inquirer/type": "npm:^4.0.7" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10c0/3ca2cd5e42802b0c7daad15c2d44c16ef04fffb6f434e676185afba9868aa561930c5a19032cae0066abfe7820949ed2776ca4d8bf03519009ef2cc7472db900 + languageName: node + linkType: hard + +"@inquirer/external-editor@npm:^3.0.4": + version: 3.0.4 + resolution: "@inquirer/external-editor@npm:3.0.4" + dependencies: + chardet: "npm:^2.1.1" + iconv-lite: "npm:^0.7.2" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10c0/ec50397f0132aca03e026690466dfb1a63203744926513eb26e0d950004e4c2359bfab24405ea7afa50d3cb1620707b81b1c2751bd5dbee2f8f7d1905be61f05 + languageName: node + linkType: hard + +"@inquirer/figures@npm:^2.0.8": + version: 2.0.8 + resolution: "@inquirer/figures@npm:2.0.8" + checksum: 10c0/49fd3b196c783c09d64c9f5692f9c26d0740169b99b55c97adc00e0f4e5665ec4097ab9bff40360c29c4b6b348b315b4eaadbab6cdeb5cb8335a8ac44ec62168 + languageName: node + linkType: hard + +"@inquirer/input@npm:^5.1.3": + version: 5.1.3 + resolution: "@inquirer/input@npm:5.1.3" + dependencies: + "@inquirer/core": "npm:^12.0.0" + "@inquirer/type": "npm:^4.0.7" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10c0/0a6d200ca16a0e422a51af4f5a879448850bb2b43bb7e4ae7815cd56c6ca9083ed333dedb7700009f61d3d227bb84e46a6cdd74f7863057b9134a122239acfe9 + languageName: node + linkType: hard + +"@inquirer/number@npm:^4.2.0": + version: 4.2.0 + resolution: "@inquirer/number@npm:4.2.0" + dependencies: + "@inquirer/core": "npm:^12.0.0" + "@inquirer/type": "npm:^4.0.7" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10c0/ccde39bc940b324874d456b9dbf110e6c9d501a904229ea0f858689224e1d38e6b419932779d15c11b28af35f7b7b955337b1d173348a78bf999602f87eaf68f + languageName: node + linkType: hard + +"@inquirer/password@npm:^5.1.2": + version: 5.1.2 + resolution: "@inquirer/password@npm:5.1.2" + dependencies: + "@inquirer/ansi": "npm:^2.0.7" + "@inquirer/core": "npm:^12.0.0" + "@inquirer/type": "npm:^4.0.7" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10c0/5b0fc1f7e81e64c9f379c3a303ee317cf237b470dc61e1b54afb29a670bceaf6369d6d0dc4b63471590fc79d82b6e1fdcdb7a4ccfe22117478596f9775302a66 + languageName: node + linkType: hard + +"@inquirer/prompts@npm:^8.6.0": + version: 8.6.0 + resolution: "@inquirer/prompts@npm:8.6.0" + dependencies: + "@inquirer/checkbox": "npm:^5.2.2" + "@inquirer/confirm": "npm:^6.2.0" + "@inquirer/editor": "npm:^5.3.0" + "@inquirer/expand": "npm:^5.1.2" + "@inquirer/input": "npm:^5.1.3" + "@inquirer/number": "npm:^4.2.0" + "@inquirer/password": "npm:^5.1.2" + "@inquirer/rawlist": "npm:^5.3.2" + "@inquirer/search": "npm:^4.3.0" + "@inquirer/select": "npm:^5.2.2" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10c0/9d175bc185cd7d5df0e0560b58bb40b5339f099b223c1bdf5b3ded8b1117030eb89f0a38056a4971dd59e78c8e88a639a7f61751a8e850af29791e9da5ed2277 + languageName: node + linkType: hard + +"@inquirer/rawlist@npm:^5.3.2": + version: 5.3.2 + resolution: "@inquirer/rawlist@npm:5.3.2" + dependencies: + "@inquirer/core": "npm:^12.0.0" + "@inquirer/type": "npm:^4.0.7" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10c0/5ea4af1142e111a49fe92d5d4695fb9eff9a787188a2b349ea0caca1c3ed60683fc0247f9a375148ba14c5d4f36c90f83fa08de8d4ddfd63df9027fd40ef8bf1 + languageName: node + linkType: hard + +"@inquirer/search@npm:^4.3.0": + version: 4.3.0 + resolution: "@inquirer/search@npm:4.3.0" + dependencies: + "@inquirer/core": "npm:^12.0.0" + "@inquirer/figures": "npm:^2.0.8" + "@inquirer/type": "npm:^4.0.7" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10c0/7a5fd304dd2b8ebc1698954997aff5eb0a1e74ce89bb23f87041455d5f5d449c970f49179a477ceca5fe1beb86af919b49aa978c1f50a4145f909575295c9dc1 + languageName: node + linkType: hard + +"@inquirer/select@npm:^5.2.2": + version: 5.2.2 + resolution: "@inquirer/select@npm:5.2.2" + dependencies: + "@inquirer/ansi": "npm:^2.0.7" + "@inquirer/core": "npm:^12.0.0" + "@inquirer/figures": "npm:^2.0.8" + "@inquirer/type": "npm:^4.0.7" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10c0/deffe20aed1953533c2cbc6ea66972524da0d01ffef49b39de4a083011d6b931c21b8f0c8d3a8e4e37e2d196fbfc4afe9b3dc51315708cb7687d2d79f7027d86 + languageName: node + linkType: hard + +"@inquirer/type@npm:^4.0.7": + version: 4.0.7 + resolution: "@inquirer/type@npm:4.0.7" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10c0/80678ac1c6e19ce309909e4a54a69adc95697ea3abc2cb92f17b1bc52f4caadbcb4003ae7339fb5a70c0d36d3bde975e1bb4450069662f41c953a0d28695bb70 + languageName: node + linkType: hard + +"@isaacs/fs-minipass@npm:^4.0.0": + version: 4.0.1 + resolution: "@isaacs/fs-minipass@npm:4.0.1" + dependencies: + minipass: "npm:^7.0.4" + checksum: 10c0/c25b6dc1598790d5b55c0947a9b7d111cfa92594db5296c3b907e2f533c033666f692a3939eadac17b1c7c40d362d0b0635dc874cbfe3e70db7c2b07cc97a5d2 + languageName: node + linkType: hard + +"@jridgewell/sourcemap-codec@npm:^1.5.5": + version: 1.5.5 + resolution: "@jridgewell/sourcemap-codec@npm:1.5.5" + checksum: 10c0/f9e538f302b63c0ebc06eecb1dd9918dd4289ed36147a0ddce35d6ea4d7ebbda243cda7b2213b6a5e1d8087a298d5cf630fb2bd39329cdecb82017023f6081a0 + languageName: node + linkType: hard + +"@monaco-editor/loader@npm:^1.5.0": + version: 1.7.0 + resolution: "@monaco-editor/loader@npm:1.7.0" + dependencies: + state-local: "npm:^1.0.6" + checksum: 10c0/1fd3579cb09b669493cf553dfc0723a93483140a44353ce9a739827edfa7b2c6541b254024d15c48176ece749ef666b6d16cce6cf0e4396c7fa1c5a48012d08a + languageName: node + linkType: hard + +"@monaco-editor/react@npm:^4.7.0": + version: 4.7.0 + resolution: "@monaco-editor/react@npm:4.7.0" + dependencies: + "@monaco-editor/loader": "npm:^1.5.0" + peerDependencies: + monaco-editor: ">= 0.25.0 < 1" + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + checksum: 10c0/a4e91c6eda1a71f5fd23871bd801de435490ccbc43934d23cba65cefe2be7e9d02c9a57c4ef80fc9fe99e4d4deea51e5db19cb4e0ecf3f51818dda0eb9cdbf12 + languageName: node + linkType: hard + +"@oxc-project/types@npm:=0.147.0": + version: 0.147.0 + resolution: "@oxc-project/types@npm:0.147.0" + checksum: 10c0/adec7c1d4c0a031a83afe976884695ff1467987da3f9e775d24bd596ca82346d3734cc9898f5645955050246a13621b4442696753a2d83d2954127fbdfbd5a5f + languageName: node + linkType: hard + +"@oxlint/darwin-arm64@npm:0.16.12": + version: 0.16.12 + resolution: "@oxlint/darwin-arm64@npm:0.16.12" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@oxlint/darwin-x64@npm:0.16.12": + version: 0.16.12 + resolution: "@oxlint/darwin-x64@npm:0.16.12" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@oxlint/linux-arm64-gnu@npm:0.16.12": + version: 0.16.12 + resolution: "@oxlint/linux-arm64-gnu@npm:0.16.12" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@oxlint/linux-arm64-musl@npm:0.16.12": + version: 0.16.12 + resolution: "@oxlint/linux-arm64-musl@npm:0.16.12" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@oxlint/linux-x64-gnu@npm:0.16.12": + version: 0.16.12 + resolution: "@oxlint/linux-x64-gnu@npm:0.16.12" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@oxlint/linux-x64-musl@npm:0.16.12": + version: 0.16.12 + resolution: "@oxlint/linux-x64-musl@npm:0.16.12" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@oxlint/win32-arm64@npm:0.16.12": + version: 0.16.12 + resolution: "@oxlint/win32-arm64@npm:0.16.12" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@oxlint/win32-x64@npm:0.16.12": + version: 0.16.12 + resolution: "@oxlint/win32-x64@npm:0.16.12" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@radix-ui/colors@npm:^3.0.0": + version: 3.0.0 + resolution: "@radix-ui/colors@npm:3.0.0" + checksum: 10c0/612c6bb4efe987f2d49aaca2f785687d08150da59a248328bc2b4e187ece4ef731c1dc5c6a9430fffcac4af9e65cf2f89ceb9806f8809940c9eb238b79d2cf2f + languageName: node + linkType: hard + +"@remix-run/router@npm:1.23.4": + version: 1.23.4 + resolution: "@remix-run/router@npm:1.23.4" + checksum: 10c0/ef17eb2a606c125f09c55683585099725421af1c11f1c1d1c3841fe4eea3f99eb56b26ecbb17429a173c704ecae336c37e0d74855faa9330667433ef4f419436 + languageName: node + linkType: hard + +"@rolldown/binding-android-arm-eabi@npm:1.2.6": + version: 1.2.6 + resolution: "@rolldown/binding-android-arm-eabi@npm:1.2.6" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@rolldown/binding-android-arm64@npm:1.2.6": + version: 1.2.6 + resolution: "@rolldown/binding-android-arm64@npm:1.2.6" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@rolldown/binding-darwin-arm64@npm:1.2.6": + version: 1.2.6 + resolution: "@rolldown/binding-darwin-arm64@npm:1.2.6" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@rolldown/binding-darwin-x64@npm:1.2.6": + version: 1.2.6 + resolution: "@rolldown/binding-darwin-x64@npm:1.2.6" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@rolldown/binding-freebsd-x64@npm:1.2.6": + version: 1.2.6 + resolution: "@rolldown/binding-freebsd-x64@npm:1.2.6" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@rolldown/binding-linux-arm-gnueabihf@npm:1.2.6": + version: 1.2.6 + resolution: "@rolldown/binding-linux-arm-gnueabihf@npm:1.2.6" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@rolldown/binding-linux-arm64-gnu@npm:1.2.6": + version: 1.2.6 + resolution: "@rolldown/binding-linux-arm64-gnu@npm:1.2.6" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@rolldown/binding-linux-arm64-musl@npm:1.2.6": + version: 1.2.6 + resolution: "@rolldown/binding-linux-arm64-musl@npm:1.2.6" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@rolldown/binding-linux-ppc64-gnu@npm:1.2.6": + version: 1.2.6 + resolution: "@rolldown/binding-linux-ppc64-gnu@npm:1.2.6" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@rolldown/binding-linux-s390x-gnu@npm:1.2.6": + version: 1.2.6 + resolution: "@rolldown/binding-linux-s390x-gnu@npm:1.2.6" + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@rolldown/binding-linux-x64-gnu@npm:1.2.6": + version: 1.2.6 + resolution: "@rolldown/binding-linux-x64-gnu@npm:1.2.6" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@rolldown/binding-linux-x64-musl@npm:1.2.6": + version: 1.2.6 + resolution: "@rolldown/binding-linux-x64-musl@npm:1.2.6" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@rolldown/binding-openharmony-arm64@npm:1.2.6": + version: 1.2.6 + resolution: "@rolldown/binding-openharmony-arm64@npm:1.2.6" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + +"@rolldown/binding-win32-arm64-msvc@npm:1.2.6": + version: 1.2.6 + resolution: "@rolldown/binding-win32-arm64-msvc@npm:1.2.6" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@rolldown/binding-win32-x64-msvc@npm:1.2.6": + version: 1.2.6 + resolution: "@rolldown/binding-win32-x64-msvc@npm:1.2.6" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@rolldown/pluginutils@npm:^1.0.0": + version: 1.0.1 + resolution: "@rolldown/pluginutils@npm:1.0.1" + checksum: 10c0/99d9b06d90196823e4d8c841f258db7a16e5dbba5824a2962b05d907b79f1ba929d56f22dd744fd530936e568c865ee56a719dc31e57e13bc0a8eb4764a8d8dd + languageName: node + linkType: hard + +"@sniptt/guards@npm:^0.2.0": + version: 0.2.0 + resolution: "@sniptt/guards@npm:0.2.0" + checksum: 10c0/749bb0f550d1ddd4abdb23dc1076cba26e977659922b73d000bceeb253c09270aaced0d18e92f09d4ce2fdaae33e55f60f2142f5359bc90ba505422af0873526 + languageName: node + linkType: hard + +"@standard-schema/spec@npm:^1.1.0": + version: 1.1.0 + resolution: "@standard-schema/spec@npm:1.1.0" + checksum: 10c0/d90f55acde4b2deb983529c87e8025fa693de1a5e8b49ecc6eb84d1fd96328add0e03d7d551442156c7432fd78165b2c26ff561b970a9a881f046abb78d6a526 + languageName: node + linkType: hard + +"@tabler/icons-react@npm:^3.31.0": + version: 3.46.0 + resolution: "@tabler/icons-react@npm:3.46.0" + dependencies: + "@tabler/icons": "npm:3.46.0" + peerDependencies: + react: ">= 16" + checksum: 10c0/8fab7d3a9ec6b62724a1628b8bad68a7b503979e4418f97d057c442adc5257b227a08185a884d452d171983648e36fa92cd7bfe9457f693e9b67bbf4ecf8ec24 + languageName: node + linkType: hard + +"@tabler/icons@npm:3.46.0": + version: 3.46.0 + resolution: "@tabler/icons@npm:3.46.0" + checksum: 10c0/c9671449c2f53fa7e329c487162f89a28417cdd5ce96cfa290fe21388724da303dd2297af3b39a88a74b02c36ce55598bc3cf6537dbddf548881da2c05f35f5e + languageName: node + linkType: hard + +"@types/chai@npm:^5.2.2": + version: 5.2.3 + resolution: "@types/chai@npm:5.2.3" + dependencies: + "@types/deep-eql": "npm:*" + assertion-error: "npm:^2.0.1" + checksum: 10c0/e0ef1de3b6f8045a5e473e867c8565788c444271409d155588504840ad1a53611011f85072188c2833941189400228c1745d78323dac13fcede9c2b28bacfb2f + languageName: node + linkType: hard + +"@types/deep-eql@npm:*": + version: 4.0.2 + resolution: "@types/deep-eql@npm:4.0.2" + checksum: 10c0/bf3f811843117900d7084b9d0c852da9a044d12eb40e6de73b552598a6843c21291a8a381b0532644574beecd5e3491c5ff3a0365ab86b15d59862c025384844 + languageName: node + linkType: hard + +"@types/estree@npm:^1.0.0": + version: 1.0.9 + resolution: "@types/estree@npm:1.0.9" + checksum: 10c0/3ad3286ca2988cd550dafb8f2ad599c8474868e954fa601a36655bdfefd8039f7c714b8c1c7f2ae219ffbd58bd4660e66fa7479a0120fc02d4777057d4865387 + languageName: node + linkType: hard + +"@types/node@npm:*": + version: 26.3.0 + resolution: "@types/node@npm:26.3.0" + dependencies: + undici-types: "npm:~8.3.0" + checksum: 10c0/b1b71d722cea67807abde4993d71aa6e4e24ae3072635ff5fbca3e5fbe7f0d4adc20507ab3f1e14b13720d742fb5cae4b12c7326fbe706fcda24e2aab8306374 + languageName: node + linkType: hard + +"@types/node@npm:^24.7.2": + version: 24.13.3 + resolution: "@types/node@npm:24.13.3" + dependencies: + undici-types: "npm:~7.18.0" + checksum: 10c0/a5bc08f49b9581dcdca90e02cd77197a3c799840807664942e5cd5161a553d4d2ae8064f7e3294410d748d7d098b5c1848be7fa50c06f29c4193ab16be4e59eb + languageName: node + linkType: hard + +"@types/qs@npm:^6.9.0": + version: 6.15.1 + resolution: "@types/qs@npm:6.15.1" + checksum: 10c0/1dfdbcb4cf2a8f66d57f0b9a9fe6b1c7091cb816687b6698c1351eaf31f62e412cea9b7453a9637b570cd5fad8dced527e5a9e69b4fcc6e318daacd8b749f094 + languageName: node + linkType: hard + +"@types/react@npm:^19.0.0": + version: 19.2.18 + resolution: "@types/react@npm:19.2.18" + dependencies: + csstype: "npm:^3.2.2" + checksum: 10c0/d04216172b4b4362b310017c210dfbe019fb4f4e7dffd0313e70b3acb3051d5c3e76e7e84e3cf4c6a3c824993ffadfe3949e589a6398a09d4200e7670e2962de + languageName: node + linkType: hard + +"@types/ws@npm:^6.0.1": + version: 6.0.4 + resolution: "@types/ws@npm:6.0.4" + dependencies: + "@types/node": "npm:*" + checksum: 10c0/fa958e64596ca9487c3ed6012834de70b47f25d971f1950cfb8e6a99cb77ff340ae82ac7627744e01b58010674ef8ede07d5a2ac29ca9ad0d67a430fcc69ae14 + languageName: node + linkType: hard + +"@typescript/native-preview-darwin-arm64@npm:7.0.0-dev.20260707.2": + version: 7.0.0-dev.20260707.2 + resolution: "@typescript/native-preview-darwin-arm64@npm:7.0.0-dev.20260707.2" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@typescript/native-preview-darwin-x64@npm:7.0.0-dev.20260707.2": + version: 7.0.0-dev.20260707.2 + resolution: "@typescript/native-preview-darwin-x64@npm:7.0.0-dev.20260707.2" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@typescript/native-preview-linux-arm64@npm:7.0.0-dev.20260707.2": + version: 7.0.0-dev.20260707.2 + resolution: "@typescript/native-preview-linux-arm64@npm:7.0.0-dev.20260707.2" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + +"@typescript/native-preview-linux-arm@npm:7.0.0-dev.20260707.2": + version: 7.0.0-dev.20260707.2 + resolution: "@typescript/native-preview-linux-arm@npm:7.0.0-dev.20260707.2" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@typescript/native-preview-linux-x64@npm:7.0.0-dev.20260707.2": + version: 7.0.0-dev.20260707.2 + resolution: "@typescript/native-preview-linux-x64@npm:7.0.0-dev.20260707.2" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + +"@typescript/native-preview-win32-arm64@npm:7.0.0-dev.20260707.2": + version: 7.0.0-dev.20260707.2 + resolution: "@typescript/native-preview-win32-arm64@npm:7.0.0-dev.20260707.2" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@typescript/native-preview-win32-x64@npm:7.0.0-dev.20260707.2": + version: 7.0.0-dev.20260707.2 + resolution: "@typescript/native-preview-win32-x64@npm:7.0.0-dev.20260707.2" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@typescript/native-preview@npm:^7.0.0-dev.20260116.1": + version: 7.0.0-dev.20260707.2 + resolution: "@typescript/native-preview@npm:7.0.0-dev.20260707.2" + dependencies: + "@typescript/native-preview-darwin-arm64": "npm:7.0.0-dev.20260707.2" + "@typescript/native-preview-darwin-x64": "npm:7.0.0-dev.20260707.2" + "@typescript/native-preview-linux-arm": "npm:7.0.0-dev.20260707.2" + "@typescript/native-preview-linux-arm64": "npm:7.0.0-dev.20260707.2" + "@typescript/native-preview-linux-x64": "npm:7.0.0-dev.20260707.2" + "@typescript/native-preview-win32-arm64": "npm:7.0.0-dev.20260707.2" + "@typescript/native-preview-win32-x64": "npm:7.0.0-dev.20260707.2" + dependenciesMeta: + "@typescript/native-preview-darwin-arm64": + optional: true + "@typescript/native-preview-darwin-x64": + optional: true + "@typescript/native-preview-linux-arm": + optional: true + "@typescript/native-preview-linux-arm64": + optional: true + "@typescript/native-preview-linux-x64": + optional: true + "@typescript/native-preview-win32-arm64": + optional: true + "@typescript/native-preview-win32-x64": + optional: true + bin: + tsgo: bin/tsgo + checksum: 10c0/42c527da5c4d61884e50591dba0a37e6d98f992e355037213f2039d606dd9fddfbacee308d40b4f3b4cfa06f79b07b7e86b45b92a83a9e60741f65e07a031982 + languageName: node + linkType: hard + +"@vitest/expect@npm:4.1.11": + version: 4.1.11 + resolution: "@vitest/expect@npm:4.1.11" + dependencies: + "@standard-schema/spec": "npm:^1.1.0" + "@types/chai": "npm:^5.2.2" + "@vitest/spy": "npm:4.1.11" + "@vitest/utils": "npm:4.1.11" + chai: "npm:^6.2.2" + tinyrainbow: "npm:^3.1.0" + checksum: 10c0/0aa5e0973aca93a58cbdc3041c6bfed5897e976124965203b96a23b811f4ca403590c7eb15802c8d8366ec27a1db0682451e8a91c4d06617636de262baf86e4b + languageName: node + linkType: hard + +"@vitest/mocker@npm:4.1.11": + version: 4.1.11 + resolution: "@vitest/mocker@npm:4.1.11" + dependencies: + "@vitest/spy": "npm:4.1.11" + estree-walker: "npm:^3.0.3" + magic-string: "npm:^0.30.21" + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + checksum: 10c0/3111ea34bd5046f6c70bbd67cf8b89608ee8c41cb27ab2bd61d42f4b68810e9ea16a9a757a71bc254c105f73b407d00ebb6bab1ab0f7f5cdcc9d7d16602a3933 + languageName: node + linkType: hard + +"@vitest/pretty-format@npm:4.1.11": + version: 4.1.11 + resolution: "@vitest/pretty-format@npm:4.1.11" + dependencies: + tinyrainbow: "npm:^3.1.0" + checksum: 10c0/ad32525c73807c0b72f38dc29bc51fd5a17879dc650f37995a9c5adbb8526e15f787691f76aa8768448ec7ed5bf5ba2b12329eac8fda1428b4d6b8c036390f71 + languageName: node + linkType: hard + +"@vitest/runner@npm:4.1.11": + version: 4.1.11 + resolution: "@vitest/runner@npm:4.1.11" + dependencies: + "@vitest/utils": "npm:4.1.11" + pathe: "npm:^2.0.3" + checksum: 10c0/3c782b055e9e688e1785f7c8937bd1669bad1b0e5758cb8b844f918f30a324b1d21e174d46c941ce80ebf37f2b89b55b6d619a675025914a1ece6377b95909e2 + languageName: node + linkType: hard + +"@vitest/snapshot@npm:4.1.11": + version: 4.1.11 + resolution: "@vitest/snapshot@npm:4.1.11" + dependencies: + "@vitest/pretty-format": "npm:4.1.11" + "@vitest/utils": "npm:4.1.11" + magic-string: "npm:^0.30.21" + pathe: "npm:^2.0.3" + checksum: 10c0/35d82a7c2a3e4b57529c30387d568d9106b6bf960189214e5d8cb1f5288cb042305c2e5c7b0b2f8cb56a2c814973de0310bc56d72deb79427bea272b6224190c + languageName: node + linkType: hard + +"@vitest/spy@npm:4.1.11": + version: 4.1.11 + resolution: "@vitest/spy@npm:4.1.11" + checksum: 10c0/06c68247a8efd21006abe7532fee17f30ba83c8cda3e0b952ef89e278d58058f4b1de69b6bbaa2ed614c568bf4f5769fcc76be42802dedf2bcdd9c0aae601740 + languageName: node + linkType: hard + +"@vitest/utils@npm:4.1.11": + version: 4.1.11 + resolution: "@vitest/utils@npm:4.1.11" + dependencies: + "@vitest/pretty-format": "npm:4.1.11" + convert-source-map: "npm:^2.0.0" + tinyrainbow: "npm:^3.1.0" + checksum: 10c0/a2c1ddc64333458c3e031465c1ee0440a7660fd1b298c54ccbf865ef1e5cf3ebdd6c5c75a5ea235d8a672498b394e7121f992b096f021f45014ab25e9abd1b52 + languageName: node + linkType: hard + +"abbrev@npm:^5.0.0": + version: 5.0.0 + resolution: "abbrev@npm:5.0.0" + checksum: 10c0/8e88f5c798ea4562d28c5a3e9ad69e3879890bc5d695d8f2dffb8609be4c890aacc8f80ef4553fdd2c6a62d70c2ce8bc57b38074e383beb7487bdafa9ed42ea5 + languageName: node + linkType: hard + +"agent-base@npm:6": + version: 6.0.2 + resolution: "agent-base@npm:6.0.2" + dependencies: + debug: "npm:4" + checksum: 10c0/dc4f757e40b5f3e3d674bc9beb4f1048f4ee83af189bae39be99f57bf1f48dde166a8b0a5342a84b5944ee8e6ed1e5a9d801858f4ad44764e84957122fe46261 + languageName: node + linkType: hard + +"ansi-escapes@npm:^7.3.0": + version: 7.3.0 + resolution: "ansi-escapes@npm:7.3.0" + dependencies: + environment: "npm:^1.0.0" + checksum: 10c0/068961d99f0ef28b661a4a9f84a5d645df93ccf3b9b93816cc7d46bbe1913321d4cdf156bb842a4e1e4583b7375c631fa963efb43001c4eb7ff9ab8f78fc0679 + languageName: node + linkType: hard + +"ansi-regex@npm:^6.2.2": + version: 6.3.0 + resolution: "ansi-regex@npm:6.3.0" + checksum: 10c0/bc047786d0531f1f22d1e22e9863e8554488a399f1ee5270b753efa6de938a788d27456f2b256770f11fdd8b1e847483bc1b55ddc2b3ffe82ec45c875292c651 + languageName: node + linkType: hard + +"ansi-styles@npm:^6.2.1, ansi-styles@npm:^6.2.3": + version: 6.2.3 + resolution: "ansi-styles@npm:6.2.3" + checksum: 10c0/23b8a4ce14e18fb854693b95351e286b771d23d8844057ed2e7d083cd3e708376c3323707ec6a24365f7d7eda3ca00327fe04092e29e551499ec4c8b7bfac868 + languageName: node + linkType: hard + +"assertion-error@npm:^2.0.1": + version: 2.0.1 + resolution: "assertion-error@npm:2.0.1" + checksum: 10c0/bbbcb117ac6480138f8c93cf7f535614282dea9dc828f540cdece85e3c665e8f78958b96afac52f29ff883c72638e6a87d469ecc9fe5bc902df03ed24a55dba8 + languageName: node + linkType: hard + +"async-function@npm:^1.0.0": + version: 1.0.0 + resolution: "async-function@npm:1.0.0" + checksum: 10c0/669a32c2cb7e45091330c680e92eaeb791bc1d4132d827591e499cd1f776ff5a873e77e5f92d0ce795a8d60f10761dec9ddfe7225a5de680f5d357f67b1aac73 + languageName: node + linkType: hard + +"async-generator-function@npm:^1.0.0": + version: 1.0.0 + resolution: "async-generator-function@npm:1.0.0" + checksum: 10c0/2c50ef856c543ad500d8d8777d347e3c1ba623b93e99c9263ecc5f965c1b12d2a140e2ab6e43c3d0b85366110696f28114649411cbcd10b452a92a2318394186 + languageName: node + linkType: hard + +"async-limiter@npm:~1.0.0": + version: 1.0.1 + resolution: "async-limiter@npm:1.0.1" + checksum: 10c0/0693d378cfe86842a70d4c849595a0bb50dc44c11649640ca982fa90cbfc74e3cc4753b5a0847e51933f2e9c65ce8e05576e75e5e1fd963a086e673735b35969 + languageName: node + linkType: hard + +"asynckit@npm:^0.4.0": + version: 0.4.0 + resolution: "asynckit@npm:0.4.0" + checksum: 10c0/d73e2ddf20c4eb9337e1b3df1a0f6159481050a5de457c55b14ea2e5cb6d90bb69e004c9af54737a5ee0917fcf2c9e25de67777bbe58261847846066ba75bc9d + languageName: node + linkType: hard + +"aura-twenty@workspace:.": + version: 0.0.0-use.local + resolution: "aura-twenty@workspace:." + dependencies: + "@types/node": "npm:^24.7.2" + "@types/react": "npm:^19.0.0" + "@typescript/native-preview": "npm:^7.0.0-dev.20260116.1" + oxlint: "npm:^0.16.0" + react: "npm:^19.0.0" + react-dom: "npm:^19.0.0" + twenty-client-sdk: "npm:2.35.1" + twenty-sdk: "npm:2.35.1" + twenty-ui: "npm:1.0.0-alpha.1" + typescript: "npm:^5.9.3" + vite-tsconfig-paths: "npm:^4.2.1" + vitest: "npm:^4.0.0" + languageName: unknown + linkType: soft + +"auto-bind@npm:^5.0.1": + version: 5.0.1 + resolution: "auto-bind@npm:5.0.1" + checksum: 10c0/a703375350ea7b6e92405d8e6bcc6dbfb84b0d7c7172b33e5788a7593929a18227999ff9aa9c32436741d06d021e6672457b1cec73287efe3fab95cff6627eaf + languageName: node + linkType: hard + +"axios@npm:^1.16.0": + version: 1.20.0 + resolution: "axios@npm:1.20.0" + dependencies: + follow-redirects: "npm:^1.16.0" + form-data: "npm:^4.0.6" + https-proxy-agent: "npm:^5.0.1" + proxy-from-env: "npm:^2.1.0" + checksum: 10c0/976088acf532286356f4c2e65df1ef47ba7da3936d83e928d0d64357bbf5370456abd3eb5826c15df322335fcb1fe200d4a84b4fcf20ffccdd63b40d80fa495c + languageName: node + linkType: hard + +"backo2@npm:^1.0.2": + version: 1.0.2 + resolution: "backo2@npm:1.0.2" + checksum: 10c0/a9e825a6a38a6d1c4a94476eabc13d6127dfaafb0967baf104affbb67806ae26abbb58dab8d572d2cd21ef06634ff57c3ad48dff14b904e18de1474cc2f22bf3 + languageName: node + linkType: hard + +"call-bind-apply-helpers@npm:^1.0.1, call-bind-apply-helpers@npm:^1.0.2": + version: 1.0.2 + resolution: "call-bind-apply-helpers@npm:1.0.2" + dependencies: + es-errors: "npm:^1.3.0" + function-bind: "npm:^1.1.2" + checksum: 10c0/47bd9901d57b857590431243fea704ff18078b16890a6b3e021e12d279bbf211d039155e27d7566b374d49ee1f8189344bac9833dec7a20cdec370506361c938 + languageName: node + linkType: hard + +"chai@npm:^6.2.2": + version: 6.2.2 + resolution: "chai@npm:6.2.2" + checksum: 10c0/e6c69e5f0c11dffe6ea13d0290936ebb68fcc1ad688b8e952e131df6a6d5797d5e860bc55cef1aca2e950c3e1f96daf79e9d5a70fb7dbaab4e46355e2635ed53 + languageName: node + linkType: hard + +"chalk@npm:^5.3.0, chalk@npm:^5.6.0": + version: 5.6.2 + resolution: "chalk@npm:5.6.2" + checksum: 10c0/99a4b0f0e7991796b1e7e3f52dceb9137cae2a9dfc8fc0784a550dc4c558e15ab32ed70b14b21b52beb2679b4892b41a0aa44249bcb996f01e125d58477c6976 + languageName: node + linkType: hard + +"chardet@npm:^2.1.1": + version: 2.2.0 + resolution: "chardet@npm:2.2.0" + checksum: 10c0/8d43a1dd3ce535aa070d04139fae62f879b4388394eb1d857364ce416675a1f0f63974fba8208e9cd11b49e1a6da3e65ea6393d0fc654a8c4217c0d74c16b1b4 + languageName: node + linkType: hard + +"chokidar@npm:^4.0.0": + version: 4.0.3 + resolution: "chokidar@npm:4.0.3" + dependencies: + readdirp: "npm:^4.0.1" + checksum: 10c0/a58b9df05bb452f7d105d9e7229ac82fa873741c0c40ddcc7bb82f8a909fbe3f7814c9ebe9bc9a2bef9b737c0ec6e2d699d179048ef06ad3ec46315df0ebe6ad + languageName: node + linkType: hard + +"chownr@npm:^3.0.0": + version: 3.0.0 + resolution: "chownr@npm:3.0.0" + checksum: 10c0/43925b87700f7e3893296c8e9c56cc58f926411cce3a6e5898136daaf08f08b9a8eb76d37d3267e707d0dcc17aed2e2ebdf5848c0c3ce95cf910a919935c1b10 + languageName: node + linkType: hard + +"cli-boxes@npm:^3.0.0": + version: 3.0.0 + resolution: "cli-boxes@npm:3.0.0" + checksum: 10c0/4db3e8fbfaf1aac4fb3a6cbe5a2d3fa048bee741a45371b906439b9ffc821c6e626b0f108bdcd3ddf126a4a319409aedcf39a0730573ff050fdd7b6731e99fb9 + languageName: node + linkType: hard + +"cli-cursor@npm:^4.0.0": + version: 4.0.0 + resolution: "cli-cursor@npm:4.0.0" + dependencies: + restore-cursor: "npm:^4.0.0" + checksum: 10c0/e776e8c3c6727300d0539b0d25160b2bb56aed1a63942753ba1826b012f337a6f4b7ace3548402e4f2f13b5e16bfd751be672c44b203205e7eca8be94afec42c + languageName: node + linkType: hard + +"cli-truncate@npm:^5.1.1": + version: 5.2.0 + resolution: "cli-truncate@npm:5.2.0" + dependencies: + slice-ansi: "npm:^8.0.0" + string-width: "npm:^8.2.0" + checksum: 10c0/0d4ec94702ca85b64522ac93633837fb5ea7db17b79b1322a60f6045e6ae2b8cd7bd4c1d19ac7d1f9e10e3bbda1112e172e439b68c02b785ee00da8d6a5c5471 + languageName: node + linkType: hard + +"cli-width@npm:^4.1.0": + version: 4.1.0 + resolution: "cli-width@npm:4.1.0" + checksum: 10c0/1fbd56413578f6117abcaf858903ba1f4ad78370a4032f916745fa2c7e390183a9d9029cf837df320b0fdce8137668e522f60a30a5f3d6529ff3872d265a955f + languageName: node + linkType: hard + +"clsx@npm:^2.1.1": + version: 2.1.1 + resolution: "clsx@npm:2.1.1" + checksum: 10c0/c4c8eb865f8c82baab07e71bfa8897c73454881c4f99d6bc81585aecd7c441746c1399d08363dc096c550cceaf97bd4ce1e8854e1771e9998d9f94c4fe075839 + languageName: node + linkType: hard + +"code-excerpt@npm:^4.0.0": + version: 4.0.0 + resolution: "code-excerpt@npm:4.0.0" + dependencies: + convert-to-spaces: "npm:^2.0.1" + checksum: 10c0/b6c5a06e039cecd2ab6a0e10ee0831de8362107d1f298ca3558b5f9004cb8e0260b02dd6c07f57b9a0e346c76864d2873311ee1989809fdeb05bd5fbbadde773 + languageName: node + linkType: hard + +"combined-stream@npm:^1.0.8": + version: 1.0.8 + resolution: "combined-stream@npm:1.0.8" + dependencies: + delayed-stream: "npm:~1.0.0" + checksum: 10c0/0dbb829577e1b1e839fa82b40c07ffaf7de8a09b935cadd355a73652ae70a88b4320db322f6634a4ad93424292fa80973ac6480986247f1734a1137debf271d5 + languageName: node + linkType: hard + +"commander@npm:^12.0.0": + version: 12.1.0 + resolution: "commander@npm:12.1.0" + checksum: 10c0/6e1996680c083b3b897bfc1cfe1c58dfbcd9842fd43e1aaf8a795fbc237f65efcc860a3ef457b318e73f29a4f4a28f6403c3d653d021d960e4632dd45bde54a9 + languageName: node + linkType: hard + +"convert-source-map@npm:^2.0.0": + version: 2.0.0 + resolution: "convert-source-map@npm:2.0.0" + checksum: 10c0/8f2f7a27a1a011cc6cc88cc4da2d7d0cfa5ee0369508baae3d98c260bb3ac520691464e5bbe4ae7cdf09860c1d69ecc6f70c63c6e7c7f7e3f18ec08484dc7d9b + languageName: node + linkType: hard + +"convert-to-spaces@npm:^2.0.1": + version: 2.0.1 + resolution: "convert-to-spaces@npm:2.0.1" + checksum: 10c0/d90aa0e3b6a27f9d5265a8d32def3c5c855b3e823a9db1f26d772f8146d6b91020a2fdfd905ce8048a73fad3aaf836fef8188c67602c374405e2ae8396c4ac46 + languageName: node + linkType: hard + +"css-mediaquery@npm:^0.1.2": + version: 0.1.2 + resolution: "css-mediaquery@npm:0.1.2" + checksum: 10c0/b7825a78f52ce8a8198e004fcad0d7be1d3c9a0463ecd05ba31a0f2c94fb81468ad6f4d7bf715a6ca775696e7a17500c2a339b5216a6d0f789cbf78f9454d048 + languageName: node + linkType: hard + +"csstype@npm:^3.2.2": + version: 3.2.3 + resolution: "csstype@npm:3.2.3" + checksum: 10c0/cd29c51e70fa822f1cecd8641a1445bed7063697469d35633b516e60fe8c1bde04b08f6c5b6022136bb669b64c63d4173af54864510fbb4ee23281801841a3ce + languageName: node + linkType: hard + +"debug@npm:4, debug@npm:^4.1.1": + version: 4.4.3 + resolution: "debug@npm:4.4.3" + dependencies: + ms: "npm:^2.1.3" + peerDependenciesMeta: + supports-color: + optional: true + checksum: 10c0/d79136ec6c83ecbefd0f6a5593da6a9c91ec4d7ddc4b54c883d6e71ec9accb5f67a1a5e96d00a328196b5b5c86d365e98d8a3a70856aaf16b4e7b1985e67f5a6 + languageName: node + linkType: hard + +"delayed-stream@npm:~1.0.0": + version: 1.0.0 + resolution: "delayed-stream@npm:1.0.0" + checksum: 10c0/d758899da03392e6712f042bec80aa293bbe9e9ff1b2634baae6a360113e708b91326594c8a486d475c69d6259afb7efacdc3537bfcda1c6c648e390ce601b19 + languageName: node + linkType: hard + +"detect-libc@npm:^2.0.3, detect-libc@npm:^2.1.2": + version: 2.1.2 + resolution: "detect-libc@npm:2.1.2" + checksum: 10c0/acc675c29a5649fa1fb6e255f993b8ee829e510b6b56b0910666949c80c364738833417d0edb5f90e4e46be17228b0f2b66a010513984e18b15deeeac49369c4 + languageName: node + linkType: hard + +"dunder-proto@npm:^1.0.1": + version: 1.0.1 + resolution: "dunder-proto@npm:1.0.1" + dependencies: + call-bind-apply-helpers: "npm:^1.0.1" + es-errors: "npm:^1.3.0" + gopd: "npm:^1.2.0" + checksum: 10c0/199f2a0c1c16593ca0a145dbf76a962f8033ce3129f01284d48c45ed4e14fea9bbacd7b3610b6cdc33486cef20385ac054948fefc6272fcce645c09468f93031 + languageName: node + linkType: hard + +"emoji-regex@npm:^10.3.0": + version: 10.6.0 + resolution: "emoji-regex@npm:10.6.0" + checksum: 10c0/1e4aa097bb007301c3b4b1913879ae27327fdc48e93eeefefe3b87e495eb33c5af155300be951b4349ff6ac084f4403dc9eff970acba7c1c572d89396a9a32d7 + languageName: node + linkType: hard + +"env-paths@npm:^2.2.0": + version: 2.2.1 + resolution: "env-paths@npm:2.2.1" + checksum: 10c0/285325677bf00e30845e330eec32894f5105529db97496ee3f598478e50f008c5352a41a30e5e72ec9de8a542b5a570b85699cd63bd2bc646dbcb9f311d83bc4 + languageName: node + linkType: hard + +"environment@npm:^1.0.0": + version: 1.1.0 + resolution: "environment@npm:1.1.0" + checksum: 10c0/fb26434b0b581ab397039e51ff3c92b34924a98b2039dcb47e41b7bca577b9dbf134a8eadb364415c74464b682e2d3afe1a4c0eb9873dc44ea814c5d3103331d + languageName: node + linkType: hard + +"es-define-property@npm:^1.0.1": + version: 1.0.1 + resolution: "es-define-property@npm:1.0.1" + checksum: 10c0/3f54eb49c16c18707949ff25a1456728c883e81259f045003499efba399c08bad00deebf65cccde8c0e07908c1a225c9d472b7107e558f2a48e28d530e34527c + languageName: node + linkType: hard + +"es-errors@npm:^1.3.0": + version: 1.3.0 + resolution: "es-errors@npm:1.3.0" + checksum: 10c0/0a61325670072f98d8ae3b914edab3559b6caa980f08054a3b872052640d91da01d38df55df797fcc916389d77fc92b8d5906cf028f4db46d7e3003abecbca85 + languageName: node + linkType: hard + +"es-module-lexer@npm:^2.0.0": + version: 2.3.2 + resolution: "es-module-lexer@npm:2.3.2" + checksum: 10c0/5e7389424c43478439f12f9a6aca1750f6f99afa384fc3de329f4a45f152ab156671055008adaafc74960877abf8cc338aeebf6bed8c21297b146fd6eb7a22f8 + languageName: node + linkType: hard + +"es-object-atoms@npm:^1.0.0, es-object-atoms@npm:^1.1.1": + version: 1.1.2 + resolution: "es-object-atoms@npm:1.1.2" + dependencies: + es-errors: "npm:^1.3.0" + checksum: 10c0/1772861f094f739d6f41b579cfb9a18579daffeb434552a370a5fbef50a32d22227e27b63fdbb757b7ddd429d1b42fe52ccae7966d9302a2ec221b6f1b41bbc4 + languageName: node + linkType: hard + +"es-set-tostringtag@npm:^2.1.0": + version: 2.1.0 + resolution: "es-set-tostringtag@npm:2.1.0" + dependencies: + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.6" + has-tostringtag: "npm:^1.0.2" + hasown: "npm:^2.0.2" + checksum: 10c0/ef2ca9ce49afe3931cb32e35da4dcb6d86ab02592cfc2ce3e49ced199d9d0bb5085fc7e73e06312213765f5efa47cc1df553a6a5154584b21448e9fb8355b1af + languageName: node + linkType: hard + +"es-toolkit@npm:^1.39.10": + version: 1.51.0 + resolution: "es-toolkit@npm:1.51.0" + dependenciesMeta: + "@trivago/prettier-plugin-sort-imports@4.3.0": + unplugged: true + prettier-plugin-sort-re-exports@0.0.1: + unplugged: true + vitepress-plugin-sandpack@1.1.4: + unplugged: true + checksum: 10c0/ad519228a317a27b11144b582c1168c552143dd58ce4e62be50c185de48c69dea996f6ad6ebc518129ebd58c34da150c1a2134d4a3d7cdc75120c626e57ce7c4 + languageName: node + linkType: hard + +"esbuild@npm:^0.28.1": + version: 0.28.2 + resolution: "esbuild@npm:0.28.2" + dependencies: + "@esbuild/aix-ppc64": "npm:0.28.2" + "@esbuild/android-arm": "npm:0.28.2" + "@esbuild/android-arm64": "npm:0.28.2" + "@esbuild/android-x64": "npm:0.28.2" + "@esbuild/darwin-arm64": "npm:0.28.2" + "@esbuild/darwin-x64": "npm:0.28.2" + "@esbuild/freebsd-arm64": "npm:0.28.2" + "@esbuild/freebsd-x64": "npm:0.28.2" + "@esbuild/linux-arm": "npm:0.28.2" + "@esbuild/linux-arm64": "npm:0.28.2" + "@esbuild/linux-ia32": "npm:0.28.2" + "@esbuild/linux-loong64": "npm:0.28.2" + "@esbuild/linux-mips64el": "npm:0.28.2" + "@esbuild/linux-ppc64": "npm:0.28.2" + "@esbuild/linux-riscv64": "npm:0.28.2" + "@esbuild/linux-s390x": "npm:0.28.2" + "@esbuild/linux-x64": "npm:0.28.2" + "@esbuild/netbsd-arm64": "npm:0.28.2" + "@esbuild/netbsd-x64": "npm:0.28.2" + "@esbuild/openbsd-arm64": "npm:0.28.2" + "@esbuild/openbsd-x64": "npm:0.28.2" + "@esbuild/openharmony-arm64": "npm:0.28.2" + "@esbuild/sunos-x64": "npm:0.28.2" + "@esbuild/win32-arm64": "npm:0.28.2" + "@esbuild/win32-ia32": "npm:0.28.2" + "@esbuild/win32-x64": "npm:0.28.2" + dependenciesMeta: + "@esbuild/aix-ppc64": + optional: true + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-arm64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-arm64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/openharmony-arm64": + optional: true + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: 10c0/9b19edb63bd7780fd2e8e65a1394a0e8a05a17d697f73f0647ffe3c134709d8dbe744ab3fd57b35c9470db268cae7678fafd3dcd3ce1f34fc590568c2433f5d2 + languageName: node + linkType: hard + +"escape-string-regexp@npm:^2.0.0": + version: 2.0.0 + resolution: "escape-string-regexp@npm:2.0.0" + checksum: 10c0/2530479fe8db57eace5e8646c9c2a9c80fa279614986d16dcc6bcaceb63ae77f05a851ba6c43756d816c61d7f4534baf56e3c705e3e0d884818a46808811c507 + languageName: node + linkType: hard + +"estree-walker@npm:^3.0.3": + version: 3.0.3 + resolution: "estree-walker@npm:3.0.3" + dependencies: + "@types/estree": "npm:^1.0.0" + checksum: 10c0/c12e3c2b2642d2bcae7d5aa495c60fa2f299160946535763969a1c83fc74518ffa9c2cd3a8b69ac56aea547df6a8aac25f729a342992ef0bbac5f1c73e78995d + languageName: node + linkType: hard + +"eventemitter3@npm:^3.1.0": + version: 3.1.2 + resolution: "eventemitter3@npm:3.1.2" + checksum: 10c0/c67262eccbf85848b7cc6d4abb6c6e34155e15686db2a01c57669fd0d44441a574a19d44d25948b442929e065774cbe5003d8e77eed47674fbf876ac77887793 + languageName: node + linkType: hard + +"expect-type@npm:^1.3.0": + version: 1.4.0 + resolution: "expect-type@npm:1.4.0" + checksum: 10c0/d40d76b8570695d36587beb3cc28494da2ca3ec8f04e67f5622ed2d372d850e401a9adef19c6835e1a8173903f157c79540b34c7b3fbd7cd8ce726cc903c57b7 + languageName: node + linkType: hard + +"exponential-backoff@npm:^3.1.1": + version: 3.1.3 + resolution: "exponential-backoff@npm:3.1.3" + checksum: 10c0/77e3ae682b7b1f4972f563c6dbcd2b0d54ac679e62d5d32f3e5085feba20483cf28bd505543f520e287a56d4d55a28d7874299941faf637e779a1aa5994d1267 + languageName: node + linkType: hard + +"fast-string-truncated-width@npm:^3.0.2": + version: 3.0.3 + resolution: "fast-string-truncated-width@npm:3.0.3" + checksum: 10c0/043b8663397d14a3880ce4f3407bcda60b40db9bbeafe62863a35d1f9c69ea17c8da3fcd72de235553e6c9cd053128cde9e24ca0d4a7463208f48db3cd23d981 + languageName: node + linkType: hard + +"fast-string-width@npm:^3.0.2": + version: 3.0.2 + resolution: "fast-string-width@npm:3.0.2" + dependencies: + fast-string-truncated-width: "npm:^3.0.2" + checksum: 10c0/c8822d175315bb353ebe782b65214ac53b13e3bf704e03b132ea7bdfa8de6a636375b3ab7a4097545393d109381c37c4f387c72a462c90b61412dbc4632f39a7 + languageName: node + linkType: hard + +"fast-wrap-ansi@npm:^0.2.0": + version: 0.2.2 + resolution: "fast-wrap-ansi@npm:0.2.2" + dependencies: + fast-string-width: "npm:^3.0.2" + checksum: 10c0/1aa7be4f7cb86f4bdb14691cb6bcc0b8df8b3b89df142ade3ae1602332dcf6f990cd750a923cd581ca0847808cb4ec1aa5afaafa7a72f849e87a2a62c98fa370 + languageName: node + linkType: hard + +"fdir@npm:^6.5.0": + version: 6.5.0 + resolution: "fdir@npm:6.5.0" + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + checksum: 10c0/e345083c4306b3aed6cb8ec551e26c36bab5c511e99ea4576a16750ddc8d3240e63826cc624f5ae17ad4dc82e68a253213b60d556c11bfad064b7607847ed07f + languageName: node + linkType: hard + +"follow-redirects@npm:^1.16.0": + version: 1.16.0 + resolution: "follow-redirects@npm:1.16.0" + peerDependenciesMeta: + debug: + optional: true + checksum: 10c0/a1e2900163e6f1b4d1ed5c221b607f41decbab65534c63fe7e287e40a5d552a6496e7d9d7d976fa4ba77b4c51c11e5e9f683f10b43011ea11e442ff128d0e181 + languageName: node + linkType: hard + +"form-data@npm:^4.0.6": + version: 4.0.6 + resolution: "form-data@npm:4.0.6" + dependencies: + asynckit: "npm:^0.4.0" + combined-stream: "npm:^1.0.8" + es-set-tostringtag: "npm:^2.1.0" + hasown: "npm:^2.0.4" + mime-types: "npm:^2.1.35" + checksum: 10c0/43947a77bf0ff45c6ceed789778982d47a3f3e720a74b71721174ebf3310a5f1a8be1d6b38a3ee3688e8a18a2c4273073ec0844cd37efda3eaf46d41c9c318ff + languageName: node + linkType: hard + +"fsevents@npm:~2.3.3": + version: 2.3.3 + resolution: "fsevents@npm:2.3.3" + dependencies: + node-gyp: "npm:latest" + checksum: 10c0/a1f0c44595123ed717febbc478aa952e47adfc28e2092be66b8ab1635147254ca6cfe1df792a8997f22716d4cbafc73309899ff7bfac2ac3ad8cf2e4ecc3ec60 + conditions: os=darwin + languageName: node + linkType: hard + +"fsevents@patch:fsevents@npm%3A~2.3.3#optional!builtin": + version: 2.3.3 + resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin::version=2.3.3&hash=df0bf1" + dependencies: + node-gyp: "npm:latest" + conditions: os=darwin + languageName: node + linkType: hard + +"function-bind@npm:^1.1.2": + version: 1.1.2 + resolution: "function-bind@npm:1.1.2" + checksum: 10c0/d8680ee1e5fcd4c197e4ac33b2b4dce03c71f4d91717292785703db200f5c21f977c568d28061226f9b5900cbcd2c84463646134fd5337e7925e0942bc3f46d5 + languageName: node + linkType: hard + +"generator-function@npm:^2.0.0": + version: 2.0.1 + resolution: "generator-function@npm:2.0.1" + checksum: 10c0/8a9f59df0f01cfefafdb3b451b80555e5cf6d76487095db91ac461a0e682e4ff7a9dbce15f4ecec191e53586d59eece01949e05a4b4492879600bbbe8e28d6b8 + languageName: node + linkType: hard + +"get-east-asian-width@npm:^1.0.0, get-east-asian-width@npm:^1.3.1, get-east-asian-width@npm:^1.5.0": + version: 1.6.0 + resolution: "get-east-asian-width@npm:1.6.0" + checksum: 10c0/7e72e9550fd49ca5b246f9af6bb2afc129c96412845ff6556b3274fd44817a381702ca17028efe9866b261a3d44254cbf21e6c90cf05b4b61675630af776d431 + languageName: node + linkType: hard + +"get-intrinsic@npm:^1.2.6": + version: 1.3.1 + resolution: "get-intrinsic@npm:1.3.1" + dependencies: + async-function: "npm:^1.0.0" + async-generator-function: "npm:^1.0.0" + call-bind-apply-helpers: "npm:^1.0.2" + es-define-property: "npm:^1.0.1" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.1.1" + function-bind: "npm:^1.1.2" + generator-function: "npm:^2.0.0" + get-proto: "npm:^1.0.1" + gopd: "npm:^1.2.0" + has-symbols: "npm:^1.1.0" + hasown: "npm:^2.0.2" + math-intrinsics: "npm:^1.1.0" + checksum: 10c0/9f4ab0cf7efe0fd2c8185f52e6f637e708f3a112610c88869f8f041bb9ecc2ce44bf285dfdbdc6f4f7c277a5b88d8e94a432374d97cca22f3de7fc63795deb5d + languageName: node + linkType: hard + +"get-proto@npm:^1.0.1": + version: 1.0.1 + resolution: "get-proto@npm:1.0.1" + dependencies: + dunder-proto: "npm:^1.0.1" + es-object-atoms: "npm:^1.0.0" + checksum: 10c0/9224acb44603c5526955e83510b9da41baf6ae73f7398875fba50edc5e944223a89c4a72b070fcd78beb5f7bdda58ecb6294adc28f7acfc0da05f76a2399643c + languageName: node + linkType: hard + +"globrex@npm:^0.1.2": + version: 0.1.2 + resolution: "globrex@npm:0.1.2" + checksum: 10c0/a54c029520cf58bda1d8884f72bd49b4cd74e977883268d931fd83bcbd1a9eb96d57c7dbd4ad80148fb9247467ebfb9b215630b2ed7563b2a8de02e1ff7f89d1 + languageName: node + linkType: hard + +"gopd@npm:^1.2.0": + version: 1.2.0 + resolution: "gopd@npm:1.2.0" + checksum: 10c0/50fff1e04ba2b7737c097358534eacadad1e68d24cccee3272e04e007bed008e68d2614f3987788428fd192a5ae3889d08fb2331417e4fc4a9ab366b2043cead + languageName: node + linkType: hard + +"graceful-fs@npm:^4.2.6": + version: 4.2.11 + resolution: "graceful-fs@npm:4.2.11" + checksum: 10c0/386d011a553e02bc594ac2ca0bd6d9e4c22d7fa8cfbfc448a6d148c59ea881b092db9dbe3547ae4b88e55f1b01f7c4a2ecc53b310c042793e63aa44cf6c257f2 + languageName: node + linkType: hard + +"graphql-query-batcher@npm:^1.0.1": + version: 1.0.1 + resolution: "graphql-query-batcher@npm:1.0.1" + checksum: 10c0/804d0f4064721a2116a16b9eac422e9233e85f4ab5b250cb8f83662725658ffde35779a8ae8211037f3dd2f9717de8cb63b394ad8057ce22171a83db2471196a + languageName: node + linkType: hard + +"graphql-sse@npm:^2.5.4": + version: 2.6.1 + resolution: "graphql-sse@npm:2.6.1" + peerDependencies: + graphql: ">=0.11 <=17" + checksum: 10c0/8a767b83c970dea75db634b27cd28d5040d6e7b07234c38919bc88d15eacf978171e380ba8ab3fa21a1779421e24b8e19dc51a798a90c5766382c406b3d6c221 + languageName: node + linkType: hard + +"graphql@npm:^16.8.1": + version: 16.14.2 + resolution: "graphql@npm:16.14.2" + checksum: 10c0/a95a96961eaff55cc9fe9d31fae6f33499ac988b972d07ea5085024cb1333f515b902f376e7393a5489aa82200a8aff3eb96580e4d1b69d702ed19b6eb1ce97a + languageName: node + linkType: hard + +"has-symbols@npm:^1.0.3, has-symbols@npm:^1.1.0": + version: 1.1.0 + resolution: "has-symbols@npm:1.1.0" + checksum: 10c0/dde0a734b17ae51e84b10986e651c664379018d10b91b6b0e9b293eddb32f0f069688c841fb40f19e9611546130153e0a2a48fd7f512891fb000ddfa36f5a20e + languageName: node + linkType: hard + +"has-tostringtag@npm:^1.0.2": + version: 1.0.2 + resolution: "has-tostringtag@npm:1.0.2" + dependencies: + has-symbols: "npm:^1.0.3" + checksum: 10c0/a8b166462192bafe3d9b6e420a1d581d93dd867adb61be223a17a8d6dad147aa77a8be32c961bb2f27b3ef893cae8d36f564ab651f5e9b7938ae86f74027c48c + languageName: node + linkType: hard + +"hasown@npm:^2.0.2, hasown@npm:^2.0.4": + version: 2.0.4 + resolution: "hasown@npm:2.0.4" + dependencies: + function-bind: "npm:^1.1.2" + checksum: 10c0/2d8de939e270b70618f8cebb69746620db10617dbb495bc66ddad326955ea24d3ca4af133aff3eb7c1853e0218f867bc2b050ec26fe02e3aea58f880ffc5e506 + languageName: node + linkType: hard + +"https-proxy-agent@npm:^5.0.1": + version: 5.0.1 + resolution: "https-proxy-agent@npm:5.0.1" + dependencies: + agent-base: "npm:6" + debug: "npm:4" + checksum: 10c0/6dd639f03434003577c62b27cafdb864784ef19b2de430d8ae2a1d45e31c4fd60719e5637b44db1a88a046934307da7089e03d6089ec3ddacc1189d8de8897d1 + languageName: node + linkType: hard + +"hyphenate-style-name@npm:^1.0.0": + version: 1.1.0 + resolution: "hyphenate-style-name@npm:1.1.0" + checksum: 10c0/bfe88deac2414a41a0d08811e277c8c098f23993d6a1eb17f14a0f11b54c4d42865a63d3cfe1914668eefb9a188e2de58f38b55a179a238fd1fef606893e194f + languageName: node + linkType: hard + +"iconv-lite@npm:^0.7.2": + version: 0.7.3 + resolution: "iconv-lite@npm:0.7.3" + dependencies: + safer-buffer: "npm:>= 2.1.2 < 3.0.0" + checksum: 10c0/be2fd2414f7e94be3a63063fa0ad5919c16bc7b6e00c77eea0765ced6db405739f38dd51016583058fba25cc1d0106ba30b83fbb7a7e32f824d4db76f23d8d33 + languageName: node + linkType: hard + +"indent-string@npm:^5.0.0": + version: 5.0.0 + resolution: "indent-string@npm:5.0.0" + checksum: 10c0/8ee77b57d92e71745e133f6f444d6fa3ed503ad0e1bcd7e80c8da08b42375c07117128d670589725ed07b1978065803fa86318c309ba45415b7fe13e7f170220 + languageName: node + linkType: hard + +"ink@npm:^6.8.0": + version: 6.8.0 + resolution: "ink@npm:6.8.0" + dependencies: + "@alcalzone/ansi-tokenize": "npm:^0.2.4" + ansi-escapes: "npm:^7.3.0" + ansi-styles: "npm:^6.2.1" + auto-bind: "npm:^5.0.1" + chalk: "npm:^5.6.0" + cli-boxes: "npm:^3.0.0" + cli-cursor: "npm:^4.0.0" + cli-truncate: "npm:^5.1.1" + code-excerpt: "npm:^4.0.0" + es-toolkit: "npm:^1.39.10" + indent-string: "npm:^5.0.0" + is-in-ci: "npm:^2.0.0" + patch-console: "npm:^2.0.0" + react-reconciler: "npm:^0.33.0" + scheduler: "npm:^0.27.0" + signal-exit: "npm:^3.0.7" + slice-ansi: "npm:^8.0.0" + stack-utils: "npm:^2.0.6" + string-width: "npm:^8.1.1" + terminal-size: "npm:^4.0.1" + type-fest: "npm:^5.4.1" + widest-line: "npm:^6.0.0" + wrap-ansi: "npm:^9.0.0" + ws: "npm:^8.18.0" + yoga-layout: "npm:~3.2.1" + peerDependencies: + "@types/react": ">=19.0.0" + react: ">=19.0.0" + react-devtools-core: ">=6.1.2" + peerDependenciesMeta: + "@types/react": + optional: true + react-devtools-core: + optional: true + checksum: 10c0/50500e547fdf6a1f1d836d6befbd4770e3ab649ef0be1884500a6da411fb68a90e22dd7dcc9c404911d30e9f87506b3b9d8e997c6c6ceac85ee054b4dadefaff + languageName: node + linkType: hard + +"inquirer@npm:^14.0.0": + version: 14.1.0 + resolution: "inquirer@npm:14.1.0" + dependencies: + "@inquirer/ansi": "npm:^2.0.7" + "@inquirer/core": "npm:^12.0.0" + "@inquirer/prompts": "npm:^8.6.0" + "@inquirer/type": "npm:^4.0.7" + mute-stream: "npm:^3.0.0" + run-async: "npm:^4.0.6" + peerDependencies: + "@types/node": ">=18" + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10c0/171d1167327d96689a9284f86a755bef666921797a2427ad1ec46169c99cfb94616c135a18bd067645eed9f0eef2846c2c0ff6d324d29ee24fcfe56e147f06c3 + languageName: node + linkType: hard + +"is-fullwidth-code-point@npm:^5.0.0, is-fullwidth-code-point@npm:^5.1.0": + version: 5.1.0 + resolution: "is-fullwidth-code-point@npm:5.1.0" + dependencies: + get-east-asian-width: "npm:^1.3.1" + checksum: 10c0/c1172c2e417fb73470c56c431851681591f6a17233603a9e6f94b7ba870b2e8a5266506490573b607fb1081318589372034aa436aec07b465c2029c0bc7f07a4 + languageName: node + linkType: hard + +"is-in-ci@npm:^2.0.0": + version: 2.0.0 + resolution: "is-in-ci@npm:2.0.0" + bin: + is-in-ci: cli.js + checksum: 10c0/1e1d1056939a681e8206035de5ad84e0404556eaa7622bb55f0f1868b9788bff3df427bc0b1ed5a172623154a90fcb1e759a230817cd73d09435543ae3c71feb + languageName: node + linkType: hard + +"isexe@npm:^4.0.0": + version: 4.0.0 + resolution: "isexe@npm:4.0.0" + checksum: 10c0/5884815115bceac452877659a9c7726382531592f43dc29e5d48b7c4100661aed54018cb90bd36cb2eaeba521092570769167acbb95c18d39afdccbcca06c5ce + languageName: node + linkType: hard + +"isomorphic-unfetch@npm:^3.0.0": + version: 3.1.0 + resolution: "isomorphic-unfetch@npm:3.1.0" + dependencies: + node-fetch: "npm:^2.6.1" + unfetch: "npm:^4.2.0" + checksum: 10c0/d3b61fca06304db692b7f76bdfd3a00f410e42cfa7403c3b250546bf71589d18cf2f355922f57198e4cc4a9872d3647b20397a5c3edf1a347c90d57c83cf2a89 + languageName: node + linkType: hard + +"iterall@npm:^1.2.1": + version: 1.3.0 + resolution: "iterall@npm:1.3.0" + checksum: 10c0/40de624e5fe937c4c0e511981b91caea9ff2142bfc0316cccc8506eaa03aa253820cc17c5bc5f0a98706c7268a373e5ebee9af9a0c8a359730cf7c05938b57b5 + languageName: node + linkType: hard + +"js-tokens@npm:^3.0.0 || ^4.0.0": + version: 4.0.0 + resolution: "js-tokens@npm:4.0.0" + checksum: 10c0/e248708d377aa058eacf2037b07ded847790e6de892bbad3dac0abba2e759cb9f121b00099a65195616badcb6eca8d14d975cb3e89eb1cfda644756402c8aeed + languageName: node + linkType: hard + +"jsonc-parser@npm:^3.2.0": + version: 3.3.1 + resolution: "jsonc-parser@npm:3.3.1" + checksum: 10c0/269c3ae0a0e4f907a914bf334306c384aabb9929bd8c99f909275ebd5c2d3bc70b9bcd119ad794f339dec9f24b6a4ee9cd5a8ab2e6435e730ad4075388fc2ab6 + languageName: node + linkType: hard + +"lightningcss-android-arm64@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-android-arm64@npm:1.33.0" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"lightningcss-darwin-arm64@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-darwin-arm64@npm:1.33.0" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"lightningcss-darwin-x64@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-darwin-x64@npm:1.33.0" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"lightningcss-freebsd-x64@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-freebsd-x64@npm:1.33.0" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"lightningcss-linux-arm-gnueabihf@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-linux-arm-gnueabihf@npm:1.33.0" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"lightningcss-linux-arm64-gnu@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-linux-arm64-gnu@npm:1.33.0" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"lightningcss-linux-arm64-musl@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-linux-arm64-musl@npm:1.33.0" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"lightningcss-linux-x64-gnu@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-linux-x64-gnu@npm:1.33.0" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"lightningcss-linux-x64-musl@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-linux-x64-musl@npm:1.33.0" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"lightningcss-win32-arm64-msvc@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-win32-arm64-msvc@npm:1.33.0" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"lightningcss-win32-x64-msvc@npm:1.33.0": + version: 1.33.0 + resolution: "lightningcss-win32-x64-msvc@npm:1.33.0" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"lightningcss@npm:^1.33.0": + version: 1.33.0 + resolution: "lightningcss@npm:1.33.0" + dependencies: + detect-libc: "npm:^2.0.3" + lightningcss-android-arm64: "npm:1.33.0" + lightningcss-darwin-arm64: "npm:1.33.0" + lightningcss-darwin-x64: "npm:1.33.0" + lightningcss-freebsd-x64: "npm:1.33.0" + lightningcss-linux-arm-gnueabihf: "npm:1.33.0" + lightningcss-linux-arm64-gnu: "npm:1.33.0" + lightningcss-linux-arm64-musl: "npm:1.33.0" + lightningcss-linux-x64-gnu: "npm:1.33.0" + lightningcss-linux-x64-musl: "npm:1.33.0" + lightningcss-win32-arm64-msvc: "npm:1.33.0" + lightningcss-win32-x64-msvc: "npm:1.33.0" + dependenciesMeta: + lightningcss-android-arm64: + optional: true + lightningcss-darwin-arm64: + optional: true + lightningcss-darwin-x64: + optional: true + lightningcss-freebsd-x64: + optional: true + lightningcss-linux-arm-gnueabihf: + optional: true + lightningcss-linux-arm64-gnu: + optional: true + lightningcss-linux-arm64-musl: + optional: true + lightningcss-linux-x64-gnu: + optional: true + lightningcss-linux-x64-musl: + optional: true + lightningcss-win32-arm64-msvc: + optional: true + lightningcss-win32-x64-msvc: + optional: true + checksum: 10c0/ce1f8279fbae636dbf37fa6e7385d5f98ed881d72af3362f24afbd4685e19c1fcdfecf17e5dd77f2ebee3d0c23ade276230d85842d07292229a2cffba8ff20a3 + languageName: node + linkType: hard + +"lodash@npm:^4.17.20, lodash@npm:^4.17.21": + version: 4.18.1 + resolution: "lodash@npm:4.18.1" + checksum: 10c0/757228fc68805c59789e82185135cf85f05d0b2d3d54631d680ca79ec21944ec8314d4533639a14b8bcfbd97a517e78960933041a5af17ecb693ec6eecb99a27 + languageName: node + linkType: hard + +"loose-envify@npm:^1.4.0": + version: 1.4.0 + resolution: "loose-envify@npm:1.4.0" + dependencies: + js-tokens: "npm:^3.0.0 || ^4.0.0" + bin: + loose-envify: cli.js + checksum: 10c0/655d110220983c1a4b9c0c679a2e8016d4b67f6e9c7b5435ff5979ecdb20d0813f4dec0a08674fcbdd4846a3f07edbb50a36811fd37930b94aaa0d9daceb017e + languageName: node + linkType: hard + +"magic-string@npm:^0.30.21": + version: 0.30.21 + resolution: "magic-string@npm:0.30.21" + dependencies: + "@jridgewell/sourcemap-codec": "npm:^1.5.5" + checksum: 10c0/299378e38f9a270069fc62358522ddfb44e94244baa0d6a8980ab2a9b2490a1d03b236b447eee309e17eb3bddfa482c61259d47960eb018a904f0ded52780c4a + languageName: node + linkType: hard + +"matchmediaquery@npm:^0.3.0": + version: 0.3.1 + resolution: "matchmediaquery@npm:0.3.1" + dependencies: + css-mediaquery: "npm:^0.1.2" + checksum: 10c0/19a254e0a1eb639fcc6aa7428a6e2d36cb5646a165579897e771580653e470b43d7f4a224c522700dbf1d8f19fb7c63a9719e0a6017727ade967155a2ae89fea + languageName: node + linkType: hard + +"math-intrinsics@npm:^1.1.0": + version: 1.1.0 + resolution: "math-intrinsics@npm:1.1.0" + checksum: 10c0/7579ff94e899e2f76ab64491d76cf606274c874d8f2af4a442c016bd85688927fcfca157ba6bf74b08e9439dc010b248ce05b96cc7c126a354c3bae7fcb48b7f + languageName: node + linkType: hard + +"mime-db@npm:1.52.0": + version: 1.52.0 + resolution: "mime-db@npm:1.52.0" + checksum: 10c0/0557a01deebf45ac5f5777fe7740b2a5c309c6d62d40ceab4e23da9f821899ce7a900b7ac8157d4548ddbb7beffe9abc621250e6d182b0397ec7f10c7b91a5aa + languageName: node + linkType: hard + +"mime-types@npm:^2.1.35": + version: 2.1.35 + resolution: "mime-types@npm:2.1.35" + dependencies: + mime-db: "npm:1.52.0" + checksum: 10c0/82fb07ec56d8ff1fc999a84f2f217aa46cb6ed1033fefaabd5785b9a974ed225c90dc72fff460259e66b95b73648596dbcc50d51ed69cdf464af2d237d3149b2 + languageName: node + linkType: hard + +"mimic-fn@npm:^2.1.0": + version: 2.1.0 + resolution: "mimic-fn@npm:2.1.0" + checksum: 10c0/b26f5479d7ec6cc2bce275a08f146cf78f5e7b661b18114e2506dd91ec7ec47e7a25bf4360e5438094db0560bcc868079fb3b1fb3892b833c1ecbf63f80c95a4 + languageName: node + linkType: hard + +"minipass@npm:^7.0.4, minipass@npm:^7.1.2": + version: 7.1.3 + resolution: "minipass@npm:7.1.3" + checksum: 10c0/539da88daca16533211ea5a9ee98dc62ff5742f531f54640dd34429e621955e91cc280a91a776026264b7f9f6735947629f920944e9c1558369e8bf22eb33fbb + languageName: node + linkType: hard + +"minizlib@npm:^3.1.0": + version: 3.1.0 + resolution: "minizlib@npm:3.1.0" + dependencies: + minipass: "npm:^7.1.2" + checksum: 10c0/5aad75ab0090b8266069c9aabe582c021ae53eb33c6c691054a13a45db3b4f91a7fb1bd79151e6b4e9e9a86727b522527c0a06ec7d45206b745d54cd3097bcec + languageName: node + linkType: hard + +"ms@npm:^2.1.3": + version: 2.1.3 + resolution: "ms@npm:2.1.3" + checksum: 10c0/d924b57e7312b3b63ad21fc5b3dc0af5e78d61a1fc7cfb5457edaf26326bf62be5307cc87ffb6862ef1c2b33b0233cdb5d4f01c4c958cc0d660948b65a287a48 + languageName: node + linkType: hard + +"mute-stream@npm:^3.0.0": + version: 3.0.0 + resolution: "mute-stream@npm:3.0.0" + checksum: 10c0/12cdb36a101694c7a6b296632e6d93a30b74401873cf7507c88861441a090c71c77a58f213acadad03bc0c8fa186639dec99d68a14497773a8744320c136e701 + languageName: node + linkType: hard + +"nanoid@npm:^3.3.17": + version: 3.3.18 + resolution: "nanoid@npm:3.3.18" + bin: + nanoid: bin/nanoid.cjs + checksum: 10c0/b994b4e396730f8be2520923284e2040d61eaee55cc6d4935ef6d38d34bafdc46133eda4d3faea5073bda545aa6079d82b886caeac5c731cf9ac18bcc1301425 + languageName: node + linkType: hard + +"node-fetch@npm:^2.6.1": + version: 2.7.0 + resolution: "node-fetch@npm:2.7.0" + dependencies: + whatwg-url: "npm:^5.0.0" + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + checksum: 10c0/b55786b6028208e6fbe594ccccc213cab67a72899c9234eb59dba51062a299ea853210fcf526998eaa2867b0963ad72338824450905679ff0fa304b8c5093ae8 + languageName: node + linkType: hard + +"node-gyp@npm:latest": + version: 13.0.2 + resolution: "node-gyp@npm:13.0.2" + dependencies: + env-paths: "npm:^2.2.0" + exponential-backoff: "npm:^3.1.1" + graceful-fs: "npm:^4.2.6" + nopt: "npm:^10.0.0" + proc-log: "npm:^7.0.0" + semver: "npm:^7.3.5" + tar: "npm:^7.5.4" + tinyglobby: "npm:^0.2.12" + undici: "npm:^8.4.1" + which: "npm:^7.0.0" + bin: + node-gyp: bin/node-gyp.js + checksum: 10c0/29d33ccf47ffeeb5e7af925550b416f698a26a72eb10975c7eb5775c1d0cecacd76d164a4aa45503d3ddcece209db65c712be00423c69381a4cd14e2e6540559 + languageName: node + linkType: hard + +"nopt@npm:^10.0.0": + version: 10.0.1 + resolution: "nopt@npm:10.0.1" + dependencies: + abbrev: "npm:^5.0.0" + bin: + nopt: bin/nopt.js + checksum: 10c0/980d89257f9587f3e1f77877ddbf905d6aa3b738ec33e49a4fa1a059a0dd82eb28063982b150654a7ae9de386f2ead60e56172db7d37cf56de545f7392a2a26a + languageName: node + linkType: hard + +"object-assign@npm:^4.1.1": + version: 4.1.1 + resolution: "object-assign@npm:4.1.1" + checksum: 10c0/1f4df9945120325d041ccf7b86f31e8bcc14e73d29171e37a7903050e96b81323784ec59f93f102ec635bcf6fa8034ba3ea0a8c7e69fa202b87ae3b6cec5a414 + languageName: node + linkType: hard + +"obug@npm:^2.1.1": + version: 2.1.4 + resolution: "obug@npm:2.1.4" + checksum: 10c0/34a0ee97cd88573cfd97d384c2a79f07118ae5680d7e45d1de6e99c74eddefe145e8ca27a2db02195a1ee5fded5aa22b924869c842728c201b9f109a27d0ef19 + languageName: node + linkType: hard + +"onetime@npm:^5.1.0": + version: 5.1.2 + resolution: "onetime@npm:5.1.2" + dependencies: + mimic-fn: "npm:^2.1.0" + checksum: 10c0/ffcef6fbb2692c3c40749f31ea2e22677a876daea92959b8a80b521d95cca7a668c884d8b2045d1d8ee7d56796aa405c405462af112a1477594cc63531baeb8f + languageName: node + linkType: hard + +"oxlint@npm:^0.16.0": + version: 0.16.12 + resolution: "oxlint@npm:0.16.12" + dependencies: + "@oxlint/darwin-arm64": "npm:0.16.12" + "@oxlint/darwin-x64": "npm:0.16.12" + "@oxlint/linux-arm64-gnu": "npm:0.16.12" + "@oxlint/linux-arm64-musl": "npm:0.16.12" + "@oxlint/linux-x64-gnu": "npm:0.16.12" + "@oxlint/linux-x64-musl": "npm:0.16.12" + "@oxlint/win32-arm64": "npm:0.16.12" + "@oxlint/win32-x64": "npm:0.16.12" + dependenciesMeta: + "@oxlint/darwin-arm64": + optional: true + "@oxlint/darwin-x64": + optional: true + "@oxlint/linux-arm64-gnu": + optional: true + "@oxlint/linux-arm64-musl": + optional: true + "@oxlint/linux-x64-gnu": + optional: true + "@oxlint/linux-x64-musl": + optional: true + "@oxlint/win32-arm64": + optional: true + "@oxlint/win32-x64": + optional: true + bin: + oxc_language_server: bin/oxc_language_server + oxlint: bin/oxlint + checksum: 10c0/209c3484039c4f1fdd340689e81be93a70fdf74e8c111731e70d81580d223b0029439107c95fa4118cec3c9b03ba6c56624d3dcb8cdc79af222da3561122b3e7 + languageName: node + linkType: hard + +"patch-console@npm:^2.0.0": + version: 2.0.0 + resolution: "patch-console@npm:2.0.0" + checksum: 10c0/486602591a0af7af8d4c76d8eea42cad32b6de7200488819c6383c75e43733ca7bdc80e30f2e68ce05f06a1607cce1683a1706c6672ca27dada1921b366e8f1c + languageName: node + linkType: hard + +"pathe@npm:^2.0.3": + version: 2.0.3 + resolution: "pathe@npm:2.0.3" + checksum: 10c0/c118dc5a8b5c4166011b2b70608762e260085180bb9e33e80a50dcdb1e78c010b1624f4280c492c92b05fc276715a4c357d1f9edc570f8f1b3d90b6839ebaca1 + languageName: node + linkType: hard + +"picocolors@npm:^1.1.1": + version: 1.1.1 + resolution: "picocolors@npm:1.1.1" + checksum: 10c0/e2e3e8170ab9d7c7421969adaa7e1b31434f789afb9b3f115f6b96d91945041ac3ceb02e9ec6fe6510ff036bcc0bf91e69a1772edc0b707e12b19c0f2d6bcf58 + languageName: node + linkType: hard + +"picomatch@npm:^4.0.3, picomatch@npm:^4.0.4, picomatch@npm:^4.0.5": + version: 4.0.7 + resolution: "picomatch@npm:4.0.7" + checksum: 10c0/beb6ae02c43ae44e84883b90830196d9046b1726ead292adcf7f57945e0bb0d992d68563d87e03b484b6f3c9a5c6defda7523477f047d7f0e663f126cc01787f + languageName: node + linkType: hard + +"postcss@npm:^8.5.26": + version: 8.5.26 + resolution: "postcss@npm:8.5.26" + dependencies: + nanoid: "npm:^3.3.17" + picocolors: "npm:^1.1.1" + source-map-js: "npm:^1.2.1" + checksum: 10c0/2bdafc00d96bd57b6649a52e458864a4bf58ee56cfdbe4aea1472b5cccc127e6c1ad653bd0bec50d211e650eb0b9270c80e1e72aff2e2fa40d9e7363234d6e43 + languageName: node + linkType: hard + +"preact@npm:^10.28.3": + version: 10.29.8 + resolution: "preact@npm:10.29.8" + peerDependencies: + preact-render-to-string: ">=5" + peerDependenciesMeta: + preact-render-to-string: + optional: true + checksum: 10c0/505460352139e0ce5b9c971b69d8d9f3b73ea0f316e3ff1c0b3db9a1abe206c9dff6ba385eb796253c38fcc08b5ade70cd8da3d9d6c9e9aec1a2fd59348253ae + languageName: node + linkType: hard + +"prettier@npm:^3.8.3": + version: 3.9.6 + resolution: "prettier@npm:3.9.6" + bin: + prettier: bin/prettier.cjs + checksum: 10c0/9f7ddae234035868f3daab3dc34e6de7e54622185afb017378029f0c09a9c023248ccf6f34def0b17a0538e18c47aa1b3188bab72965d1bf735919baa0747e99 + languageName: node + linkType: hard + +"proc-log@npm:^7.0.0": + version: 7.0.0 + resolution: "proc-log@npm:7.0.0" + checksum: 10c0/b89c2d862604f35fec795477b0c7e376feab3ba0d4f4d291c4e959567442697cf451ac557d0623c1cc38af45a78128b983410f397a10c5d3a67f76c33de4754b + languageName: node + linkType: hard + +"prop-types@npm:^15.6.1": + version: 15.8.1 + resolution: "prop-types@npm:15.8.1" + dependencies: + loose-envify: "npm:^1.4.0" + object-assign: "npm:^4.1.1" + react-is: "npm:^16.13.1" + checksum: 10c0/59ece7ca2fb9838031d73a48d4becb9a7cc1ed10e610517c7d8f19a1e02fa47f7c27d557d8a5702bec3cfeccddc853579832b43f449e54635803f277b1c78077 + languageName: node + linkType: hard + +"proxy-from-env@npm:^2.1.0": + version: 2.1.0 + resolution: "proxy-from-env@npm:2.1.0" + checksum: 10c0/ed01729fd4d094eab619cd7e17ce3698b3413b31eb102c4904f9875e677cd207392795d5b4adee9cec359dfd31c44d5ad7595a3a3ad51c40250e141512281c58 + languageName: node + linkType: hard + +"react-dom@npm:^19.0.0, react-dom@npm:^19.2.0": + version: 19.2.8 + resolution: "react-dom@npm:19.2.8" + dependencies: + scheduler: "npm:^0.27.0" + peerDependencies: + react: ^19.2.8 + checksum: 10c0/41ba2247b76f687fcfe5bbc99f514d6b851d8c8041c2f5ded36ed05bd7fdc5208cacbac9de51e3e6633e77f96f44cec9f0d4a5a55184dba4e00738f224439134 + languageName: node + linkType: hard + +"react-is@npm:^16.13.1": + version: 16.13.1 + resolution: "react-is@npm:16.13.1" + checksum: 10c0/33977da7a5f1a287936a0c85639fec6ca74f4f15ef1e59a6bc20338fc73dc69555381e211f7a3529b8150a1f71e4225525b41b60b52965bda53ce7d47377ada1 + languageName: node + linkType: hard + +"react-reconciler@npm:^0.33.0": + version: 0.33.0 + resolution: "react-reconciler@npm:0.33.0" + dependencies: + scheduler: "npm:^0.27.0" + peerDependencies: + react: ^19.2.0 + checksum: 10c0/3f7b27ea8d0ff4c8bf0e402a285e1af9b7d0e6f4c1a70a28f4384938bc1130bc82a90a31df0b79ef5e380e2e55e2598bd90b4dbf802b1203d735ba0355817d3a + languageName: node + linkType: hard + +"react-responsive@npm:^9.0.2": + version: 9.0.2 + resolution: "react-responsive@npm:9.0.2" + dependencies: + hyphenate-style-name: "npm:^1.0.0" + matchmediaquery: "npm:^0.3.0" + prop-types: "npm:^15.6.1" + shallow-equal: "npm:^1.2.1" + peerDependencies: + react: ">=16.8.0" + checksum: 10c0/c58b30d8b8ff993edbc73f7de59a2d86d8abd6380b06a9b41667c3bdd5401e5e710fa1d0fdac21368759ab9b958e8b479a111329a06e9b0241ee08f500b37a20 + languageName: node + linkType: hard + +"react-router-dom@npm:^6.4.4": + version: 6.30.6 + resolution: "react-router-dom@npm:6.30.6" + dependencies: + "@remix-run/router": "npm:1.23.4" + react-router: "npm:6.30.6" + peerDependencies: + react: ">=16.8" + react-dom: ">=16.8" + checksum: 10c0/57dbb2ae4ac787f78c4d10b764c1c5223635378033e33dda4e0a4417144ae847fb43dc98008075951826258287c27c67729ca713c90957cf2ecf9055561bdc2e + languageName: node + linkType: hard + +"react-router@npm:6.30.6": + version: 6.30.6 + resolution: "react-router@npm:6.30.6" + dependencies: + "@remix-run/router": "npm:1.23.4" + peerDependencies: + react: ">=16.8" + checksum: 10c0/d101388d92a44e159ef30dd2402a7f7b6aace8f837bb42aed1f8a6b3a4eff769d1822bf69382f0acb85a790628dac081c22bedb2bca12707e0e5e91427010fc3 + languageName: node + linkType: hard + +"react@npm:^19.0.0, react@npm:^19.2.0": + version: 19.2.8 + resolution: "react@npm:19.2.8" + checksum: 10c0/5f86bdb56426652fd6d989d30a6f2e603c057272c47c9ca3a3fbe190a3a39ee9ccce937d63cfc039717abed1b8891d6a499134bc35311acc07eafdacd86537cd + languageName: node + linkType: hard + +"readdirp@npm:^4.0.1": + version: 4.1.2 + resolution: "readdirp@npm:4.1.2" + checksum: 10c0/60a14f7619dec48c9c850255cd523e2717001b0e179dc7037cfa0895da7b9e9ab07532d324bfb118d73a710887d1e35f79c495fa91582784493e085d18c72c62 + languageName: node + linkType: hard + +"reselect@npm:^5.2.0": + version: 5.3.0 + resolution: "reselect@npm:5.3.0" + checksum: 10c0/c79728e4f315152bfb2d82456d35075b705392d9e72c485d7d9b1061bd80af628416216d80f02a42f24dbb24d738aec7fec45806930aaa1d17c32e9875fa1bd6 + languageName: node + linkType: hard + +"restore-cursor@npm:^4.0.0": + version: 4.0.0 + resolution: "restore-cursor@npm:4.0.0" + dependencies: + onetime: "npm:^5.1.0" + signal-exit: "npm:^3.0.2" + checksum: 10c0/6f7da8c5e422ac26aa38354870b1afac09963572cf2879443540449068cb43476e9cbccf6f8de3e0171e0d6f7f533c2bc1a0a008003c9a525bbc098e89041318 + languageName: node + linkType: hard + +"rolldown@npm:~1.2.4": + version: 1.2.6 + resolution: "rolldown@npm:1.2.6" + dependencies: + "@oxc-project/types": "npm:=0.147.0" + "@rolldown/binding-android-arm-eabi": "npm:1.2.6" + "@rolldown/binding-android-arm64": "npm:1.2.6" + "@rolldown/binding-darwin-arm64": "npm:1.2.6" + "@rolldown/binding-darwin-x64": "npm:1.2.6" + "@rolldown/binding-freebsd-x64": "npm:1.2.6" + "@rolldown/binding-linux-arm-gnueabihf": "npm:1.2.6" + "@rolldown/binding-linux-arm64-gnu": "npm:1.2.6" + "@rolldown/binding-linux-arm64-musl": "npm:1.2.6" + "@rolldown/binding-linux-ppc64-gnu": "npm:1.2.6" + "@rolldown/binding-linux-s390x-gnu": "npm:1.2.6" + "@rolldown/binding-linux-x64-gnu": "npm:1.2.6" + "@rolldown/binding-linux-x64-musl": "npm:1.2.6" + "@rolldown/binding-openharmony-arm64": "npm:1.2.6" + "@rolldown/binding-win32-arm64-msvc": "npm:1.2.6" + "@rolldown/binding-win32-x64-msvc": "npm:1.2.6" + "@rolldown/pluginutils": "npm:^1.0.0" + dependenciesMeta: + "@rolldown/binding-android-arm-eabi": + optional: true + "@rolldown/binding-android-arm64": + optional: true + "@rolldown/binding-darwin-arm64": + optional: true + "@rolldown/binding-darwin-x64": + optional: true + "@rolldown/binding-freebsd-x64": + optional: true + "@rolldown/binding-linux-arm-gnueabihf": + optional: true + "@rolldown/binding-linux-arm64-gnu": + optional: true + "@rolldown/binding-linux-arm64-musl": + optional: true + "@rolldown/binding-linux-ppc64-gnu": + optional: true + "@rolldown/binding-linux-s390x-gnu": + optional: true + "@rolldown/binding-linux-x64-gnu": + optional: true + "@rolldown/binding-linux-x64-musl": + optional: true + "@rolldown/binding-openharmony-arm64": + optional: true + "@rolldown/binding-win32-arm64-msvc": + optional: true + "@rolldown/binding-win32-x64-msvc": + optional: true + bin: + rolldown: ./bin/cli.mjs + checksum: 10c0/34f8fd335f3ec8e55002aa4f2f6f0ffd4dc1f2c47f877d4ad37fbe62c97d3b63486705494e739b9e451cccf4c4bc4e0f14648d7f978427964c693c0165289308 + languageName: node + linkType: hard + +"run-async@npm:^4.0.6": + version: 4.0.6 + resolution: "run-async@npm:4.0.6" + checksum: 10c0/3e512c689d356238a06a59839deddeb09aec23bc66f780fe970fcf12b64bfc00c6880e9530ea22b8cf88a927145561f5a43343d8be87166e849ec0daaa3d4cf4 + languageName: node + linkType: hard + +"safer-buffer@npm:>= 2.1.2 < 3.0.0": + version: 2.1.2 + resolution: "safer-buffer@npm:2.1.2" + checksum: 10c0/7e3c8b2e88a1841c9671094bbaeebd94448111dd90a81a1f606f3f67708a6ec57763b3b47f06da09fc6054193e0e6709e77325415dc8422b04497a8070fa02d4 + languageName: node + linkType: hard + +"scheduler@npm:^0.27.0": + version: 0.27.0 + resolution: "scheduler@npm:0.27.0" + checksum: 10c0/4f03048cb05a3c8fddc45813052251eca00688f413a3cee236d984a161da28db28ba71bd11e7a3dd02f7af84ab28d39fb311431d3b3772fed557945beb00c452 + languageName: node + linkType: hard + +"semver@npm:7.6.3": + version: 7.6.3 + resolution: "semver@npm:7.6.3" + bin: + semver: bin/semver.js + checksum: 10c0/88f33e148b210c153873cb08cfe1e281d518aaa9a666d4d148add6560db5cd3c582f3a08ccb91f38d5f379ead256da9931234ed122057f40bb5766e65e58adaf + languageName: node + linkType: hard + +"semver@npm:^7.3.5, semver@npm:^7.8.5": + version: 7.8.5 + resolution: "semver@npm:7.8.5" + bin: + semver: bin/semver.js + checksum: 10c0/b1f3127a5be8125a94f37188b361c212466c292c6910adce3ec106cff5dc211ccaedc4739c11bb70fda59d6fc1f040a9bca289f4e093451521a2372e5231fe0c + languageName: node + linkType: hard + +"shallow-equal@npm:^1.2.1": + version: 1.2.1 + resolution: "shallow-equal@npm:1.2.1" + checksum: 10c0/51e03abadd97c9ebe590547d92db9148446962a3f23a3a0fb1ba2fccab80af881eef0ff1f8ccefd3f066c0bc5a4c8ca53706194813b95c8835fa66448a843a26 + languageName: node + linkType: hard + +"sharp@npm:^0.35.3": + version: 0.35.4 + resolution: "sharp@npm:0.35.4" + dependencies: + "@img/colour": "npm:^1.1.0" + "@img/sharp-darwin-arm64": "npm:0.35.4" + "@img/sharp-darwin-x64": "npm:0.35.4" + "@img/sharp-freebsd-wasm32": "npm:0.35.4" + "@img/sharp-libvips-darwin-arm64": "npm:1.3.3" + "@img/sharp-libvips-darwin-x64": "npm:1.3.3" + "@img/sharp-libvips-linux-arm": "npm:1.3.3" + "@img/sharp-libvips-linux-arm64": "npm:1.3.3" + "@img/sharp-libvips-linux-ppc64": "npm:1.3.3" + "@img/sharp-libvips-linux-riscv64": "npm:1.3.3" + "@img/sharp-libvips-linux-s390x": "npm:1.3.3" + "@img/sharp-libvips-linux-x64": "npm:1.3.3" + "@img/sharp-libvips-linuxmusl-arm64": "npm:1.3.3" + "@img/sharp-libvips-linuxmusl-x64": "npm:1.3.3" + "@img/sharp-linux-arm": "npm:0.35.4" + "@img/sharp-linux-arm64": "npm:0.35.4" + "@img/sharp-linux-ppc64": "npm:0.35.4" + "@img/sharp-linux-riscv64": "npm:0.35.4" + "@img/sharp-linux-s390x": "npm:0.35.4" + "@img/sharp-linux-x64": "npm:0.35.4" + "@img/sharp-linuxmusl-arm64": "npm:0.35.4" + "@img/sharp-linuxmusl-x64": "npm:0.35.4" + "@img/sharp-webcontainers-wasm32": "npm:0.35.4" + "@img/sharp-win32-arm64": "npm:0.35.4" + "@img/sharp-win32-ia32": "npm:0.35.4" + "@img/sharp-win32-x64": "npm:0.35.4" + detect-libc: "npm:^2.1.2" + semver: "npm:^7.8.5" + dependenciesMeta: + "@img/sharp-darwin-arm64": + optional: true + "@img/sharp-darwin-x64": + optional: true + "@img/sharp-freebsd-wasm32": + optional: true + "@img/sharp-libvips-darwin-arm64": + optional: true + "@img/sharp-libvips-darwin-x64": + optional: true + "@img/sharp-libvips-linux-arm": + optional: true + "@img/sharp-libvips-linux-arm64": + optional: true + "@img/sharp-libvips-linux-ppc64": + optional: true + "@img/sharp-libvips-linux-riscv64": + optional: true + "@img/sharp-libvips-linux-s390x": + optional: true + "@img/sharp-libvips-linux-x64": + optional: true + "@img/sharp-libvips-linuxmusl-arm64": + optional: true + "@img/sharp-libvips-linuxmusl-x64": + optional: true + "@img/sharp-linux-arm": + optional: true + "@img/sharp-linux-arm64": + optional: true + "@img/sharp-linux-ppc64": + optional: true + "@img/sharp-linux-riscv64": + optional: true + "@img/sharp-linux-s390x": + optional: true + "@img/sharp-linux-x64": + optional: true + "@img/sharp-linuxmusl-arm64": + optional: true + "@img/sharp-linuxmusl-x64": + optional: true + "@img/sharp-webcontainers-wasm32": + optional: true + "@img/sharp-win32-arm64": + optional: true + "@img/sharp-win32-ia32": + optional: true + "@img/sharp-win32-x64": + optional: true + peerDependenciesMeta: + "@types/node": + optional: true + checksum: 10c0/dbcc0909200cbff98e82805c86085b94afbd45f373897a04b21e52e9bbc1671dc64970350dd3cb6b2d0970f3444be7100d728005dc6620e39b599269ed7e15eb + languageName: node + linkType: hard + +"siginfo@npm:^2.0.0": + version: 2.0.0 + resolution: "siginfo@npm:2.0.0" + checksum: 10c0/3def8f8e516fbb34cb6ae415b07ccc5d9c018d85b4b8611e3dc6f8be6d1899f693a4382913c9ed51a06babb5201639d76453ab297d1c54a456544acf5c892e34 + languageName: node + linkType: hard + +"signal-exit@npm:^3.0.2, signal-exit@npm:^3.0.7": + version: 3.0.7 + resolution: "signal-exit@npm:3.0.7" + checksum: 10c0/25d272fa73e146048565e08f3309d5b942c1979a6f4a58a8c59d5fa299728e9c2fcd1a759ec870863b1fd38653670240cd420dad2ad9330c71f36608a6a1c912 + languageName: node + linkType: hard + +"signal-exit@npm:^4.1.0": + version: 4.1.0 + resolution: "signal-exit@npm:4.1.0" + checksum: 10c0/41602dce540e46d599edba9d9860193398d135f7ff72cab629db5171516cfae628d21e7bfccde1bbfdf11c48726bc2a6d1a8fb8701125852fbfda7cf19c6aa83 + languageName: node + linkType: hard + +"slice-ansi@npm:^8.0.0": + version: 8.0.0 + resolution: "slice-ansi@npm:8.0.0" + dependencies: + ansi-styles: "npm:^6.2.3" + is-fullwidth-code-point: "npm:^5.1.0" + checksum: 10c0/0ce4aa91febb7cea4a00c2c27bb820fa53b6d2862ce0f80f7120134719f7914fc416b0ed966cf35250a3169e152916392f35917a2d7cad0fcc5d8b841010fa9a + languageName: node + linkType: hard + +"source-map-js@npm:^1.2.1": + version: 1.2.1 + resolution: "source-map-js@npm:1.2.1" + checksum: 10c0/7bda1fc4c197e3c6ff17de1b8b2c20e60af81b63a52cb32ec5a5d67a20a7d42651e2cb34ebe93833c5a2a084377e17455854fee3e21e7925c64a51b6a52b0faf + languageName: node + linkType: hard + +"stack-utils@npm:^2.0.6": + version: 2.0.6 + resolution: "stack-utils@npm:2.0.6" + dependencies: + escape-string-regexp: "npm:^2.0.0" + checksum: 10c0/651c9f87667e077584bbe848acaecc6049bc71979f1e9a46c7b920cad4431c388df0f51b8ad7cfd6eed3db97a2878d0fc8b3122979439ea8bac29c61c95eec8a + languageName: node + linkType: hard + +"stackback@npm:0.0.2": + version: 0.0.2 + resolution: "stackback@npm:0.0.2" + checksum: 10c0/89a1416668f950236dd5ac9f9a6b2588e1b9b62b1b6ad8dff1bfc5d1a15dbf0aafc9b52d2226d00c28dffff212da464eaeebfc6b7578b9d180cef3e3782c5983 + languageName: node + linkType: hard + +"state-local@npm:^1.0.6": + version: 1.0.7 + resolution: "state-local@npm:1.0.7" + checksum: 10c0/8dc7daeac71844452fafb514a6d6b6f40d7e2b33df398309ea1c7b3948d6110c57f112b7196500a10c54fdde40291488c52c875575670fb5c819602deca48bd9 + languageName: node + linkType: hard + +"std-env@npm:^4.0.0-rc.1": + version: 4.2.0 + resolution: "std-env@npm:4.2.0" + checksum: 10c0/40ac525ce7b7c556abc332a7376f14356eeb1a7f17f6ff9a003eb9f52326ff1f3745d3e1b43452675b1ec6fcc319f1b1d6f3b0d386cf3f91058479ad883cff69 + languageName: node + linkType: hard + +"string-width@npm:^7.0.0": + version: 7.2.0 + resolution: "string-width@npm:7.2.0" + dependencies: + emoji-regex: "npm:^10.3.0" + get-east-asian-width: "npm:^1.0.0" + strip-ansi: "npm:^7.1.0" + checksum: 10c0/eb0430dd43f3199c7a46dcbf7a0b34539c76fe3aa62763d0b0655acdcbdf360b3f66f3d58ca25ba0205f42ea3491fa00f09426d3b7d3040e506878fc7664c9b9 + languageName: node + linkType: hard + +"string-width@npm:^8.1.0, string-width@npm:^8.1.1, string-width@npm:^8.2.0": + version: 8.2.2 + resolution: "string-width@npm:8.2.2" + dependencies: + get-east-asian-width: "npm:^1.5.0" + strip-ansi: "npm:^7.1.2" + checksum: 10c0/895624534e4e2f229e880bbc30aa77bdeb758e1a0edce203e0a17b8b4f08b8d3c1156516efc50a35e3efd0052d938a73797692111250ac9f52ee384710a01714 + languageName: node + linkType: hard + +"strip-ansi@npm:^7.1.0, strip-ansi@npm:^7.1.2": + version: 7.2.0 + resolution: "strip-ansi@npm:7.2.0" + dependencies: + ansi-regex: "npm:^6.2.2" + checksum: 10c0/544d13b7582f8254811ea97db202f519e189e59d35740c46095897e254e4f1aa9fe1524a83ad6bc5ad67d4dd6c0281d2e0219ed62b880a6238a16a17d375f221 + languageName: node + linkType: hard + +"subscriptions-transport-ws@npm:^0.9.16": + version: 0.9.19 + resolution: "subscriptions-transport-ws@npm:0.9.19" + dependencies: + backo2: "npm:^1.0.2" + eventemitter3: "npm:^3.1.0" + iterall: "npm:^1.2.1" + symbol-observable: "npm:^1.0.4" + ws: "npm:^5.2.0 || ^6.0.0 || ^7.0.0" + peerDependencies: + graphql: ">=0.10.0" + checksum: 10c0/6f2ade56865f0ba291d3ff82c79781b051c2374873bac853286fedfdbc05001b8c4018ab7cba44af667ead7f573e48d18892d58a8f9ca8d90dfb4bff5c125045 + languageName: node + linkType: hard + +"symbol-observable@npm:^1.0.4": + version: 1.2.0 + resolution: "symbol-observable@npm:1.2.0" + checksum: 10c0/009fee50798ef80ed4b8195048288f108b03de162db07493f2e1fd993b33fafa72d659e832b584da5a2427daa78e5a738fb2a9ab027ee9454252e0bedbcd1fdc + languageName: node + linkType: hard + +"tagged-tag@npm:^1.0.0": + version: 1.0.0 + resolution: "tagged-tag@npm:1.0.0" + checksum: 10c0/91d25c9ffb86a91f20522cefb2cbec9b64caa1febe27ad0df52f08993ff60888022d771e868e6416cf2e72dab68449d2139e8709ba009b74c6c7ecd4000048d1 + languageName: node + linkType: hard + +"tar@npm:^7.5.4": + version: 7.5.22 + resolution: "tar@npm:7.5.22" + dependencies: + "@isaacs/fs-minipass": "npm:^4.0.0" + chownr: "npm:^3.0.0" + minipass: "npm:^7.1.2" + minizlib: "npm:^3.1.0" + yallist: "npm:^5.0.0" + checksum: 10c0/1311f6be85a8157ac4c9147bae43e13923d2a1aae15e4aa1bd5239e4e03d2cf53cfe103dde7f35832fbb4c938b042856bc8e9a0afd29abd05e2d1608788c4fea + languageName: node + linkType: hard + +"terminal-size@npm:^4.0.1": + version: 4.0.1 + resolution: "terminal-size@npm:4.0.1" + checksum: 10c0/89afd9d816dd9dbfe4499da9aeea70491bbde4ff4592226a9c8ac71074a7580afead6a78e95ecc35f6d42e09087b55ffcb1019302cd55e0cc957b6ce5c4847e8 + languageName: node + linkType: hard + +"tinybench@npm:^2.9.0": + version: 2.9.0 + resolution: "tinybench@npm:2.9.0" + checksum: 10c0/c3500b0f60d2eb8db65250afe750b66d51623057ee88720b7f064894a6cb7eb93360ca824a60a31ab16dab30c7b1f06efe0795b352e37914a9d4bad86386a20c + languageName: node + linkType: hard + +"tinyexec@npm:^1.0.2": + version: 1.3.0 + resolution: "tinyexec@npm:1.3.0" + checksum: 10c0/e9b89f97489d2aab2cef408da279e6b32547e738d1275032ccb8fd0028a006d93eb70fc51c6cffd9fc2f5aca6c2a273d8b6f73b52d46ee5116da6b94969ef958 + languageName: node + linkType: hard + +"tinyglobby@npm:^0.2.12, tinyglobby@npm:^0.2.15, tinyglobby@npm:^0.2.17": + version: 0.2.17 + resolution: "tinyglobby@npm:0.2.17" + dependencies: + fdir: "npm:^6.5.0" + picomatch: "npm:^4.0.4" + checksum: 10c0/7f7bb0f197c88bc4b20c231e0deca4240ca3bf313a88f5a7fee93a872b84966a4d50220947c0455ad07a60b3b360961c5b7fd979222aeb716a9f99b412002e4c + languageName: node + linkType: hard + +"tinyrainbow@npm:^3.1.0": + version: 3.1.1 + resolution: "tinyrainbow@npm:3.1.1" + checksum: 10c0/f9d2743832c6191f753408f36224fe817620b8abcef572b2e570204c673a901d753ff84ca8e7b88f9c79e934295b3ffc6fcbc56a06f126e24e1ec6186dcad40d + languageName: node + linkType: hard + +"tr46@npm:~0.0.3": + version: 0.0.3 + resolution: "tr46@npm:0.0.3" + checksum: 10c0/047cb209a6b60c742f05c9d3ace8fa510bff609995c129a37ace03476a9b12db4dbf975e74600830ef0796e18882b2381fb5fb1f6b4f96b832c374de3ab91a11 + languageName: node + linkType: hard + +"tsconfck@npm:^3.0.3": + version: 3.1.6 + resolution: "tsconfck@npm:3.1.6" + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + bin: + tsconfck: bin/tsconfck.js + checksum: 10c0/269c3c513540be44844117bb9b9258fe6f8aeab026d32aeebf458d5299125f330711429dbb556dbf125a0bc25f4a81e6c24ac96de2740badd295c3fb400f66c4 + languageName: node + linkType: hard + +"tslib@npm:^1.9.3": + version: 1.14.1 + resolution: "tslib@npm:1.14.1" + checksum: 10c0/69ae09c49eea644bc5ebe1bca4fa4cc2c82b7b3e02f43b84bd891504edf66dbc6b2ec0eef31a957042de2269139e4acff911e6d186a258fb14069cd7f6febce2 + languageName: node + linkType: hard + +"tslib@npm:^2.0.0, tslib@npm:^2.4.0": + version: 2.8.1 + resolution: "tslib@npm:2.8.1" + checksum: 10c0/9c4759110a19c53f992d9aae23aac5ced636e99887b51b9e61def52611732872ff7668757d4e4c61f19691e36f4da981cd9485e869b4a7408d689f6bf1f14e62 + languageName: node + linkType: hard + +"twenty-client-sdk@npm:2.35.1": + version: 2.35.1 + resolution: "twenty-client-sdk@npm:2.35.1" + dependencies: + "@genql/runtime": "npm:^2.10.0" + esbuild: "npm:^0.28.1" + graphql: "npm:^16.8.1" + lodash: "npm:^4.17.21" + prettier: "npm:^3.8.3" + checksum: 10c0/c3618f929bb5e92bc02dfe9bb506c903c0f30a4fee6ad23578a0544dcfc8a0b8cebceef8ef3925eaaefa798e1f57bf8cb4f249bb1ad38a45eec8ca8a83edaf34 + languageName: node + linkType: hard + +"twenty-sdk@npm:2.35.1": + version: 2.35.1 + resolution: "twenty-sdk@npm:2.35.1" + dependencies: + "@sniptt/guards": "npm:^0.2.0" + axios: "npm:^1.16.0" + chalk: "npm:^5.3.0" + chokidar: "npm:^4.0.0" + commander: "npm:^12.0.0" + esbuild: "npm:^0.28.1" + graphql: "npm:^16.8.1" + graphql-sse: "npm:^2.5.4" + ink: "npm:^6.8.0" + inquirer: "npm:^14.0.0" + jsonc-parser: "npm:^3.2.0" + preact: "npm:^10.28.3" + react: "npm:^19.2.0" + react-dom: "npm:^19.2.0" + semver: "npm:7.6.3" + sharp: "npm:^0.35.3" + tinyglobby: "npm:^0.2.15" + twenty-client-sdk: "npm:2.35.1" + typescript: "npm:^5.9.3" + uuid: "npm:^13.0.2" + bin: + twenty: dist/cli.cjs + checksum: 10c0/589f9af78aff68f529eaad5697cd31b530ab3b348e5866acb08dd17b254bbe83fcce0488f46143b3bab9a292e34c6a6fbe67733a8bafa4caa8a07bc6d2dcaefb + languageName: node + linkType: hard + +"twenty-ui@npm:1.0.0-alpha.1": + version: 1.0.0-alpha.1 + resolution: "twenty-ui@npm:1.0.0-alpha.1" + dependencies: + "@base-ui/react": "npm:^1.5.0" + "@monaco-editor/react": "npm:^4.7.0" + "@radix-ui/colors": "npm:^3.0.0" + "@sniptt/guards": "npm:^0.2.0" + "@tabler/icons-react": "npm:^3.31.0" + clsx: "npm:^2.1.1" + react-responsive: "npm:^9.0.2" + react-router-dom: "npm:^6.4.4" + type-fest: "npm:^4.10.1" + zod: "npm:^4.1.11" + peerDependencies: + monaco-editor: ">= 0.25.0 < 1" + react: ^19.0.0 + react-dom: ^19.0.0 + checksum: 10c0/213582e582949d6ffe90a7a8ab89a1d4e9dc724d34ed1b80bd7d50e834a8cb8539723556d41b39dc658f76f176bd8490ae0da8a3d6a713e79e6573b732084179 + languageName: node + linkType: hard + +"type-fest@npm:^4.10.1": + version: 4.41.0 + resolution: "type-fest@npm:4.41.0" + checksum: 10c0/f5ca697797ed5e88d33ac8f1fec21921839871f808dc59345c9cf67345bfb958ce41bd821165dbf3ae591cedec2bf6fe8882098dfdd8dc54320b859711a2c1e4 + languageName: node + linkType: hard + +"type-fest@npm:^5.4.1": + version: 5.8.0 + resolution: "type-fest@npm:5.8.0" + dependencies: + tagged-tag: "npm:^1.0.0" + checksum: 10c0/c8aae118a763d550a9552a511dff6b71840a23dab4edf693cf1c4df22596942794e6f6723389bd9036a90182249d915158bacf0815a1ae87f05f901b1d5f574e + languageName: node + linkType: hard + +"typescript@npm:^5.9.3": + version: 5.9.3 + resolution: "typescript@npm:5.9.3" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/6bd7552ce39f97e711db5aa048f6f9995b53f1c52f7d8667c1abdc1700c68a76a308f579cd309ce6b53646deb4e9a1be7c813a93baaf0a28ccd536a30270e1c5 + languageName: node + linkType: hard + +"typescript@patch:typescript@npm%3A^5.9.3#optional!builtin": + version: 5.9.3 + resolution: "typescript@patch:typescript@npm%3A5.9.3#optional!builtin::version=5.9.3&hash=5786d5" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/ad09fdf7a756814dce65bc60c1657b40d44451346858eea230e10f2e95a289d9183b6e32e5c11e95acc0ccc214b4f36289dcad4bf1886b0adb84d711d336a430 + languageName: node + linkType: hard + +"undici-types@npm:~7.18.0": + version: 7.18.2 + resolution: "undici-types@npm:7.18.2" + checksum: 10c0/85a79189113a238959d7a647368e4f7c5559c3a404ebdb8fc4488145ce9426fcd82252a844a302798dfc0e37e6fb178ff481ed03bc4caf634c5757d9ef43521d + languageName: node + linkType: hard + +"undici-types@npm:~8.3.0": + version: 8.3.0 + resolution: "undici-types@npm:8.3.0" + checksum: 10c0/c8aa7e2fbebfce519654dafadc0ece59be888d2ccaf180fb4495da875e7b536d2456345c384069c7e6f3e9c9ab7435f074957da306f142343eee86ff8048855a + languageName: node + linkType: hard + +"undici@npm:^8.4.1": + version: 8.10.0 + resolution: "undici@npm:8.10.0" + checksum: 10c0/37ae2b1db8f65c3a003504e2040e96887b99f9db16ba07dcf49c37ed8b7f8c72f4452f2d46f52f7c9e49518fe8325e3b5e7e02ac3a2424886bd67d4b62a0ecab + languageName: node + linkType: hard + +"unfetch@npm:^4.2.0": + version: 4.2.0 + resolution: "unfetch@npm:4.2.0" + checksum: 10c0/a5c0a896a6f09f278b868075aea65652ad185db30e827cb7df45826fe5ab850124bf9c44c4dafca4bf0c55a0844b17031e8243467fcc38dd7a7d435007151f1b + languageName: node + linkType: hard + +"use-sync-external-store@npm:^1.6.0": + version: 1.6.0 + resolution: "use-sync-external-store@npm:1.6.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + checksum: 10c0/35e1179f872a53227bdf8a827f7911da4c37c0f4091c29b76b1e32473d1670ebe7bcd880b808b7549ba9a5605c233350f800ffab963ee4a4ee346ee983b6019b + languageName: node + linkType: hard + +"utility-types@npm:^3.10.0": + version: 3.11.0 + resolution: "utility-types@npm:3.11.0" + checksum: 10c0/2f1580137b0c3e6cf5405f37aaa8f5249961a76d26f1ca8efc0ff49a2fc0e0b2db56de8e521a174d075758e0c7eb3e590edec0832eb44478b958f09914920f19 + languageName: node + linkType: hard + +"uuid@npm:^13.0.2": + version: 13.0.2 + resolution: "uuid@npm:13.0.2" + bin: + uuid: dist-node/bin/uuid + checksum: 10c0/32c7ee84fa7c7966cc09b3a1514a752a8e2f609f15c00033fbaedc0255d577d1877b839f11ee537f37e587bbc620bbb98c3b3a1482931b8ed47d79497cd7a7cd + languageName: node + linkType: hard + +"vite-tsconfig-paths@npm:^4.2.1": + version: 4.3.2 + resolution: "vite-tsconfig-paths@npm:4.3.2" + dependencies: + debug: "npm:^4.1.1" + globrex: "npm:^0.1.2" + tsconfck: "npm:^3.0.3" + peerDependencies: + vite: "*" + peerDependenciesMeta: + vite: + optional: true + checksum: 10c0/f390ac1d1c3992fc5ac50f9274c1090f8b55ab34a89ea88893db9a6924a3b26c9f64bc1163615150ad100749db73b6b2cf1d57f6cd60df6e762ceb5b8ad30024 + languageName: node + linkType: hard + +"vite@npm:^6.0.0 || ^7.0.0 || ^8.0.0": + version: 8.2.2 + resolution: "vite@npm:8.2.2" + dependencies: + fsevents: "npm:~2.3.3" + lightningcss: "npm:^1.33.0" + picomatch: "npm:^4.0.5" + postcss: "npm:^8.5.26" + rolldown: "npm:~1.2.4" + tinyglobby: "npm:^0.2.17" + peerDependencies: + "@types/node": ^20.19.0 || >=22.12.0 + "@vitejs/devtools": ^0.4.0 || ^0.5.0 + esbuild: ^0.27.0 || ^0.28.0 + jiti: ">=1.21.0" + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: ">=0.54.8" + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + dependenciesMeta: + fsevents: + optional: true + peerDependenciesMeta: + "@types/node": + optional: true + "@vitejs/devtools": + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + bin: + vite: bin/vite.js + checksum: 10c0/94cbbbdc38ad500dcb86b6202ddd14aa41d05c80739766cada9bbe250b410d1a27be433c9c491ed39744019471ac1e27a59908616a88c42f9787bdb6bdca49d2 + languageName: node + linkType: hard + +"vitest@npm:^4.0.0": + version: 4.1.11 + resolution: "vitest@npm:4.1.11" + dependencies: + "@vitest/expect": "npm:4.1.11" + "@vitest/mocker": "npm:4.1.11" + "@vitest/pretty-format": "npm:4.1.11" + "@vitest/runner": "npm:4.1.11" + "@vitest/snapshot": "npm:4.1.11" + "@vitest/spy": "npm:4.1.11" + "@vitest/utils": "npm:4.1.11" + es-module-lexer: "npm:^2.0.0" + expect-type: "npm:^1.3.0" + magic-string: "npm:^0.30.21" + obug: "npm:^2.1.1" + pathe: "npm:^2.0.3" + picomatch: "npm:^4.0.3" + std-env: "npm:^4.0.0-rc.1" + tinybench: "npm:^2.9.0" + tinyexec: "npm:^1.0.2" + tinyglobby: "npm:^0.2.15" + tinyrainbow: "npm:^3.1.0" + vite: "npm:^6.0.0 || ^7.0.0 || ^8.0.0" + why-is-node-running: "npm:^2.3.0" + peerDependencies: + "@edge-runtime/vm": "*" + "@opentelemetry/api": ^1.9.0 + "@types/node": ^20.0.0 || ^22.0.0 || >=24.0.0 + "@vitest/browser-playwright": 4.1.11 + "@vitest/browser-preview": 4.1.11 + "@vitest/browser-webdriverio": 4.1.11 + "@vitest/coverage-istanbul": 4.1.11 + "@vitest/coverage-v8": 4.1.11 + "@vitest/ui": 4.1.11 + happy-dom: "*" + jsdom: "*" + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + "@edge-runtime/vm": + optional: true + "@opentelemetry/api": + optional: true + "@types/node": + optional: true + "@vitest/browser-playwright": + optional: true + "@vitest/browser-preview": + optional: true + "@vitest/browser-webdriverio": + optional: true + "@vitest/coverage-istanbul": + optional: true + "@vitest/coverage-v8": + optional: true + "@vitest/ui": + optional: true + happy-dom: + optional: true + jsdom: + optional: true + vite: + optional: false + bin: + vitest: ./vitest.mjs + checksum: 10c0/3fa0948cf74adcccc8cbcdb4e6d30ada6933bdfb1816ff99200f3d3b689325b37dc483b22535b57b6d911f7a7b64eaa6a5f8da1606cfe7f2466f48000c21e296 + languageName: node + linkType: hard + +"webidl-conversions@npm:^3.0.0": + version: 3.0.1 + resolution: "webidl-conversions@npm:3.0.1" + checksum: 10c0/5612d5f3e54760a797052eb4927f0ddc01383550f542ccd33d5238cfd65aeed392a45ad38364970d0a0f4fea32e1f4d231b3d8dac4a3bdd385e5cf802ae097db + languageName: node + linkType: hard + +"whatwg-url@npm:^5.0.0": + version: 5.0.0 + resolution: "whatwg-url@npm:5.0.0" + dependencies: + tr46: "npm:~0.0.3" + webidl-conversions: "npm:^3.0.0" + checksum: 10c0/1588bed84d10b72d5eec1d0faa0722ba1962f1821e7539c535558fb5398d223b0c50d8acab950b8c488b4ba69043fd833cc2697056b167d8ad46fac3995a55d5 + languageName: node + linkType: hard + +"which@npm:^7.0.0": + version: 7.0.0 + resolution: "which@npm:7.0.0" + dependencies: + isexe: "npm:^4.0.0" + bin: + node-which: bin/which.js + checksum: 10c0/ca0b54f198f78bbc4b7c02e34bda8d335cb352e0adb4cbca1c37b1a957af3a879a82c4c27ca6525bc942f548d8b64f816ef6528360af9f3de55ffb9b979b620d + languageName: node + linkType: hard + +"why-is-node-running@npm:^2.3.0": + version: 2.3.0 + resolution: "why-is-node-running@npm:2.3.0" + dependencies: + siginfo: "npm:^2.0.0" + stackback: "npm:0.0.2" + bin: + why-is-node-running: cli.js + checksum: 10c0/1cde0b01b827d2cf4cb11db962f3958b9175d5d9e7ac7361d1a7b0e2dc6069a263e69118bd974c4f6d0a890ef4eedfe34cf3d5167ec14203dbc9a18620537054 + languageName: node + linkType: hard + +"widest-line@npm:^6.0.0": + version: 6.0.0 + resolution: "widest-line@npm:6.0.0" + dependencies: + string-width: "npm:^8.1.0" + checksum: 10c0/735f1fdcd97fe765a07bb8b5e73c020bed8e53ab34e83ce0ef01693ba3c914d9e7977fe5f5facf0d0b670297a82dd5e376d3efa0896860dfcdaf7cd6924c0fb7 + languageName: node + linkType: hard + +"wrap-ansi@npm:^9.0.0": + version: 9.0.2 + resolution: "wrap-ansi@npm:9.0.2" + dependencies: + ansi-styles: "npm:^6.2.1" + string-width: "npm:^7.0.0" + strip-ansi: "npm:^7.1.0" + checksum: 10c0/3305839b9a0d6fb930cb63a52f34d3936013d8b0682ff3ec133c9826512620f213800ffa19ea22904876d5b7e9a3c1f40682f03597d986a4ca881fa7b033688c + languageName: node + linkType: hard + +"ws@npm:^5.2.0 || ^6.0.0 || ^7.0.0": + version: 7.5.13 + resolution: "ws@npm:7.5.13" + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + checksum: 10c0/e223726bbda5768ed58ec9252d04eda7018ef380ea122bb94e595a4d7a02b5baeb423933936576fddb8fa51b642375f7ae814bf0f6f5d9a3ce290566578a8c5a + languageName: node + linkType: hard + +"ws@npm:^6.1.4": + version: 6.2.6 + resolution: "ws@npm:6.2.6" + dependencies: + async-limiter: "npm:~1.0.0" + checksum: 10c0/24245efc3c09b6175df32ddb4a06f85432c4c88abb0e75f43f2551c1d5de456218b1b6c7685c414b8e03a68a167488e4aadf04a895681c8e01502025ce505742 + languageName: node + linkType: hard + +"ws@npm:^8.18.0": + version: 8.21.3 + resolution: "ws@npm:8.21.3" + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ">=5.0.2" + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + checksum: 10c0/7b28dc2863ea0e2cece68d142a3eee90361021b73f750431e6d8076bb7dede5fdfdb75b3d29534b62f411261147b76b1bc80fb5cc63ab0aabd467280e85b22e0 + languageName: node + linkType: hard + +"yallist@npm:^5.0.0": + version: 5.0.0 + resolution: "yallist@npm:5.0.0" + checksum: 10c0/a499c81ce6d4a1d260d4ea0f6d49ab4da09681e32c3f0472dee16667ed69d01dae63a3b81745a24bd78476ec4fcf856114cb4896ace738e01da34b2c42235416 + languageName: node + linkType: hard + +"yoga-layout@npm:~3.2.1": + version: 3.2.1 + resolution: "yoga-layout@npm:3.2.1" + checksum: 10c0/9001e51be993c85e03757e5a04a2b61b8b30c9e5a7865d0156ca87a6431a3b717d51eb4990bfe588189fcfeac688dd9c3de707bbd50d1c344a84e63974cc54a8 + languageName: node + linkType: hard + +"zen-observable-ts@npm:^0.8.21": + version: 0.8.21 + resolution: "zen-observable-ts@npm:0.8.21" + dependencies: + tslib: "npm:^1.9.3" + zen-observable: "npm:^0.8.0" + checksum: 10c0/fe4a02f862b5f7e8ae0f86230c37b84c7d5611f5c206981afb4043e732d04cf7067a6cbe1ba82d20f18b735a3387937195a12542158a631d308ae3959a1d93c4 + languageName: node + linkType: hard + +"zen-observable@npm:^0.8.0": + version: 0.8.15 + resolution: "zen-observable@npm:0.8.15" + checksum: 10c0/71cc2f2bbb537300c3f569e25693d37b3bc91f225cefce251a71c30bc6bb3e7f8e9420ca0eb57f2ac9e492b085b8dfa075fd1e8195c40b83c951dd59c6e4fbf8 + languageName: node + linkType: hard + +"zod@npm:^4.1.11": + version: 4.4.3 + resolution: "zod@npm:4.4.3" + checksum: 10c0/7ea31b558e88f9faf44f31dd185e2e1cbf51fed3081787fb96cc2534749b50c0acfc6da7f0922a7353ed092dd358c7d50c28ea96c94d04af64191bd33152eca3 + languageName: node + linkType: hard